From 7f45b993e4b82e72b3e45b9b4ef73d44d86dd47a Mon Sep 17 00:00:00 2001 From: Sergey Vasilenko Date: Sat, 18 Apr 2015 17:13:02 +0300 Subject: [PATCH] L23network v1.1 got from pre-release FUEL 36f30ae7f19092a61eebb0522ca20d27468b4cbf has minimum modifications --- .fixtures.yml | 7 + Gemfile | 23 +- Modulefile | 2 +- README.md | 688 ++++++++++++------ Rakefile | 12 +- lib/facter/l23_os.rb | 18 + lib/facter/ovs_vlan_splinters.rb | 35 + lib/facter/util/netstat.rb | 2 +- .../parser/functions/cidr_to_netmask.rb | 3 +- .../parser/functions/debug__dump_to_file.rb | 11 + .../parser/functions/default_provider_for.rb | 19 + .../parser/functions/ethtool_convert_hash.rb | 73 ++ .../functions/generate_network_config.rb | 384 ++++++++++ ...get_hash_with_defaults_and_deprecations.rb | 46 ++ .../functions/get_network_role_property.rb | 93 +++ .../functions/get_pair_of_jack_names.rb | 15 + lib/puppet/parser/functions/get_patch_name.rb | 15 + .../parser/functions/get_provider_for.rb | 24 + .../functions/get_route_resource_name.rb | 13 + .../parser/functions/lib/prepare_cidr.rb | 11 +- lib/puppet/parser/functions/merge_arrays.rb | 34 - .../parser/functions/netmask_to_cidr.rb | 18 + lib/puppet/parser/functions/nodes_to_hosts.rb | 21 + .../functions/prepare_network_config.rb | 22 + .../parser/functions/sanitize_bool_in_hash.rb | 22 + lib/puppet/provider/k_mod/lnx.rb | 63 ++ .../provider/l23_stored_config/lnx_centos6.rb | 17 + .../provider/l23_stored_config/lnx_ubuntu.rb | 26 + .../provider/l23_stored_config/ovs_centos6.rb | 65 ++ .../provider/l23_stored_config/ovs_ubuntu.rb | 39 + lib/puppet/provider/l23_stored_config_base.rb | 66 ++ .../provider/l23_stored_config_centos6.rb | 334 +++++++++ .../provider/l23_stored_config_ubuntu.rb | 452 ++++++++++++ lib/puppet/provider/l2_base.rb | 628 ++++++++++++++++ lib/puppet/provider/l2_bond/lnx.rb | 201 +++++ lib/puppet/provider/l2_bond/ovs.rb | 112 +++ lib/puppet/provider/l2_bridge/lnx.rb | 96 +++ lib/puppet/provider/l2_bridge/ovs.rb | 103 +++ lib/puppet/provider/l2_ovs_bond/ovs.rb | 50 -- lib/puppet/provider/l2_ovs_bridge/ovs.rb | 85 --- lib/puppet/provider/l2_ovs_port/ovs.rb | 55 -- lib/puppet/provider/l2_patch/ovs.rb | 188 +++++ lib/puppet/provider/l2_port/lnx.rb | 205 ++++++ lib/puppet/provider/l2_port/ovs.rb | 100 +++ lib/puppet/provider/l3_if_downup/ruby.rb | 171 ----- .../provider/l3_if_downup/util/netstat.rb | 71 -- lib/puppet/provider/l3_ifconfig/lnx.rb | 243 +++++++ lib/puppet/provider/l3_route/lnx.rb | 172 +++++ lib/puppet/provider/lnx_base.rb | 94 +++ lib/puppet/provider/ovs_base.rb | 141 ++++ lib/puppet/type/cfg.rb | 1 + lib/puppet/type/k_mod.rb | 14 + lib/puppet/type/l23_stored_config.rb | 332 +++++++++ lib/puppet/type/l2_bond.rb | 217 ++++++ lib/puppet/type/l2_bridge.rb | 115 +++ lib/puppet/type/l2_ovs_bond.rb | 56 -- lib/puppet/type/l2_ovs_bridge.rb | 46 -- lib/puppet/type/l2_ovs_port.rb | 62 -- lib/puppet/type/l2_patch.rb | 92 +++ lib/puppet/type/l2_port.rb | 226 ++++++ lib/puppet/type/l3_if_downup.rb | 26 +- lib/puppet/type/l3_ifconfig.rb | 145 ++++ lib/puppet/type/l3_route.rb | 108 +++ .../l23_ethtool_name_commands_mapping.rb | 42 ++ lib/puppetx/l23_hash_tools.rb | 66 ++ lib/puppetx/l23_network_scheme.rb | 12 + lib/puppetx/l23_utils.rb | 105 +++ manifests/examples/run_network_scheme.pp | 12 + manifests/hosts_file.pp | 18 + manifests/init.pp | 63 +- manifests/l2.pp | 99 ++- manifests/l2/bond.pp | 189 ++++- manifests/l2/bond_interface.pp | 40 + manifests/l2/bridge.pp | 60 +- manifests/l2/centos_upndown_scripts.pp | 10 +- manifests/l2/patch.pp | 69 ++ manifests/l2/port.pp | 135 +++- manifests/l3/create_br_iface.pp | 123 ---- manifests/l3/defaultroute.pp | 19 +- manifests/l3/ifconfig.pp | 269 +++---- manifests/l3/route.pp | 34 + manifests/params.pp | 76 +- metadata.json | 30 + spec/classes/bond__spec.rb | 79 ++ spec/classes/empty_network_scheme__spec.rb | 39 + spec/classes/l23network_init__spec.rb | 67 ++ spec/defines/ifconfig__dhcp__spec.rb | 144 ++++ spec/defines/ifconfig__spec.rb | 49 ++ spec/defines/l2_bond__spec.rb | 213 ++++++ spec/defines/l2_bridge__spec.rb | 171 +++++ spec/defines/l2_patch__spec.rb | 111 +++ spec/defines/l2_port__spec.rb | 224 ++++++ spec/fixtures/manifests/site.pp | 1 + .../get_network_role_property__spec.rb | 78 ++ .../functions/get_pair_of_jack_names__spec.rb | 36 + spec/functions/get_patch_name__spec.rb | 37 + spec/functions/merge_arrays__spec.rb | 25 - spec/functions/sanitize_bool_in_hash__spec.rb | 154 ++++ spec/spec.opts | 1 - spec/spec_helper.rb | 10 +- templates/ethtool_Debian.erb | 5 + templates/ip_route_Debian.erb | 4 + templates/ipconfig_Debian_bondslave.erb | 3 - templates/ipconfig_Debian_dhcp.erb | 10 - templates/ipconfig_Debian_manual.erb | 11 - templates/ipconfig_Debian_static.erb | 19 - templates/ipconfig_RedHat_bondslave.erb | 6 - templates/ipconfig_RedHat_dhcp.erb | 10 - ...pt.erb => ipconfig_RedHat_ifdn-script.erb} | 0 ...pt.erb => ipconfig_RedHat_ifup-script.erb} | 5 + templates/ipconfig_RedHat_manual.erb | 9 - templates/ipconfig_RedHat_static.erb | 15 - templates/route_RedHat.erb | 3 + 113 files changed, 8428 insertions(+), 1410 deletions(-) create mode 100644 .fixtures.yml create mode 100644 lib/facter/l23_os.rb create mode 100644 lib/facter/ovs_vlan_splinters.rb create mode 100644 lib/puppet/parser/functions/debug__dump_to_file.rb create mode 100644 lib/puppet/parser/functions/default_provider_for.rb create mode 100644 lib/puppet/parser/functions/ethtool_convert_hash.rb create mode 100644 lib/puppet/parser/functions/generate_network_config.rb create mode 100644 lib/puppet/parser/functions/get_hash_with_defaults_and_deprecations.rb create mode 100644 lib/puppet/parser/functions/get_network_role_property.rb create mode 100644 lib/puppet/parser/functions/get_pair_of_jack_names.rb create mode 100644 lib/puppet/parser/functions/get_patch_name.rb create mode 100644 lib/puppet/parser/functions/get_provider_for.rb create mode 100644 lib/puppet/parser/functions/get_route_resource_name.rb delete mode 100644 lib/puppet/parser/functions/merge_arrays.rb create mode 100644 lib/puppet/parser/functions/netmask_to_cidr.rb create mode 100644 lib/puppet/parser/functions/nodes_to_hosts.rb create mode 100644 lib/puppet/parser/functions/prepare_network_config.rb create mode 100644 lib/puppet/parser/functions/sanitize_bool_in_hash.rb create mode 100644 lib/puppet/provider/k_mod/lnx.rb create mode 100644 lib/puppet/provider/l23_stored_config/lnx_centos6.rb create mode 100644 lib/puppet/provider/l23_stored_config/lnx_ubuntu.rb create mode 100644 lib/puppet/provider/l23_stored_config/ovs_centos6.rb create mode 100644 lib/puppet/provider/l23_stored_config/ovs_ubuntu.rb create mode 100644 lib/puppet/provider/l23_stored_config_base.rb create mode 100644 lib/puppet/provider/l23_stored_config_centos6.rb create mode 100644 lib/puppet/provider/l23_stored_config_ubuntu.rb create mode 100644 lib/puppet/provider/l2_base.rb create mode 100644 lib/puppet/provider/l2_bond/lnx.rb create mode 100644 lib/puppet/provider/l2_bond/ovs.rb create mode 100644 lib/puppet/provider/l2_bridge/lnx.rb create mode 100644 lib/puppet/provider/l2_bridge/ovs.rb delete mode 100644 lib/puppet/provider/l2_ovs_bond/ovs.rb delete mode 100644 lib/puppet/provider/l2_ovs_bridge/ovs.rb delete mode 100644 lib/puppet/provider/l2_ovs_port/ovs.rb create mode 100644 lib/puppet/provider/l2_patch/ovs.rb create mode 100644 lib/puppet/provider/l2_port/lnx.rb create mode 100644 lib/puppet/provider/l2_port/ovs.rb delete mode 100644 lib/puppet/provider/l3_if_downup/ruby.rb delete mode 100644 lib/puppet/provider/l3_if_downup/util/netstat.rb create mode 100644 lib/puppet/provider/l3_ifconfig/lnx.rb create mode 100644 lib/puppet/provider/l3_route/lnx.rb create mode 100644 lib/puppet/provider/lnx_base.rb create mode 100644 lib/puppet/provider/ovs_base.rb create mode 100644 lib/puppet/type/k_mod.rb create mode 100644 lib/puppet/type/l23_stored_config.rb create mode 100644 lib/puppet/type/l2_bond.rb create mode 100644 lib/puppet/type/l2_bridge.rb delete mode 100644 lib/puppet/type/l2_ovs_bond.rb delete mode 100644 lib/puppet/type/l2_ovs_bridge.rb delete mode 100644 lib/puppet/type/l2_ovs_port.rb create mode 100644 lib/puppet/type/l2_patch.rb create mode 100644 lib/puppet/type/l2_port.rb create mode 100644 lib/puppet/type/l3_ifconfig.rb create mode 100644 lib/puppet/type/l3_route.rb create mode 100644 lib/puppetx/l23_ethtool_name_commands_mapping.rb create mode 100644 lib/puppetx/l23_hash_tools.rb create mode 100644 lib/puppetx/l23_network_scheme.rb create mode 100644 lib/puppetx/l23_utils.rb create mode 100644 manifests/examples/run_network_scheme.pp create mode 100644 manifests/hosts_file.pp create mode 100644 manifests/l2/bond_interface.pp create mode 100644 manifests/l2/patch.pp delete mode 100644 manifests/l3/create_br_iface.pp create mode 100644 manifests/l3/route.pp create mode 100644 metadata.json create mode 100644 spec/classes/bond__spec.rb create mode 100644 spec/classes/empty_network_scheme__spec.rb create mode 100644 spec/classes/l23network_init__spec.rb create mode 100644 spec/defines/ifconfig__dhcp__spec.rb create mode 100644 spec/defines/ifconfig__spec.rb create mode 100644 spec/defines/l2_bond__spec.rb create mode 100644 spec/defines/l2_bridge__spec.rb create mode 100644 spec/defines/l2_patch__spec.rb create mode 100644 spec/defines/l2_port__spec.rb create mode 100644 spec/functions/get_network_role_property__spec.rb create mode 100644 spec/functions/get_pair_of_jack_names__spec.rb create mode 100644 spec/functions/get_patch_name__spec.rb delete mode 100644 spec/functions/merge_arrays__spec.rb create mode 100644 spec/functions/sanitize_bool_in_hash__spec.rb delete mode 100644 spec/spec.opts create mode 100644 templates/ethtool_Debian.erb create mode 100644 templates/ip_route_Debian.erb delete mode 100644 templates/ipconfig_Debian_bondslave.erb delete mode 100644 templates/ipconfig_Debian_dhcp.erb delete mode 100644 templates/ipconfig_Debian_manual.erb delete mode 100644 templates/ipconfig_Debian_static.erb delete mode 100644 templates/ipconfig_RedHat_bondslave.erb delete mode 100644 templates/ipconfig_RedHat_dhcp.erb rename templates/{ipconfig_RedHat_static_down-script.erb => ipconfig_RedHat_ifdn-script.erb} (100%) rename templates/{ipconfig_RedHat_static_up-script.erb => ipconfig_RedHat_ifup-script.erb} (51%) delete mode 100644 templates/ipconfig_RedHat_manual.erb delete mode 100644 templates/ipconfig_RedHat_static.erb create mode 100644 templates/route_RedHat.erb diff --git a/.fixtures.yml b/.fixtures.yml new file mode 100644 index 0000000..89050d1 --- /dev/null +++ b/.fixtures.yml @@ -0,0 +1,7 @@ +fixtures: + repositories: + 'stdlib': 'git://github.com/puppetlabs/puppetlabs-stdlib.git' + 'filemapper': 'git://github.com/adrienthebo/puppet-filemapper.git' + 'sysctl': 'git://github.com/duritong/puppet-sysctl.git' + symlinks: + 'l23network': "#{source_dir}" \ No newline at end of file diff --git a/Gemfile b/Gemfile index 957feb6..b6036ab 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,17 @@ -#source :rubygems source 'https://rubygems.org' -gem 'rake' -gem 'puppet-lint' -gem 'rspec' -gem 'rspec-puppet' +group :development, :test do + gem 'rake', :require => false + gem 'rspec', '<=2.99', :require => false + gem 'rspec-puppet', :require => false + gem 'puppetlabs_spec_helper', :require => false + gem 'puppet-lint', :require => false +end -## Will come in handy later on. But you could just use -# gem 'puppet' -puppetversion = ENV.key?('PUPPET_VERSION') ? "~> #{ENV['PUPPET_VERSION']}" : ['>= 2.7'] -gem 'puppet', puppetversion -gem 'puppetlabs_spec_helper' +if puppetversion = ENV['PUPPET_GEM_VERSION'] + gem 'puppet', puppetversion, :require => false +else + gem 'puppet', :require => false +end +# vim:ft=ruby diff --git a/Modulefile b/Modulefile index 2b17110..1eb8038 100644 --- a/Modulefile +++ b/Modulefile @@ -1,2 +1,2 @@ -name 'Mirantis-l23network' +name 'L23network' diff --git a/README.md b/README.md index abe274b..2cfac79 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,12 @@ At this moment support Centos 6.3+ (RHEL6) and Ubuntu 12.04 or above. L23network module have a same behavior for both operation systems. +**WARNING!!!** This is a L23network v1.1, it contains some incompatibles with earlier versions. *Be carefully*. + +## Usage + +### Initializing -Usage ------ Place this module at /etc/puppet/modules/l23network or another directory with your puppet modules. Include L23network module and initialize it. It is recommended to do it on the early stage: @@ -17,265 +20,522 @@ Include L23network module and initialize it. It is recommended to do it on the e stage {'netconfig': before => Stage['main'], } - class {'l23network': stage=> 'netconfig'} - -If you do not plan to use open vSwitch you can disable it: + class { 'l23network': + use_ovs => true, + use_lnx => true, + stage => 'netconfig' + } - class {'l23network': use_ovs=>false, stage=> 'netconfig'} +Initialization class 'l23network' has following incoming parameters and its default values: + + class { 'l23network': + use_ovs => false, + use_lnx => true, + install_ovs => $use_ovs, + install_brtool => $use_lnx, + install_ethtool => $use_lnx, + install_bondtool => $use_lnx, + install_vlantool => $use_lnx, + ovs_modname => undef, + ovs_datapath_package_name => undef, + ovs_common_package_name => undef, + } +For highly customized configurations you can redefine each of ones. For example, if you plan to use open vSwitch you should enable it: + class {'l23network': + use_ovs=>true + } -L2 network configuation (Open vSwitch only) ------------------------ +### L2 network features configuation Current layout is: * *bridges* -- A "Bridge" is a virtual ethernet L2 switch. You can plug ports into it. -* *ports* -- A Port is an interface you plug into the bridge (switch). It's virtual. -* *interface* -- A physical implementation of port. +* *ports* -- A Port is an interface you plug into the bridge. It may be virtual or native interface. Then in your manifest you can either use it as a parameterized classes: - class {"l23network": } - l23network::l2::bridge{"br-mgmt": } l23network::l2::port{"eth0": bridge => "br-mgmt"} - l23network::l2::port{"mmm0": bridge => "br-mgmt"} - l23network::l2::port{"mmm1": bridge => "br-mgmt"} - l23network::l2::bridge{"br-ex": } - l23network::l2::port{"eth0": bridge => "br-ex"} - l23network::l2::port{"eth1": bridge => "br-ex", ifname_order_prefix='ovs'} - l23network::l2::port{"eee0": bridge => "br-ex", skip_existing => true} - l23network::l2::port{"eee1": bridge => "br-ex", type=>'internal'} - -You can define a type for the port. Port types are: -'system', 'internal', 'tap', 'gre', 'ipsec_gre', 'capwap', 'patch', 'null'. -If you do not define type for port (or define '') then ovs-vsctl will work by default -(see http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-vsctl.8). + l23network::l2::bridge{"br-ex": provider => ovs } + l23network::l2::port{"eth1": bridge => "br-ex" } + l23network::l2::port{"ve0": bridge => "br-ex" } + l23network::l2::port{"ve1": bridge => "br-ex" } -You can use skip_existing option if you do not want to interrupt the configuration during adding of existing port or bridge. -L3 network configuration ------------------------- +#### L2::Bridge -### Simple IP address definition, DHCP or address-less interfaces +This resource implemented for configire of bridge. - l23network::l3::ifconfig {"eth0": ipaddr=>'192.168.1.1/24'} - l23network::l3::ifconfig {"xXxXxXx": - interface => 'eth1', - ipaddr => '192.168.2.1', - netmask => '255.255.255.0' + l23network::l2::bridge { 'br1': + ensure => present, + stp => true, # or false + vendor_specific => { + ..... + }, + provider => lnx, } - l23network::l3::ifconfig {"eth2": ipaddr=>'dhcp'} - l23network::l3::ifconfig {"eth3": ipaddr=>'none'} -Option *ipaddr* can contains IP address, 'dhcp', or 'none' string. In this example we describe configuration of 4 network interfaces: -* Interface *eth0* have short CIDR-notated form of IP address definition. -* Interface *eth1* -* Interface *eth2* will be configured to use dhcp protocol. -* Interface *eth3* will be configured as interface without IP address. - Often it's need for create "master" interface for 802.1q vlans (in native linux implementation) - or as slave interface for bonding. +Non-obligatory fields: -CIDR-notated form of IP address have more priority, that classic *ipaddr* and *netmask* definition. -If you ommited *natmask* and not used CIDR-notated form -- will be used -default *netmask* value as '255.255.255.0'. +* *stp* -- enable/disable STP for bridge +* *bpdu_forward* -- enable/disable BPDU forward on bridge +* *bridge_id* -- bridge_id for STP protocol. +* *vendor_specific* -- vendor_specific hash (see below) -### Multiple IP addresses for one interface (aliases) +#### L2::Port - l23network::l3::ifconfig {"eth0": - ipaddr => ['192.168.0.1/24', '192.168.1.1/24', '192.168.2.1/24'] - } - -You can pass list of CIDR-notated IP addresses to the *ipaddr* parameter for assign many IP addresses to one interface. -In this case will be created aliases (not a subinterfaces). Array can contains one or more elements. +Resource for configuring port L2 options. Only L2 options. For configuring +L3 options -- use *L23network::l3::ifconfig* resource -### UP and DOWN interface order + l23network::l2::port { 'eth1': + mtu => 9000, # MTU value, unchanged if absent. + onboot => true, # whether port has UP state after setup or node boot + ethtool => { + ..... + }, + vendor_specific => { + ..... + }, + provider => lnx + } - l23network::l3::ifconfig {"eth1": - ipaddr=>'192.168.1.1/24' + l23network::l2::port { 'eth1.101': + ensure => present, + bridge => 'br1', # port can be a member of bridge. + # If no value given this property was unchanged, + # if given 'absent' port will be excluded from any + # bridges. + onboot => true, + provider => lnx } - l23network::l3::ifconfig {"br-ex": - ipaddr=>'192.168.10.1/24', - ifname_order_prefix='ovs' + +Alternative VLAN definition (not recommended for 'lnx' provider) + + l23network::l2::port { 'vlan77': + vlan_id => 77, + vlan_dev => eth1, + provider => lnx } - l23network::l3::ifconfig {"aaa0": - ipaddr=>'192.168.20.1/24', - ifname_order_prefix='zzz' + +#### L2::Bond + +It's a special type of port. Designed for bonding two or more interfaces. +Detail description of bonding feature you can read here: +https://www.kernel.org/doc/Documentation/networking/bonding.txt +If you plan use LACP -- we highly recommend do not use OVS. +Also we don't recommend insert native linux bonds to OVS bridges. This case works, but leads many heavy diagnostic surprises. + + l23network::l2::bond { 'bond0': + interfaces => ['eth1', 'eth2'], + bridge => 'br0', # obligatory only for OVS provider + mtu => 9000, + onboot => true, + bond_properties => { # bond configuration properties (see bonding.txt) + mode => '803.1ad', + lacp_rate => 'slow', + xmit_hash_policy => 'encap3+4' + }, + interface_properties => { # config properties for included ifaces + ethtool => { + ..... + }, + }, + vendor_specific => { + ..... + }, + provider => lnx, } -Centos and Ubuntu (at startup OS) start and configure network interfaces in alphabetical order -interface configuration file names. In example above we change configuration process order -by *ifname_order_prefix* keyword. We will have this order: +Bond **mode** and **xmit_hash_policy** configuration has some differences for +*lnx* and *ovs* providers: - ifcfg-eth1 - ifcfg-ovs-br-ex - ifcfg-zzz-aaa0 +For *lnx* provider **mode** can be: -And the OS will configure interfaces br-ex and aaa0 after eth0 +* balance-rr *(default)* +* active-backup +* balance-xor +* broadcast +* 802.3ad +* balance-tlb +* balance-alb -### Default gateway +For 802.3ad (LACP), balance-xor, balance-tlb and balance-alb cases should be +defined **xmit_hash_policy** as one of: - l23network::l3::ifconfig {"eth1": - ipaddr => '192.168.2.5/24', - gateway => '192.168.2.1', - check_by_ping => '8.8.8.8', - check_by_ping_timeout => '30' - } +* layer2 *(default)* +* layer2+3 +* layer3+4 +* encap2+3 +* encap3+4 -In this example we define default *gateway* and options for waiting that network stay up. -Parameter *check_by_ping* define IP address, that will be pinged. Puppet will be blocked for waiting -response for *check_by_ping_timeout* seconds. -Parameter *check_by_ping* can be IP address, 'gateway', or 'none' string for disabling checking. -By default gateway will be pinged. +For *ovs* provider **mode** can be: -### DNS-specific options +* active-backup +* balance-slb *(default)* +* balance-tcp - l23network::l3::ifconfig {"eth1": - ipaddr => '192.168.2.5/24', - dns_nameservers => ['8.8.8.8','8.8.4.4'], - dns_search => ['aaa.com','bbb.com'], - dns_domain => 'qqq.com' +Field **xmit_hash_policy** shouldn't use for any mode. +For *balance-tcp* mode **lacp** bond-property should be set +to 'active' or 'passive' value. + +While bond will created also will created ports, included to the bond. This +ports will be created as slave ports for this bond with properties, listed in +**interface_properties** field. If you want more flexibility, you can create +this ports by *l23network::l2::port* resource and shouldn't define +**interface_properties** field. + +**MTU** field, setted for bond interface will be passed to interfaces, included +to the bond automatically. + +For some providers (ex: ovs) **bridge** field is obligatory. + +#### L2::Patch + +It's a patchcord for connecting two bridges. Architecture limitation: two +bridges may be connected only by one patchcord. Name for patchcord interfaces +calculated automatically and can't changed in configuration. + +OVS provider can connect OVS-to-OVS and OVS-to-LNX bridges. If you connect +OVS-to-LNX bridges, you SHOULD put OVS bridge first in order. + + l23network::l2::patch { 'patch__br0--br1': + bridges => ['br0','br1'], + vendor_specific => { + ..... + }, + } + +**Naming conviency** + +Each low-level puppet patchcord resource *l2_patch* has his name in +'bridge__%bridge1%--%bridge2%' format, and bridges provided +in alphabetical order for all providers. This resource also contain 'bridges' +property. It's a array of two bridge names. +Order of names depends of provider implementation. +For example, 'ovs' provider bridge names listed in alphabetical order for +OVS-to-OVS connectivity, and ovs-bridge always first for OVS-to-LNX bridges +connectivity. + +Each *L2_patch* instance contains read-only 'jacks' property. It's a array +of two names of jacks, 'inserted' to each bridge. This property has the same +ordering style, that a 'bridges' property for this provider. + +If patchcord connect two bridges different nature, the 'cross' flag will be +setting to 'true'. + +#### Ethtool hash and offloading settings + +You can manage offloading and another options, controlled by ethtool utility, +for any resources, that has *ethtool* hash as one of incoming properties. +*Ethtool* field look like hash of hashes. Keys of the external hash -- are a +section names from ethtool manual. Ones maps to an internal hashes. Internal +hashes -- is a option to value mappings. Option names corresponds to ethtool +output option naming. For example, you can see list of offloading options by +executing 'ethtool -k eth0'. +Ethtool options are pre-defined and stateful. +All implemented sections and options you can see bellow: + + ethtool => { + offload => { + rx-checksumming => true or false, + tx-checksumming => true or false, + scatter-gather => true or false, + tcp-segmentation-offload => true or false, + udp-fragmentation-offload => true or false, + generic-segmentation-offload => true or false, + generic-receive-offload => true or false, + large-receive-offload => true or false, + rx-vlan-offload => true or false, + tx-vlan-offload => true or false, + ntuple-filters => true or false, + receive-hashing => true or false, + rx-fcs => true or false, + rx-all => true or false, + highdma => true or false, + rx-vlan-filter => true or false, + fcoe-mtu => true or false, + l2-fwd-offload => true or false, + loopback => true or false, + tx-nocache-copy => true or false, + tx-gso-robust => true or false, + tx-fcoe-segmentation => true or false, + tx-gre-segmentation => true or false, + tx-ipip-segmentation => true or false, + tx-sit-segmentation => true or false, + tx-udp_tnl-segmentation => true or false, + tx-mpls-segmentation => true or false, + tx-vlan-stag-hw-insert => true or false, + rx-vlan-stag-hw-parse => true or false, + rx-vlan-stag-filter => true or false, + }, + #settings => { + # duplex => 'half', + # mdix => off + #} } -Also we can specify DNS nameservers, and search list that will be inserted (by resolvconf lib) to /etc/resolv.conf . -Option *dns_domain* implemented only in Ubuntu. -### DHCP-specific options +### L3 network configuration + +#### L3::Ifconfig + +Resource for configuring IP addresses on interface. Only L3 options. +For configuring L2 options -- use corresponded L2 resource. - l23network::l3::ifconfig {"eth2": - ipaddr => 'dhcp', - dhcp_hostname => 'compute312', - dhcp_nowait => false, + l23network::l3::ifconfig { 'eth1.101': + ensure => present, + ipaddr => ['192.168.10.3/24', '10.20.30.40/25'], + gateway => '192.168.10.1', + gateway_metric => 10, # different Ifconfig resources should not has + # gateways with same metrics + vendor_specific => { + ..... + }, } +**DHCP or address-less interfaces** + l23network::l3::ifconfig {"eth2": ipaddr=>'dhcp'} + l23network::l3::ifconfig {"eth3": ipaddr=>'none'} + +Option *ipaddr* can contains array of IP addresses (even setup one ipaddr), 'dhcp', or 'none' string. + +CIDR-notated form of IP address is required. + +**Default gateway** -Bonding -------- -### Using standart linux ifenslave bonding -For bonding of two interfaces you need to: -* Configure the bonded interfaces as 'none' (with no IP address) -* Specify that interfaces depend on bond_master interface -* Assign IP address to the bond-master interface -* Specify bond-specific properties for bond_master interface (if you are not happy with defaults) - -For example (defaults included): - - l23network::l3::ifconfig {'bond0': - ipaddr => '192.168.232.1', - netmask => '255.255.255.0', - bond_mode => 0, - bond_miimon => 100, - bond_lacp_rate => 1, - } -> - l23network::l3::ifconfig {'eth1': ipaddr=>'none', bond_master=>'bond0'} -> - l23network::l3::ifconfig {'eth2': ipaddr=>'none', bond_master=>'bond0'} - - -More information about bonding of network interfaces you can find in manuals for you operation system: -* https://help.ubuntu.com/community/UbuntuBonding -* http://wiki.centos.org/TipsAndTricks/BondingInterfaces - -### Using Open vSwitch -For bonding two interfaces you need: -* Specify OVS bridge -* Specify special resource "bond" and add it to bridge. Specify bond-specific parameters. -* Assign IP address to the newly-created network interface (if need). - -In this example we add "eth1" and "eth2" interfaces to bridge "bridge0" as bond "bond1". - - l23network::l2::bridge{'bridge0': } -> - l23network::l2::bond{'bond1': - bridge => 'bridge0', - ports => ['eth1', 'eth2'], - properties => [ - 'lacp=active', - 'other_config:lacp-time=fast' - ], - } -> - l23network::l3::ifconfig {'bond1': - ipaddr => '192.168.232.1', - netmask => '255.255.255.0', + l23network::l3::ifconfig {"eth1": + ipaddr => ['192.168.2.5/24'], + gateway => '192.168.2.1', + gateway_metric => 10, } -Open vSwitch provides a lot of parameter for different configurations. -We can specify them in "properties" option as list of parameter=value -(or parameter:key=value) strings. -You can find more parameters in [open vSwitch documentation page](http://openvswitch.org/support/). - - - -802.1q vlan access ports ------------------------- -### Using standart linux way - -We can use tagged vlans over ordinary network interfaces and over bonds. -L23networks module supports two types of vlan interface namings: -* *vlanXXX* -- 802.1q tag XXX from the vlan interface name. You must specify the -parent interface name in the **vlandev** parameter. -* *eth0.XXX* -- 802.1q tag XXX and parent interface name from the vlan interface name - -If you are using 802.1q vlans over bonds it is strongly recommended to use the first one. - -In this example we can see both types: - - l23network::l3::ifconfig {'vlan6': - ipaddr => '192.168.6.1', - netmask => '255.255.255.0', - vlandev => 'bond0', - } - l23network::l3::ifconfig {'vlan5': - ipaddr => 'none', - vlandev => 'bond0', - } - L23network:L3:Ifconfig['bond0'] -> L23network:L3:Ifconfig['vlan6'] -> L23network:L3:Ifconfig['vlan5'] - - l23network::l3::ifconfig {'eth0': - ipaddr => '192.168.0.5', - netmask => '255.255.255.0', - gateway => '192.168.0.1', - } -> - l23network::l3::ifconfig {'eth0.101': - ipaddr => '192.168.101.1', - netmask => '255.255.255.0', - } -> - l23network::l3::ifconfig {'eth0.102': - ipaddr => 'none', - } - -### Using open vSwitch -In the open vSwitch all internal traffic is virtually tagged. -To create a 802.1q tagged access port you need to specify a vlan tag when adding a port to the bridge. -In example above we create two ports with tags 10 and 20, and assign IP address to interface with tag 10: - - - l23network::l2::bridge{'bridge0': } -> - l23network::l2::port{'vl10': - bridge => 'bridge0', - type => 'internal', - port_properties => [ - 'tag=10' - ], - } -> - l23network::l2::port{'vl20': - bridge => 'bridge0', - type => 'internal', - port_properties => [ - 'tag=20' - ], - } -> - l23network::l3::ifconfig {'vl10': - ipaddr => '192.168.101.1/24', - } -> - l23network::l3::ifconfig {'vl20': - ipaddr => 'none', - } - -You can get more details about vlans in open vSwitch at [open vSwitch documentation page](http://openvswitch.org/support/config-cookbooks/vlan-configuration-cookbook/). +if *gateway_metric* omited, gateway will be setup without metric definition. + + + +## Network Scheme + +Network scheme is a hierarchical-based manner for define network topology for host. In following examples I use yaml format for represent it. +Main idea: + * when we got undeployed server we have some number of NICs. NICs, managed by puppet should be listed in *interfaces* section. It is giveg. + * The result of our network configuration process is a some network topology on the host and some interfaces with assignet IP addresses (or without IPs). It's a *endpoints*. + * Interfaces become endpoints by successive *transformations*. I try explain how it works in the following document: [Transformations. How they work.](https://docs.google.com/document/d/12RvBjOYO83_yqeiAgxttrRaa90-8un80aEO8OzDlQ9Y) + +Example of typical network scheme: + + --- + network_scheme: + version: 1.1 + provider: lnx + interfaces: + eth1: + mtu: 7777 + eth2: + mtu: 9000 + transformations: + - action: add-br + name: br1 + - action: add-port + name: eth1 + bridge: br1 + - action: add-br + name: br-mgmt + - action: add-port + name: eth1.101 + bridge: br-mgmt + - action: add-br + name: br-ex + - action: add-port + name: eth1.102 + bridge: br-ex + - action: add-br + name: br-storage + - action: add-port + name: eth1.103 + bridge: br-storage + - action: add-br + name: br-prv + provider: ovs + - action: add-port + name: eth2 + bridge: br-prv + provider: ovs + endpoints: + br-mgmt: + IP: + - 192.168.101.3/24 + gateway: 192.168.101.1 + gateway-metric: 100 + routes: + - net: 192.168.210.0/24 + via: 192.168.101.1 + - net: 192.168.211.0/24 + via: 192.168.101.1 + - net: 192.168.212.0/24 + via: 192.168.101.1 + br-ex: + gateway: 192.168.102.1 + IP: + - 192.168.102.3/24 + br-storage: + IP: + - 192.168.103.3/24 + br-prv: + IP: none + roles: + management: br-mgmt + ceph: br-mgmt + private: br-prv + fw-admin: br1 + ex: br-ex + floating: br-ex + storage: br-storage + + +Example of typical network scheme with bonds and disabling offloads: + + --- + network_scheme: + version: "1.1" + provider: lnx + interfaces: + eth1: + mtu: 9000 + eth2: + eth3: + transformations: + - action: add-br + name: br1 + - action: add-port + bridge: br1 + name: eth1 + ethtool: + offload: + tcp-segmentation-offload: off + udp-fragmentation-offload: off + generic-segmentation-offload: off + generic-receive-offload: off + large-receive-offload: off + - action: add-br + name: br2 + - action: add-bond + name: bond23 + bridge: br2 + interfaces: + - eth2 + - eth3 + mtu: 9000 + interface_properties: + ethtool: + offload: + tcp-segmentation-offload: off + udp-fragmentation-offload: off + bond_properties: + mode: balance-rr + xmit_hash_policy: encap3+4 + updelay: 10 + downdelay: 40 + use_carrier: 0 + - action: add-br + name: br-mgmt + - action: add-port + name: bond23.101 + bridge: br-mgmt + - action: add-br + name: br-ex + - action: add-port + name: bond23.102 + bridge: br-ex + - action: add-br + name: br-storage + - action: add-port + name: bond23.103 + bridge: br-storage + endpoints: + br-mgmt: + IP: + - 192.168.101.3/24 + gateway: 192.168.101.1 + gateway-metric: 100 + br-ex: + gateway: 192.168.102.1 + IP: + - 192.168.102.3/24 + br-storage: + IP: + - 192.168.103.3/24 + roles: + fw-admin: br1 + ex: br-ex + management: br-mgmt + storage: br-storage + + +## Vendor_specific hash + +**Vendor_specific** field - is a hash, empty by default, +required only for plug-ins. It allows plugin developers not to change custom +type code for adding non-standart parameters. Due to inheriting and extending +puppet type (not the provider one), is a non-trivial task. Plugin developers +may pass any data structures by this hash and its subhashes. All data from +this hash pass to the provider transparently. + + +## Debugging + +For debug purpose you can use following puppet calls for get prefetchable +properties for existing resources. Please note, that bridges and bonds in linux +are a port too, and present in l2_port output with corresponded flags +(if_type). + + # puppet resource -vd --trace l23_stored_config + # puppet resource -vd --trace l2_port + # puppet resource -vd --trace l2_bridge + # puppet resource -vd --trace l2_bond + # puppet resource -vd --trace l3_ifconfig + # puppet resource -vd --trace l3_route + +This commands may be fail before 1st configuration networking by L23network +because some kernel modules may wasn't loaded or some command-line tools +wasn't installed. + + +## Internals + +Each L23network resource has interface trought puppet 'define' resource. +This define may conains some non difficult logic, define provider for low-level resources and call two low level resources: + * *l23_stored_config* -- for modifying OS config files + * low level resource for configuring it in runtime (e.x: *l2_bridge*) + +### L23_stored_config custom type + +This resource is implemented to manage interface config files. Each possible +parameter should be described in resource type. + +This resource allows to forget about ERB templates, because in some cases +(i.e. bridge + port with same name + ip address for this port) we should +modify config file content three times. + + l23_stored_config { 'br1': + onboot => true, + method => manual, + mtu => 1500, + ethtool => { + ..... + }, + provider => lnx_ubuntu + } + +Place of config files location defined inside provider for corresponded +operation system and provider. Provider name for l23_stored_config depends from operation system (may be with version) and network provider (native linux, ovs, etc...) + +## References -**IMPORTANT:** You can't use vlan interface names like vlanXXX if you don't want double-tagging of you network traffic. + * [Transformations. How they work.](https://docs.google.com/document/d/12RvBjOYO83_yqeiAgxttrRaa90-8un80aEO8OzDlQ9Y) --- When I started working on this module I was inspired by https://github.com/ekarlso/puppet-vswitch. Endre, big thanks... diff --git a/Rakefile b/Rakefile index 4c48915..1d92809 100644 --- a/Rakefile +++ b/Rakefile @@ -1,8 +1,8 @@ -require 'rake' -require 'rspec/core/rake_task' +require 'rubygems' require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' -RSpec::Core::RakeTask.new(:rspec) do |t| - t.pattern = 'spec/*/*__spec.rb' - t.rspec_opts = File.read("spec/spec.opts").chomp || "" -end +PuppetLint.configuration.fail_on_warnings = false +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.send('disable_class_parameter_defaults') +PuppetLint.configuration.send('disable_class_inherits_from_params_class') \ No newline at end of file diff --git a/lib/facter/l23_os.rb b/lib/facter/l23_os.rb new file mode 100644 index 0000000..1b1850d --- /dev/null +++ b/lib/facter/l23_os.rb @@ -0,0 +1,18 @@ +# Fact: l23_os +# +# Purpose: Return return os_name for using inside l23 network module +# +Facter.add(:l23_os) do + setcode do + case Facter.value(:osfamily) + when /(?i)darwin/ + 'osx' + when /(?i)debian/ + #todo: separate upstart and systemd based + 'ubuntu' + when /(?i)redhat/ + #todo: separate centos6 and centos7 + 'centos6' + end + end +end \ No newline at end of file diff --git a/lib/facter/ovs_vlan_splinters.rb b/lib/facter/ovs_vlan_splinters.rb new file mode 100644 index 0000000..05ec8f1 --- /dev/null +++ b/lib/facter/ovs_vlan_splinters.rb @@ -0,0 +1,35 @@ +# Fact: l2_ovs_vlan_splinters_need_for +# +# Purpose: Return list of intefaces, that needs for enable OVS VLAN splinters. +# +Facter.add(:l2_ovs_vlan_splinters_need_for) do + need = Facter.value(:kernelmajversion) =~ /^(2.\d|3.[0-2])/ + need = need.nil? ? false : true + rv = [] + supported_drivers = [ + '8139cp', 'acenic', 'amd8111e', 'atl1c', 'ATL1E', 'atl1', 'atl2', + 'be2net', 'bna', 'bnx2', 'bnx2x', 'cnic', 'cxgb', 'cxgb3', + 'e1000', 'e1000e', 'enic', 'forcedeth', 'igb', 'igbvf', 'ixgb', + 'ixgbe', 'jme', 'ml4x_core', 'ns83820', 'qlge', 'r8169', 'S2IO', + 'sky2', 'starfire', 'tehuti', 'tg3', 'typhoon', 'via-velocity', + 'vxge', 'gianfar', 'ehea', 'stmmac', 'vmxnet3' #, 'pcnet32' + ] + interfaces = Facter.value(:interfaces) + if need and interfaces + for dev in interfaces.split(',').select{|x| x=~/^eth/} do + basedir = "/sys/class/net/#{dev}" + if ! (File.exists?(basedir) and File.exists?("#{basedir}/device/") and File.exists?("#{basedir}/device/uevent")) + next + end + driver = File.open("#{basedir}/device/uevent"){ |f| f.read }.split("\n").select{|x| x=~/^DRIVER=/}[0].split('=')[1] + if supported_drivers.index(driver) + rv.insert(-1, dev) + end + end + end + setcode do + rv.sort().join(',') + end +end + +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/facter/util/netstat.rb b/lib/facter/util/netstat.rb index 5faa7a0..dc38197 100644 --- a/lib/facter/util/netstat.rb +++ b/lib/facter/util/netstat.rb @@ -58,7 +58,7 @@ def self.get_route_value(route, label) c1 = map[:dest] c2 = map[label.to_sym] - ((output = get_ipv4_output).respond_to?(:lines) ? output.lines.to_a : output.to_a).collect { |s| s.split}.each { |a| + get_ipv4_output.split("\n").map { |l| l.split }.each { |a| if a[c1] == route tmp1 << a[c2] end diff --git a/lib/puppet/parser/functions/cidr_to_netmask.rb b/lib/puppet/parser/functions/cidr_to_netmask.rb index 98d71ae..89e40c5 100644 --- a/lib/puppet/parser/functions/cidr_to_netmask.rb +++ b/lib/puppet/parser/functions/cidr_to_netmask.rb @@ -1,4 +1,3 @@ - # # cidr_to_netmask.rb # @@ -20,7 +19,7 @@ module Puppet::Parser::Functions ) do |arguments| if arguments.size != 1 raise(Puppet::ParseError, "cidr_to_netmask(): Wrong number of arguments " + - "given (#{arguments.size} for 1)") + "given (#{arguments.size} for 1)") end masklen = prepare_cidr(arguments[0])[1] diff --git a/lib/puppet/parser/functions/debug__dump_to_file.rb b/lib/puppet/parser/functions/debug__dump_to_file.rb new file mode 100644 index 0000000..cf7974b --- /dev/null +++ b/lib/puppet/parser/functions/debug__dump_to_file.rb @@ -0,0 +1,11 @@ +require 'yaml' +require 'json' + +Puppet::Parser::Functions::newfunction(:debug__dump_to_file, :doc => <<-EOS + debug output to file + + EOS +) do |argv| + File.open(argv[0], 'w'){ |file| file.write argv[1].to_yaml() } +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/default_provider_for.rb b/lib/puppet/parser/functions/default_provider_for.rb new file mode 100644 index 0000000..e631074 --- /dev/null +++ b/lib/puppet/parser/functions/default_provider_for.rb @@ -0,0 +1,19 @@ + +module Puppet::Parser::Functions + newfunction(:default_provider_for, :type => :rvalue, :doc => <<-EOS + Get the default provider of a type + EOS + ) do |argv| + type_name = argv[0] + fail('No type name provided!') if ! type_name + Puppet::Type.loadall() + type_name = type_name.capitalize.to_sym + return 'undef' if ! Puppet::Type.const_defined? type_name + type = Puppet::Type.const_get type_name + provider = type.defaultprovider + return 'undef' if ! provider + rv = provider.name.to_s + debug("Default provider for type '#{type_name}' is a '#{rv}'.") + return rv + end +end \ No newline at end of file diff --git a/lib/puppet/parser/functions/ethtool_convert_hash.rb b/lib/puppet/parser/functions/ethtool_convert_hash.rb new file mode 100644 index 0000000..bfc5f89 --- /dev/null +++ b/lib/puppet/parser/functions/ethtool_convert_hash.rb @@ -0,0 +1,73 @@ +# +# ethtool_convert_hash.rb +# + +module Puppet::Parser::Functions + newfunction(:ethtool_convert_hash, :type => :rvalue, :doc => <<-EOS +This function get hash of ethtool rules and sanitize it. + +*Examples:* + + ethtool_convert_hash({ + :K => [ + 'gso off', + 'gro off' + ], + :set-channels => [ + 'rx 1', + 'tx 2', + 'other 3', + ] + }) + + should returns: + + { + '-K' => 'gso off gro off', + '--set-channels' => 'rx 1 tx 2 other 3' + } + EOS + ) do |arguments| + raise(Puppet::ParseError, "ethtool_convert_hash(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size != 1 + raise(Puppet::ParseError, "ethtool_convert_hash(): Wrong argument type -- " + + "Should be a Hash.") if ! arguments[0].is_a?(Hash) + + return nil if arguments[0].empty? + + rv = {} + + arguments[0].each do |key, body| + rkey = key.to_s + if rkey.size==1 + # one letter option + if rkey != rkey.upcase() + warn("ethtool_convert_hash(): You set lower-case parameter. In most cases it is a getter!!!") + end + rkey = "-#{rkey}" + else + # GNU-style option + rkey = "--#{rkey}" + end + if body.is_a?(String) + rbody = body + elsif body.is_a?(Array) and body.size > 0 + rbody = [] + body.each do |ll| + if ll.is_a?(String) + rbody.insert(-1,ll.strip()) + else + raise(Puppet::ParseError, 'ethtool_convert_hash(): Each parameter should be a String.') + end + end + rbody = rbody.join(' ') + else + raise(Puppet::ParseError, 'ethtool_convert_hash(): Ethtool parameters should be represented as String or non-empty Array.') + end + rv[rkey] = rbody + end + return rv + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/generate_network_config.rb b/lib/puppet/parser/functions/generate_network_config.rb new file mode 100644 index 0000000..a5c3558 --- /dev/null +++ b/lib/puppet/parser/functions/generate_network_config.rb @@ -0,0 +1,384 @@ +require 'ipaddr' +require 'yaml' +require 'forwardable' +require 'puppet/parser' +require 'puppet/parser/templatewrapper' +require 'puppet/resource/type_collection_helper' +require 'puppet/util/methodhelper' +require 'puppetx/l23_utils' +require 'puppetx/l23_network_scheme' + + + +module L23network + def self.default_offload_set + { + 'generic-receive-offload' => false, + 'generic-segmentation-offload' => false + } + end + + def self.correct_ethtool_set(prop_hash) + if !prop_hash.has_key?('ethtool') and (prop_hash.has_key?('vendor_specific') and prop_hash['vendor_specific']['disable_offloading']) + # add default offload settings if: + # * no ethtool properties given + # * "disable offload" flag given + rv = {}.merge prop_hash + rv['ethtool'] ||= {} + rv['ethtool']['offload'] = default_offload_set() + rv['vendor_specific'].delete('disable_offloading') + else + rv = prop_hash + end + return rv + end + + def self.sanitize_transformation(trans, def_provider=nil) + action = trans[:action].to_s.downcase() + # Setup defaults + rv = case action + when "noop" then { + :name => nil, + :provider => nil + } + when "add-br" then { + :name => nil, + :stp => nil, + :bpdu_forward => nil, +# :bridge_id => nil, + :external_ids => nil, +# :interface_properties => nil, + :vendor_specific => nil, + :provider => def_provider + } + when "add-port" then { + :name => nil, + :bridge => nil, +# :type => "internal", + :mtu => nil, + :ethtool => nil, + :vlan_id => nil, + :vlan_dev => nil, +# :trunks => [], + :vendor_specific => nil, + :provider => def_provider + } + when "add-bond" then { + :name => nil, + :bridge => nil, + :mtu => nil, + :interfaces => [], +# :vlan_id => 0, +# :trunks => [], + :bond_properties => nil, + :interface_properties => nil, + :vendor_specific => nil, + :provider => def_provider + } + when "add-patch" then { + :name => "unnamed", # calculated later + :bridges => [], + :mtu => nil, + :vendor_specific => nil, + :provider => def_provider + } + else + raise(Puppet::ParseError, "Unknown transformation: '#{action}'.") + end + # replace defaults to real parameters + rv.map{|k,v| rv[k] = trans[k] if trans.has_key? k } + # Validate and mahgle highly required properties. Most of properties should be validated in puppet type. + rv[:action] = action + if not rv[:name].is_a? String + raise(Puppet::ParseError, "Unnamed transformation: '#{action}'.") + end + if action == "add-patch" + if !rv[:bridges].is_a? Array or rv[:bridges].size() != 2 + raise(Puppet::ParseError, "Transformation patch have wrong 'bridges' parameter.") + end + rv[:name] = get_patch_name(rv[:bridges]) # name for patch SHOULD be auto-generated + end + return rv + end +end + +Puppet::Parser::Functions::newfunction(:generate_network_config, :type => :rvalue, :doc => <<-EOS + This function get Hash of network interfaces and endpoints configuration + and realized it. + + EOS + ) do |argv| + + def default_netmask() + "/24" + end + + def create_endpoint() + { + :ipaddr => [] + } + end + + def res_factory() + # define internal puppet parameters for creating resources + { + :br => 'l23network::l2::bridge', + :port => 'l23network::l2::port', + :bond => 'l23network::l2::bond', + :patch => 'l23network::l2::patch', + :ifconfig => 'l23network::l3::ifconfig' + } + end + + if argv.size != 0 + raise(Puppet::ParseError, "generate_network_config(): Wrong number of arguments.") + end + config_hash = L23network::Scheme.get_config(lookupvar('l3_fqdn_hostname')) + if config_hash.nil? + raise(Puppet::ParseError, "generate_network_config(...): You must call prepare_network_config(...) first!") + end + + # we can't imagine, that user can write in this field, but we try to convert to numeric and compare + if config_hash[:version].to_s.to_f < 1.1 + raise(Puppet::ParseError, "generate_network_config(...): You network_scheme hash has wrong format.\nThis parser can work with v1.1 format, please convert you config.") + end + + default_provider = config_hash[:provider] || 'lnx' + + # collect interfaces and endpoints + debug("generate_network_config(): collect interfaces") + ifconfig_order = [] + born_ports = [] + # collect L2::port properties from 'interfaces' section + ports_properties = {} # additional parameters from interfaces was stored here + config_hash[:interfaces].each do |int_name, int_properties| + int_name = int_name.to_sym() + #endpoints[int_name] = create_endpoint() + born_ports << int_name + # add some of 1st level interface properties to it's config + ports_properties[int_name] ||= {} + if ! int_properties.nil? + int_properties.each do |k,v| + if v.to_s != '' + k = k.to_s.tr('-','_').to_sym + ports_properties[int_name][k] = v + end + end + end + end + # collect L3::ifconfig properties from 'endpoints' section + debug("generate_network_config(): collect endpoints") + endpoints = {} + if config_hash[:endpoints].is_a? Hash and !config_hash[:endpoints].empty? + config_hash[:endpoints].each do |e_name, e_properties| + e_name = e_name.to_sym() + endpoints[e_name] = create_endpoint() + if ! (e_properties.nil? or e_properties.empty?) + e_properties.each do |k,v| + k = k.to_s.tr('-','_').to_sym + if k == :IP + if !(v.is_a?(Array) || ['none','dhcp',nil].include?(v)) + raise(Puppet::ParseError, "generate_network_config(): IP field for endpoint '#{e_name}' must be array of IP addresses, 'dhcp' or 'none'.") + elsif ['none','dhcp',''].include?(v.to_s) + # 'none' and 'dhcp' should be passed to resource not as list + endpoints[e_name][:ipaddr] = (v.to_s == 'dhcp' ? 'dhcp' : 'none') + else + v.each do |ip| + begin + iip = IPAddr.new(ip) # validate IP address + endpoints[e_name][:ipaddr] ||= [] + endpoints[e_name][:ipaddr] << ip + rescue + raise(Puppet::ParseError, "generate_network_config(): IP address '#{ip}' for endpoint '#{e_name}' wrong!.") + end + end + end + else + endpoints[e_name][k] = v + end + end + else + endpoints[e_name][:ipaddr] = 'none' + end + end + else + config_hash[:endpoints] = {} + end + + # pre-check and auto-add main interface for sub-interface + # to transformation if required + debug("generate_network_config(): precheck transformations") + tmp = [] + config_hash[:transformations].each do |t| + if (t[:action].match(/add-(port|bond)/) && t[:name].match(/\.\d+$/)) + # we found vlan subinterface, but main interface for one didn't defined + # earlier. We should configure main interface as unaddressed interface + # wich has state UP to prevent fails in network configuration + name = t[:name].split('.')[0] + if tmp.select{|x| x[:action].match(/add-(port|bond)/) && x[:name]==name}.empty? + debug("Auto-add 'add-port(#{name})' for '#{t[:name]}'") + tmp << { + :action => 'add-port', + :name => name + } + end + tmp << t + elsif (i=tmp.index{|x| x[:action].match(/add-(port|bond)/) && x[:name]==t[:name]}) + # we has transformation for this interface already auto-added by previous + # condition. We should merge this properties into which are autocreated + # earlier by transformation and forget this. + # + # It's looks like some strange reordering + tmp[i].merge! t + debug("Auto-add 'move-properties-for-port(#{t[:name]})', because one autocreated early.") + else + tmp << t + end + end + config_hash[:transformations] = tmp + debug("generate_network_config(): process transformations") + # execute transformations + transformation_success = [] + previous = nil + config_hash[:transformations].each do |t| + action = t[:action].strip() + if action.start_with?('add-') + action = t[:action][4..-1].to_sym() + action_ensure = nil + elsif action.start_with?('del-') + action = t[:action][4..-1].to_sym() + action_ensure = 'absent' + else + action = t[:action].to_sym() + end + + # add newly-created interface to ifconfig order + if [:noop, :port, :br].index(action) + if ! ifconfig_order.include? t[:name].to_sym() + ifconfig_order << t[:name].to_sym() + end + end + + next if action == :noop + + #debug("TXX: '#{t[:name]}' => '#{t.to_yaml.gsub('!ruby/sym ',':')}'.") + trans = L23network.sanitize_transformation(t, default_provider) + #debug("TTT: '#{trans[:name]}' => '#{trans.to_yaml.gsub('!ruby/sym ',':')}'.") + + if !ports_properties[trans[:name].to_sym()].nil? + trans.merge! ports_properties[trans[:name].to_sym()] + end + + # create puppet resources for transformations + resource = res_factory[action] + resource_properties = { } + debug("generate_network_config(): Transformation '#{trans[:name]}' will be produced as \n#{trans.to_yaml.gsub('!ruby/sym ',':')}") + + trans.select{|k,v| k != :action}.each do |k,v| + if ['Hash', 'Array'].include? v.class.to_s + resource_properties[k.to_s] = L23network.reccursive_sanitize_hash(v) + if action == :bond && k==:interface_properties + # search 'disable_offloading' flag and correct ethtool properties if required + resource_properties[k.to_s] = L23network.correct_ethtool_set(resource_properties[k.to_s]) + end + elsif ! v.nil? + resource_properties[k.to_s] = v + else + #todo(sv): more powerfull handler for 'nil' properties + end + end + + resource_properties['require'] = [previous] if previous + resource_properties = L23network.correct_ethtool_set(resource_properties) + function_create_resources([resource, { + "#{trans[:name]}" => resource_properties + }]) + transformation_success << "#{t[:action].strip()}(#{trans[:name]})" + born_ports.insert(-1, trans[:name].to_sym()) if action != :patch + previous = "#{resource}[#{trans[:name]}]" + end + + # check for all in endpoints are in interfaces or born by transformation + config_hash[:endpoints].each do |e_name, e_properties| + if not born_ports.index(e_name.to_sym()) + raise(Puppet::ParseError, "generate_network_config(): Endpoint '#{e_name}' not found in interfaces or transformations result.") + end + end + + # Calculate delta between all endpoints and ifconfig_order + ifc_delta = endpoints.keys().sort() - ifconfig_order + full_ifconfig_order = ifconfig_order + ifc_delta + + # create resources for endpoints + # in order, defined by transformation + debug("generate_network_config(): process endpoints") + create_routes=[] + full_ifconfig_order.each do |endpoint_name| + if endpoints[endpoint_name] + resource_properties = { } + + # create resource + resource = res_factory[:ifconfig] + debug("generate_network_config(): Endpoint '#{endpoint_name}' will be created with additional properties \n#{endpoints[endpoint_name].to_yaml.gsub('!ruby/sym ',':')}") + # collect properties for creating endpoint resource + endpoints[endpoint_name].each_pair do |k,v| + if k.to_s.downcase == 'routes' + # for routes we should create additional resource, not a property of ifconfig + next if ! v.is_a?(Array) + v.each do |vv| + create_routes << vv + end + elsif ['Hash', 'Array'].include? v.class.to_s + resource_properties[k.to_s] = L23network.reccursive_sanitize_hash(v) + elsif ! v.nil? + resource_properties[k.to_s] = v + else + #todo(sv): more powerfull handler for 'nil' properties + end + end + resource_properties['require'] = [previous] if previous + # # set ipaddresses + # #if endpoints[endpoint_name][:IP].empty? + # # p_resource.set_parameter(:ipaddr, 'none') + # #elsif ['none','dhcp'].index(endpoints[endpoint_name][:IP][0]) + # # p_resource.set_parameter(:ipaddr, endpoints[endpoint_name][:IP][0]) + # #else + # # ipaddrs = [] + # # endpoints[endpoint_name][:IP].each do |i| + # # if i =~ /\/\d+$/ + # # ipaddrs.insert(-1, i) + # # else + # # ipaddrs.insert(-1, "#{i}#{default_netmask()}") + # # end + # # end + # # p_resource.set_parameter(:ipaddr, ipaddrs) + # #end + # #set another (see L23network::l3::ifconfig DOC) parametres + resource_properties = L23network.correct_ethtool_set(resource_properties) + function_create_resources([resource, { + "#{endpoint_name}" => resource_properties + }]) + transformation_success << "endpoint(#{endpoint_name})" + previous = "#{resource}[#{endpoint_name}]" + end + end + + debug("generate_network_config(): process additional routes") + create_routes.each do |route| + next if !route.has_key?(:net) or !route.has_key?(:via) + route_properties = { + 'destination' => route[:net], + 'gateway' => route[:via] + } + route_properties[:metric] = route[:metric] if route[:metric].to_i > 0 + route_name = L23network.get_route_resource_name(route[:net], route[:metric]) + function_create_resources(['l23network::l3::route', { + "#{route_name}" => route_properties + }]) + transformation_success << "route_for(#{route[:net]})" + end + + debug("generate_network_config(): done...") + return transformation_success.join(" -> ") +end +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/get_hash_with_defaults_and_deprecations.rb b/lib/puppet/parser/functions/get_hash_with_defaults_and_deprecations.rb new file mode 100644 index 0000000..1bbc3e5 --- /dev/null +++ b/lib/puppet/parser/functions/get_hash_with_defaults_and_deprecations.rb @@ -0,0 +1,46 @@ + +module Puppet::Parser::Functions + newfunction(:get_hash_with_defaults_and_deprecations, :type => :rvalue, :doc => <<-EOS +This function get three hashes: + * hash with incoming parameters + * hash with default values + * hash with deprecations + +It's returns a hash +EOS + ) do |arguments| + if arguments.size != 3 + raise(Puppet::ParseError, "get_hash_with_defaults_and_deprecations(): Wrong number of arguments " + + "given (#{arguments.size} for 3)") + end + + (0..2).each{ |i| + if ! arguments[i].is_a? Hash + raise(Puppet::ParseError, + "#{i}-argument of get_hash_with_defaults_and_deprecations() has wrong type." + + " Should be a Hash." + ) + end + } + + inp, defa, depre = arguments + rv = Marshal.load(Marshal.dump(inp)) + + # Add deprecated properties + depre.each { |k,v| + if rv[k].nil? and ![nil, 'undef', :undef].index(v) + warn("You using deprecated parameter '#{k}':#{v}") + rv[k] = v + end + } + + defa.each { |k,v| + if rv[k].nil? and ![nil, 'undef', :undef].index(v) + info("Setup default parameter '#{k}':#{v}") + rv[k] = v + end + } + + return rv + end +end diff --git a/lib/puppet/parser/functions/get_network_role_property.rb b/lib/puppet/parser/functions/get_network_role_property.rb new file mode 100644 index 0000000..70c6497 --- /dev/null +++ b/lib/puppet/parser/functions/get_network_role_property.rb @@ -0,0 +1,93 @@ +require 'ipaddr' +begin + require 'puppet/parser/functions/lib/prepare_cidr.rb' +rescue LoadError => e + # puppet apply does not add module lib directories to the $LOAD_PATH (See + # #4248). It should (in the future) but for the time being we need to be + # defensive which is what this rescue block is doing. + rb_file = File.join(File.dirname(__FILE__),'lib','prepare_cidr.rb') + load rb_file if File.exists?(rb_file) or raise e +end +require 'puppetx/l23_network_scheme' + +Puppet::Parser::Functions::newfunction(:get_network_role_property, :type => :rvalue, :doc => <<-EOS + This function get get network the network_role name and mode -- + and return information about network role. + + ex: get_network_role_property('admin', 'interface') + + You can use following modes: + interface -- network interface for the network_role + ipaddr -- IP address for the network_role + cidr -- CIDR-notated IP addr and mask for the network_role + netmask -- string, contains dotted nemmask + ipaddr_netmask_pair -- list of ipaddr and netmask + + Returns NIL if role not found. + + EOS + ) do |argv| + if argv.size == 2 + mode = argv[1].to_s().upcase() + else + raise(Puppet::ParseError, "get_network_role_property(...): Wrong number of arguments.") + end + + cfg = L23network::Scheme.get_config(lookupvar('l3_fqdn_hostname')) + #File.open("/tmp/L23network_scheme.yaml", 'w'){ |file| file.write cfg.to_yaml() } + if cfg.nil? + raise(Puppet::ParseError, "get_network_role_property(...): You must call prepare_network_config(...) first!") + end + + network_role = argv[0].to_sym() + + if !cfg[:roles] || !cfg[:endpoints] || cfg[:roles].class.to_s() != "Hash" || cfg[:endpoints].class.to_s() != "Hash" + raise(Puppet::ParseError, "get_network_role_property(...): Invalid cfg_hash format.") + end + + # search interface for role + interface = cfg[:roles][network_role] + if !interface + #raise(Puppet::ParseError, "get_network_role_property(...): Undefined network_role '#{network_role}'.") + Puppet::debug("get_network_role_property(...): Undefined network_role '#{network_role}'.") + return nil + end + + # get endpoint configuration hash for interface + ep = cfg[:endpoints][interface.to_sym()] + if !ep + Puppet::debug("get_network_role_property(...): Can't find interface '#{interface}' in endpoints for network_role '#{network_role}'.") + return nil + end + + if mode == 'INTERFACE' + return interface.to_s + end + + case ep[:IP].class().to_s + when "Array" + ipaddr_cidr = ep[:IP][0] ? ep[:IP][0] : nil + when "String" + Puppet::debug("get_network_role_property(...): Can't determine dynamic or empty IP address for endpoint '#{interface}' (#{ep[:IP]}).") + return nil + else + Puppet::debug("get_network_role_property(...): invalid IP address for endpoint '#{interface}'.") + return nil + end + + rv = nil + case mode + when 'CIDR' + rv = ipaddr_cidr + when 'NETMASK' + rv = IPAddr.new('255.255.255.255').mask(prepare_cidr(ipaddr_cidr)[1]).to_s + when 'IPADDR' + rv = prepare_cidr(ipaddr_cidr)[0].to_s + when 'IPADDR_NETMASK_PAIR' + rv = prepare_cidr(ipaddr_cidr)[0].to_s, IPAddr.new('255.255.255.255').mask(prepare_cidr(ipaddr_cidr)[1]).to_s + end + + rv +end + +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/get_pair_of_jack_names.rb b/lib/puppet/parser/functions/get_pair_of_jack_names.rb new file mode 100644 index 0000000..8859fd7 --- /dev/null +++ b/lib/puppet/parser/functions/get_pair_of_jack_names.rb @@ -0,0 +1,15 @@ +require 'puppetx/l23_utils' +# +module Puppet::Parser::Functions + newfunction(:get_pair_of_jack_names, :type => :rvalue) do |arguments| + if !arguments[0].is_a? Array or arguments.size != 1 or arguments[0].size != 2 + raise(Puppet::ParseError, "get_pair_of_jack_names(): Wrong arguments given. " + + "Should be array of two bridge names.") + end + + bridges = arguments[0] + # name shouldn't depend from bridge order + L23network.get_pair_of_jack_names(bridges) + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/get_patch_name.rb b/lib/puppet/parser/functions/get_patch_name.rb new file mode 100644 index 0000000..338b968 --- /dev/null +++ b/lib/puppet/parser/functions/get_patch_name.rb @@ -0,0 +1,15 @@ +require 'puppetx/l23_utils' +# +module Puppet::Parser::Functions + newfunction(:get_patch_name, :type => :rvalue) do |arguments| + if !arguments.is_a? Array or arguments.size != 1 or arguments[0].size != 2 + raise(Puppet::ParseError, "get_patch_name(): Wrong arguments given. " + + "Should be array of two bridge names.") + end + + bridges = arguments[0] + # name shouldn't depend from bridge order + L23network.get_patch_name(bridges) + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/get_provider_for.rb b/lib/puppet/parser/functions/get_provider_for.rb new file mode 100644 index 0000000..2fb117b --- /dev/null +++ b/lib/puppet/parser/functions/get_provider_for.rb @@ -0,0 +1,24 @@ + +module Puppet::Parser::Functions + newfunction(:get_provider_for, :type => :rvalue, :doc => <<-EOS + Get the default provider of a type + EOS + ) do |argv| + type_name = argv[0] + res_name = argv[1] + fail('No type name provided!') if ! type_name + Puppet::Type.loadall() + type_name = type_name.capitalize.to_sym + return 'undef' if ! Puppet::Type.const_defined? type_name + type = Puppet::Type.const_get type_name +# require 'pry' +# binding.pry + type.loadall() + rv = type.instances.select{|i| i.name.to_s.downcase == res_name.to_s.downcase}.map{|j| j[:provider].to_s} +# require 'pry' +# binding.pry + rv = rv[0] + debug("Provider for '#{type_name}[#{res_name}]' is a '#{rv}'.") + return rv + end +end \ No newline at end of file diff --git a/lib/puppet/parser/functions/get_route_resource_name.rb b/lib/puppet/parser/functions/get_route_resource_name.rb new file mode 100644 index 0000000..85956f6 --- /dev/null +++ b/lib/puppet/parser/functions/get_route_resource_name.rb @@ -0,0 +1,13 @@ +require 'puppetx/l23_utils' +# +module Puppet::Parser::Functions + newfunction(:get_route_resource_name, :type => :rvalue) do |argv| + if argv.size < 1 and argv.size > 2 + raise(Puppet::ParseError, "get_route_resource_name(): Wrong arguments given. " + + "Should be network CIDR (or default) and optionat metric positive value.") + end + + L23network.get_route_resource_name(argv[0], argv[1]) + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/lib/prepare_cidr.rb b/lib/puppet/parser/functions/lib/prepare_cidr.rb index 43d3588..1031c41 100644 --- a/lib/puppet/parser/functions/lib/prepare_cidr.rb +++ b/lib/puppet/parser/functions/lib/prepare_cidr.rb @@ -1,16 +1,17 @@ def prepare_cidr(cidr) - if ! cidr.is_a?(String) + if !cidr.is_a?(String) raise(Puppet::ParseError, "Can't recognize IP address in non-string data.") end re_groups = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/.match(cidr) - if ! re_groups or re_groups[2].to_i > 32 + if !re_groups or re_groups[2].to_i > 32 raise(Puppet::ParseError, "cidr_to_ipaddr(): Wrong CIDR: '#{cidr}'.") - end - + end + for octet in re_groups[1].split('.') raise(Puppet::ParseError, "cidr_to_ipaddr(): Wrong CIDR: '#{cidr}'.") if octet.to_i > 255 end - + return re_groups[1], re_groups[2].to_i end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/merge_arrays.rb b/lib/puppet/parser/functions/merge_arrays.rb deleted file mode 100644 index 99ec9ab..0000000 --- a/lib/puppet/parser/functions/merge_arrays.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -# merge_arrays.rb -# - -module Puppet::Parser::Functions - newfunction(:merge_arrays, :type => :rvalue, :doc => <<-EOS -This function get arrays, merge it and return. - -*Examples:* - - merge_arrays(['a','b'], ['c','d']) - - -Would result in: ['a','b','c','d'] - EOS - ) do |arguments| - raise(Puppet::ParseError, "merge_arrays(): Wrong number of arguments " + - "given (#{arguments.size} for 1)") if arguments.size < 1 - - rv = [] - - for arg in arguments - if arg.is_a?(Array) - rv += arg - else - raise(Puppet::ParseError, 'merge_arrays(): Requires only array as argument') - end - end - - return rv - end -end - -# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/netmask_to_cidr.rb b/lib/puppet/parser/functions/netmask_to_cidr.rb new file mode 100644 index 0000000..c8eca14 --- /dev/null +++ b/lib/puppet/parser/functions/netmask_to_cidr.rb @@ -0,0 +1,18 @@ +# +# netmask_to_cidr.rb +# +require 'ipaddr' + +module Puppet::Parser::Functions + newfunction(:netmask_to_cidr, :type => :rvalue, :doc => <<-EOS +This function get classic netmask and returns cidr masklen. +EOS + ) do |arguments| + if arguments.size != 1 + raise(Puppet::ParseError, "netmask_to_cidr(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") + end + + return IPAddr.new(arguments[0]).to_i.to_s(2).count("1") + end +end diff --git a/lib/puppet/parser/functions/nodes_to_hosts.rb b/lib/puppet/parser/functions/nodes_to_hosts.rb new file mode 100644 index 0000000..41281b2 --- /dev/null +++ b/lib/puppet/parser/functions/nodes_to_hosts.rb @@ -0,0 +1,21 @@ +# +# array_or_string_to_array.rb +# + +module Puppet::Parser::Functions + newfunction(:nodes_to_hosts, :type => :rvalue, :doc => <<-EOS + convert nodes array passed from Astute into + hash for puppet `host` create_resources call + EOS + ) do |args| + hosts=Hash.new + nodes=args[0] + nodes.each do |node| + hosts[node['fqdn']]={:ip=>node['internal_address'],:host_aliases=>[node['name']]} + notice("Generating host entry #{node['name']} #{node['internal_address']} #{node['fqdn']}") + end + return hosts + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/prepare_network_config.rb b/lib/puppet/parser/functions/prepare_network_config.rb new file mode 100644 index 0000000..bb94e1f --- /dev/null +++ b/lib/puppet/parser/functions/prepare_network_config.rb @@ -0,0 +1,22 @@ +require 'puppetx/l23_network_scheme' +require 'puppetx/l23_hash_tools' + +module Puppet::Parser::Functions + newfunction(:prepare_network_config, :doc => <<-EOS + This function get Hash, and prepare it for using for network configuration. + + You must call this function as early as possible. It do nothing, only stored protected + sanitized network config for usind later. + EOS + ) do |argv| + if argv.size != 1 + raise(Puppet::ParseError, "prepare_network_config(hash): Wrong number of arguments.") + end + cfg_hash = argv[0] + Puppet::Parser::Functions.autoloader.loadall + rv = L23network.sanitize_bool_in_hash(L23network.sanitize_keys_in_hash(cfg_hash)) + L23network::Scheme.set_config(lookupvar('l3_fqdn_hostname'), rv) + return true + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/parser/functions/sanitize_bool_in_hash.rb b/lib/puppet/parser/functions/sanitize_bool_in_hash.rb new file mode 100644 index 0000000..231cc6f --- /dev/null +++ b/lib/puppet/parser/functions/sanitize_bool_in_hash.rb @@ -0,0 +1,22 @@ +require 'puppetx/l23_hash_tools' + +module Puppet::Parser::Functions + newfunction(:sanitize_bool_in_hash, :type => :rvalue, :doc => <<-EOS + This function get Hash, recursive convert string implementation + of true, false, none, null, nil to Puppet/Ruby-specific + types. + + EOS + ) do |argv| + if !argv[0].is_a? Hash or argv.size != 1 + raise(Puppet::ParseError, "sanitize_bool_in_hash(hash): Wrong number of arguments or argument type.") + end + # if ! argv[0].is_a? Hash + # raise(Puppet::ParseError, "sanitize_bool_in_hash(hash): Argument should be .") + # end + + return L23network.sanitize_bool_in_hash(argv[0]) + end +end + +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/k_mod/lnx.rb b/lib/puppet/provider/k_mod/lnx.rb new file mode 100644 index 0000000..702eb21 --- /dev/null +++ b/lib/puppet/provider/k_mod/lnx.rb @@ -0,0 +1,63 @@ +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/lnx_base') + +Puppet::Type.type(:k_mod).provide(:lnx) do + defaultfor :osfamily => :linux + commands :mod_load => 'modprobe', + :ls_mod => 'lsmod', + :mod_unload => 'rmmod' + + + def self.prefetch(resources) + interfaces = instances + resources.keys.each do |name| + if provider = interfaces.find{ |ii| ii.name == name } + resources[name].provider = provider + end + end + end + + def self.instances + rv = [] + ls_mod.split(/\n+/).sort.each do |line| + name = line.split(/\s+/)[0] + rv << new({ + :ensure => :present, + :name => name, + }) + end + rv + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + mod_load(@resource[:module]) + end + + def destroy + mod_unload(@resource[:module]) + end + + def initialize(value={}) + super(value) + @property_flush = {} + @old_property_hash = {} + @old_property_hash.merge! @property_hash + end + + # def flush + # if ! @property_flush.empty? + # debug("FLUSH properties: #{@property_flush}") + # # + # # FLUSH changed properties + # # if ! @property_flush[:mtu].nil? + # # File.open("/sys/class/net/#{@resource[:interface]}/mtu", "w") { |f| f.write(@property_flush[:mtu]) } + # # end + # @property_hash = resource.to_hash + # end + # end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l23_stored_config/lnx_centos6.rb b/lib/puppet/provider/l23_stored_config/lnx_centos6.rb new file mode 100644 index 0000000..1549b12 --- /dev/null +++ b/lib/puppet/provider/l23_stored_config/lnx_centos6.rb @@ -0,0 +1,17 @@ +require 'puppetx/filemapper' +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/l23_stored_config_centos6') + +Puppet::Type.type(:l23_stored_config).provide(:lnx_centos6, :parent => Puppet::Provider::L23_stored_config_centos6) do + + include PuppetX::FileMapper + + confine :l23_os => :centos6 + defaultfor :l23_os => :centos6 + + has_feature :provider_options + #has_feature :hotpluggable + + self.unlink_empty_files = true + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l23_stored_config/lnx_ubuntu.rb b/lib/puppet/provider/l23_stored_config/lnx_ubuntu.rb new file mode 100644 index 0000000..d704e28 --- /dev/null +++ b/lib/puppet/provider/l23_stored_config/lnx_ubuntu.rb @@ -0,0 +1,26 @@ +require 'puppetx/filemapper' +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/l23_stored_config_ubuntu') + +Puppet::Type.type(:l23_stored_config).provide(:lnx_ubuntu, :parent => Puppet::Provider::L23_stored_config_ubuntu) do + + include PuppetX::FileMapper + + defaultfor :l23_os => :ubuntu + + has_feature :provider_options + #has_feature :hotpluggable + + self.unlink_empty_files = true + + def self.check_if_provider(if_data) + if if_data[:if_provider] == 'auto' + if_data[:if_provider] = :lnx + true + else + if_data[:if_provider] = nil + false + end + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l23_stored_config/ovs_centos6.rb b/lib/puppet/provider/l23_stored_config/ovs_centos6.rb new file mode 100644 index 0000000..e288343 --- /dev/null +++ b/lib/puppet/provider/l23_stored_config/ovs_centos6.rb @@ -0,0 +1,65 @@ +require 'puppetx/filemapper' +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/l23_stored_config_centos6') + +Puppet::Type.type(:l23_stored_config).provide(:ovs_centos6, :parent => Puppet::Provider::L23_stored_config_centos6) do + + include PuppetX::FileMapper + + confine :l23_os => :centos6 + + has_feature :provider_options + + self.unlink_empty_files = true + + def self.property_mappings + rv = super + rv.merge!({ + :devicetype => 'DEVICETYPE', + }) + return rv + end + + def self.properties_fake + rv = super + rv.push(:devicetype) + return rv + end + + #Dirty hack which writes config files for OVS + #bridges into /tmp directory + def select_file + if name == 'br-prv' or name == 'br-floating' + "/tmp/ifcfg-#{name}" + else + "#{self.class.script_directory}/ifcfg-#{name}" + end + end + + def self.unmangle__if_type(provider, val) + if val == :bridge + val = :OVSBridge + else + val.to_s.capitalize.intern + end + end + + def self.mangle__if_type(val) + if val == :OVSBridge + val = :bridge + else + val.to_s.downcase.intern + end + end + + #Dirty hack which deletes OVS bridges from patch OVS + #interfaces + def self.unmangle__bridge(provider, val) + if val.length == 2 + val.delete('br-prv') if val.include?('br-prv') + val.delete('br-floating') if val.include?('br-floating') + val + end + end + +end +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/provider/l23_stored_config/ovs_ubuntu.rb b/lib/puppet/provider/l23_stored_config/ovs_ubuntu.rb new file mode 100644 index 0000000..bf73d23 --- /dev/null +++ b/lib/puppet/provider/l23_stored_config/ovs_ubuntu.rb @@ -0,0 +1,39 @@ +require 'puppetx/filemapper' +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/l23_stored_config_ubuntu') + +Puppet::Type.type(:l23_stored_config).provide(:ovs_ubuntu, :parent => Puppet::Provider::L23_stored_config_ubuntu) do + + include PuppetX::FileMapper + + has_feature :provider_options + + self.unlink_empty_files = true + + def self.property_mappings + rv = super + rv.merge!({ + :onboot => 'allow-ovs' + }) + return rv + end + + def self.check_if_provider(if_data) + #((if_data[:if_provider] == 'allow-ovs') ? true : false) + if if_data[:if_provider] == 'allow-ovs' + if_data[:if_provider] = :ovs + true + else + if_data[:if_provider] = nil + false + end + end + + def self.mangle__type(val) + :ethernet + end + def self.unmangle__type(val) + nil + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l23_stored_config_base.rb b/lib/puppet/provider/l23_stored_config_base.rb new file mode 100644 index 0000000..147df3f --- /dev/null +++ b/lib/puppet/provider/l23_stored_config_base.rb @@ -0,0 +1,66 @@ +# type for managing persistent interface config options +# Inspired by puppet-network module. Adrien, thanks. + +require 'puppetx/l23_utils' + +class Puppet::Provider::L23_stored_config_base < Puppet::Provider + + COMMENT_CHAR = '#' + + # The valid vlan ID range is 0-4095; 4096 is out of range + VLAN_RANGE_REGEX = %r[\d{1,3}|40[0-9][0-5]] + + # @return [Regexp] The regular expression for interface scripts + SCRIPT_REGEX = %r[\Aifcfg-[a-z]+[\w\d-]+(?::\d+|\.#{VLAN_RANGE_REGEX})?\Z] + + def self.script_directory + raise "Should be implemented in more specific class." + end + + class MalformedInterfacesError < Puppet::Error + def initialize(msg = nil) + msg = "Malformed config file; cannot instantiate stored_config resources for interface #{name}" if msg.nil? + super + end + end + + def self.raise_malformed + @failed = true + raise MalformedInterfacesError + end + + # Map provider instances to files based on their name + # + # @return [String] The path of the file for the given interface resource + # + # @example + # prov = RedhatProvider.new(:name => 'eth1') + # prov.select_file # => '/etc/sysconfig/network-scripts/ifcfg-eth1' + # + def select_file + "#{self.class.script_directory}/ifcfg-#{name}" + end + + # Scan all files in the networking directory for interfaces + # + # @param script_dir [String] The path to the networking scripts, defaults to + # {#SCRIPT_DIRECTORY} + # + # @return [Array] All network-script config files on this machine. + # + # @example + # RedhatProvider.target_files + # # => ['/etc/sysconfig/network-scripts/ifcfg-eth0', '/etc/sysconfig/network-scripts/ifcfg-eth1'] + def self.target_files(script_dir = nil) + script_dir ||= script_directory + return [] if ! File.directory?(script_dir) + entries = Dir.entries(script_dir).select {|entry| entry.match SCRIPT_REGEX} + entries.map {|entry| File.join(script_directory, entry)} + end + + def self.post_flush_hook(filename) + File.chmod(0644, filename) if File.exist? filename + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l23_stored_config_centos6.rb b/lib/puppet/provider/l23_stored_config_centos6.rb new file mode 100644 index 0000000..da8a27d --- /dev/null +++ b/lib/puppet/provider/l23_stored_config_centos6.rb @@ -0,0 +1,334 @@ +require 'puppetx/l23_ethtool_name_commands_mapping' +require File.join(File.dirname(__FILE__), 'l23_stored_config_base') + +class Puppet::Provider::L23_stored_config_centos6 < Puppet::Provider::L23_stored_config_base + + # @return [String] The path to network-script directory on redhat systems + def self.script_directory + '/etc/sysconfig/network-scripts' + end + + def self.property_mappings + { + :method => 'BOOTPROTO', + :ipaddr => 'IPADDR', + :name => 'DEVICE', + :onboot => 'ONBOOT', + :mtu => 'MTU', + :vlan_id => 'VLAN', + :vlan_dev => 'PHYSDEV', + :vlan_mode => 'VLAN_NAME_TYPE', + :if_type => 'TYPE', + :bridge => 'BRIDGE', + :prefix => 'PREFIX', + :gateway => 'GATEWAY', + :bond_master => 'MASTER', + :slave => 'SLAVE', + :bond_mode => 'mode', + :bond_miimon => 'miimon', + :bonding_opts => 'BONDING_OPTS', + :bond_lacp_rate => 'lacp_rate', + :bond_xmit_hash_policy => 'xmit_hash_policy', + :ethtool => 'ETHTOOL_OPTS', + } + end + def property_mappings + self.class.property_mappings + end + + # In the interface config files those fields should be written as boolean + def self.boolean_properties + [ + :hotplug, + :onboot, + :vlan_id, + ] + end + def boolean_properties + self.class.boolean_properties + end + + def self.properties_fake + [ + :prefix, + :vlan_mode, + :vlan_dev, + :slave, + :bonding_opts, + ] + end + def properties_fake + self.class.properties_fake + end + + # This is a hook method that will be called by PuppetX::Filemapper + # + # @param [String] filename The path of the interfaces file being parsed + # @param [String] contents The contents of the given file + # + # @return [Array>] A single element array containing + # the key/value pairs of properties parsed from the file. + # + # @example + # RedhatProvider.parse_file('/etc/sysconfig/network-scripts/ifcfg-eth0', #) + # # => [ + # # { + # # :name => 'eth0', + # # :ipaddress => '169.254.0.1', + # # :netmask => '255.255.0.0', + # # }, + # # ] + + def self.parse_file(filename, contents) + # WARNING!!! + # this implementation can parce only one interface per file file format + + # Split up the file into lines + lines = contents.split("\n") + # Strip out all comments + lines.map! { |line| line.sub(/#.*$/, '') } + # Remove all blank lines + lines.reject! { |line| line.match(/^\s*$/) } + + # initialize hash as predictible values + hash = {} + dirty_iface_name = nil + if (m = filename.match(%r/ifcfg-(\S+)$/)) + # save iface name from file name. One will be used if iface name not defined inside config. + dirty_iface_name = m[1].strip + end + + # Convert the data into key/value pairs + pair_regex = %r/^\s*(.+?)\s*=\s*(.*)\s*$/ + + lines.each do |line| + if (m = line.match(pair_regex)) + key = m[1].strip + val = m[2].strip + hash[key] = val + else + raise Puppet::Error, %{#{filename} is malformed; "#{line}" did not match "#{pair_regex.to_s}"} + end + hash + end + if hash.has_key?('IPADDR') + hash['IPADDR'] = "#{hash['IPADDR']}/#{hash['PREFIX']}" + hash.delete('PREFIX') + end + + if hash.has_key?('BONDING_OPTS') + bonding_opts_line = hash['BONDING_OPTS'].scan(/"([^"]*)"/).to_s.split + bonding_opts_line.each do | bond_opt | + if (bom = bond_opt.match(pair_regex)) + hash[bom[1].strip] = bom[2].strip + else + raise Puppet::Error, %{#{filename} is malformed; "#{line}" did not match "#{pair_regex.to_s}"} + end + end + hash.delete('BONDING_OPTS') + end + + props = self.mangle_properties(hash) + props.merge!({:family => :inet}) + + debug("parse_file('#{filename}'): #{props.inspect}") + [props] + end + + def self.check_if_provider(if_data) + raise Puppet::Error, "self.check_if_provider(if_data) Should be implemented in more specific class." + end + + def self.mangle_properties(pairs) + props = {} + + # Unquote all values + pairs.each_pair do |key, val| + next if ! (val.is_a? String or val.is_a? Symbol) + if (munged = val.to_s.gsub(/['"]/, '')) + pairs[key] = munged + end + end + + # For each interface attribute that we recognize it, add the value to the + # hash with our expected label + property_mappings.each_pair do |type_name, in_config_name| + if (val = pairs[in_config_name]) + # We've recognized a value that maps to an actual type property, delete + # it from the pairs and copy it as an actual property + pairs.delete(in_config_name) + mangle_method_name="mangle__#{type_name}" + if self.respond_to?(mangle_method_name) + rv = self.send(mangle_method_name, val) + else + rv = val + end + props[type_name] = rv if ! [nil, :absent].include? rv + end + end + + boolean_properties.each do |bool_property| + if props[bool_property] + props[bool_property] = ! (props[bool_property] =~ /^\s*(yes|on)\s*$/i).nil? + else + props[bool_property] = :absent + end + end + + props + end + + def self.mangle__bridge(val) + Array(val) + end + + def self.mangle__if_type(val) + val.to_s.downcase.to_sym + end + + def self.mangle__method(val) + (['manual', 'static'].include? val.to_s.downcase) ? :none : val.to_sym + end + + def self.mangle__ethtool(val) + rv = {} + val.split(' ;').each do | section | + section_config = section.split(' ') + feature_params = {} + section_name = '' + L23network.ethtool_name_commands_mapping.each do |key, value | + section_name = key if value==section_config[0].split(' ')[0] + value.each { | k, v | section_name = key if v==section_config[0].split(' ')[0] } if value.is_a?(Hash) + end + next if section_name == '' + section_features = section_config.select {|k| k != section_config[0]} + section_features.each do | feature | + k,v = feature.split(' ') + tk = Hash[L23network.ethtool_name_commands_mapping[section_name].select { |key, value| value==k }] + next if tk == '' + feature_params[tk.keys.to_s] = ((v=='on' ? true : false)) + end + rv[section_name] = feature_params + end + rv + end + + ### + # Hash to file + + def self.format_file(filename, providers) + if providers.length == 0 + return "" + elsif providers.length > 1 + raise Puppet::DevError, "Unable to support multiple interfaces [#{providers.map(&:name).join(',')}] in a single file #{filename}" + end + + content = [] + provider = providers[0] + + # Map everything to a flat hash + props = {} + + property_mappings.keys.select{|v| ! properties_fake.include?(v)}.each do |type_name| + val = provider.send(type_name) + if val and val.to_s != 'absent' + props[type_name] = val + end + end + + if props.has_key?(:ipaddr) + props[:ipaddr], props[:prefix] = props[:ipaddr].to_s.split('/') + end + if props.has_key?(:bond_master) + props[:slave] = 'yes' + end + + debug("format_file('#{filename}')::properties: #{props.inspect}") + pairs = self.unmangle_properties(provider, props) + + if pairs.has_key?('mode') + bond_options = "mode=#{pairs['mode']} miimon=#{pairs['miimon']}" + if pairs.has_key?('lacp_rate') + bond_options = "#{bond_options} lacp_rate=#{pairs['lacp_rate']}" + pairs.delete('lacp_rate') + end + if pairs.has_key?('xmit_hash_policy') + bond_options = "#{bond_options} xmit_hash_policy=#{pairs['xmit_hash_policy']}" + pairs.delete('xmit_hash_policy') + end + pairs['BONDING_OPTS'] = "\"#{bond_options}\"" + pairs.delete('mode') + pairs.delete('miimon') + end + + if pairs['TYPE'] == :OVSBridge + pairs['DEVICETYPE'] = 'ovs' + end + + pairs.each_pair do |key, val| + content << "#{key}=#{val}" if ! val.nil? + end + + debug("format_file('#{filename}')::content: #{content.inspect}") + content << '' + content.join("\n") + end + + def self.unmangle_properties(provider, props) + pairs = {} + + boolean_properties.each do |bool_property| + if ! props[bool_property].nil? + props[bool_property] = (props[bool_property].to_s.downcase == 'true' || props[bool_property].integer?) ? 'yes' : 'no' + end + end + + property_mappings.each_pair do |type_name, in_config_name| + if (val = props[type_name]) + props.delete(type_name) + mangle_method_name="unmangle__#{type_name}" + if self.respond_to?(mangle_method_name) + rv = self.send(mangle_method_name, provider, val) + else + rv = val + end + pairs[in_config_name] = rv if ! ['', 'absent'].include? rv.to_s.downcase + end + end + + pairs + end + + def self.unmangle__bridge(provider, val) + (['', 'absent'] & Array(val).map{|a| a.to_s.downcase}.uniq).any? ? nil : val.to_s + end + + def self.unmangle__if_type(provider, val) + val.to_s.capitalize + end + + def self.unmangle__method(provider, val) + (['manual', 'static'].include? val.to_s.downcase) ? 'none' : val + end + + def self.unmangle__ipaddr(provider, val) + (val.to_s.downcase == 'dhcp') ? nil : val + end + + def self.unmangle__ethtool(provider, val) + rv = '' + val.each do | section_name, features | + next if L23network.ethtool_name_commands_mapping[section_name].nil? + section_key = L23network.ethtool_name_commands_mapping[section_name]['__section_key_set__'] + next if section_key.nil? + features.each do | feature, value | + next if L23network.ethtool_name_commands_mapping[section_name][feature].nil? + rv << " #{L23network.ethtool_name_commands_mapping[section_name][feature]} #{(value==true ? 'on' : 'off')} " + end + rv = "#{section_key} #{provider.name} #{rv};" + end + return "\"#{rv}\"" + end + +end +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/provider/l23_stored_config_ubuntu.rb b/lib/puppet/provider/l23_stored_config_ubuntu.rb new file mode 100644 index 0000000..ebb6184 --- /dev/null +++ b/lib/puppet/provider/l23_stored_config_ubuntu.rb @@ -0,0 +1,452 @@ +require 'puppetx/l23_utils' +require 'puppetx/l23_ethtool_name_commands_mapping' +require File.join(File.dirname(__FILE__), 'l23_stored_config_base') + +class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_config_base + + # @return [String] The path to network-script directory on redhat systems + def self.script_directory + '/etc/network/interfaces.d' + end + + def self.property_mappings + { + :if_type => 'if_type', # pseudo field, not found in config, but calculated + :if_provider => 'if_provider', # pseudo field, not found in config, but calculated + :method => 'method', + :name => 'iface', + :onboot => 'auto', + :mtu => 'mtu', + :bridge_ports => 'bridge_ports', # ports, members of bridge, fake property + :bridge_stp => 'bridge_stp', + :vlan_dev => 'vlan-raw-device', + :ipaddr => 'address', + # :netmask => 'netmask', + :gateway => 'gateway', + :gateway_metric => 'metric', # todo: rename to 'metric' + # :dhcp_hostname => 'hostname' + :bond_master => 'bond-master', + :bond_slaves => 'bond-slaves', + :bond_mode => 'bond-mode', + :bond_miimon => 'bond-miimon', + :bond_lacp_rate => 'bond-lacp-rate', + :bond_xmit_hash_policy => 'bond-xmit-hash-policy' + } + end + def property_mappings + self.class.property_mappings + end + + # Some resources can be defined as repeatable strings in the config file + # these properties should be fetched by RE-scanning and converted to array + def self.collected_properties + { + :routes => { + # post-up ip route add (default/10.20.30.0/24) via 1.2.3.4 + :detect_re => /(post-)?up\s+ip\s+r([oute]+)?\s+add\s+(default|\d+\.\d+\.\d+\.\d+\/\d+)\s+via\s+(\d+\.\d+\.\d+\.\d+)/, + :detect_shift => 3, + }, + :ethtool => { + # post-up ethtool -K eth2 property [on|off] + :detect_re => /(post-)?up\s+ethtool\s+(-\w+)\s+([\w\-]+)\s+(\w+)\s+(\w+)/, + :detect_shift => 2, + }, + } + end + def collected_properties + self.class.collected_properties + end + + # In the interface config files those fields should be written as boolean + def self.boolean_properties + [ + :hotplug, + :onboot, + :bridge_stp + ] + end + def boolean_properties + self.class.boolean_properties + end + + def self.properties_fake + [ + :onboot, + :name, + :family, + :method, + :if_type, + :if_provider + ] + end + def properties_fake + self.class.properties_fake + end + + # This is a hook method that will be called by PuppetX::Filemapper + # + # @param [String] filename The path of the interfaces file being parsed + # @param [String] contents The contents of the given file + # + # @return [Array>] A single element array containing + # the key/value pairs of properties parsed from the file. + # + # @example + # RedhatProvider.parse_file('/etc/sysconfig/network-scripts/ifcfg-eth0', #) + # # => [ + # # { + # # :name => 'eth0', + # # :ipaddress => '169.254.0.1', + # # :netmask => '255.255.0.0', + # # }, + # # ] + + def self.parse_file(filename, contents) + # WARNING!!! + # this implementation can parce only one interface per file file format + + # Split up the file into lines + lines = contents.split("\n") + # Strip out all comments + lines.map! { |line| line.sub(/#.*$/, '') } + # Remove all blank lines + lines.reject! { |line| line.match(/^\s*$/) } + + # initialize hash as predictible values + hash = {} + hash['auto'] = false + hash['if_provider'] = 'none' + hash['if_type'] = :ethernet + dirty_iface_name = nil + if (m = filename.match(%r/ifcfg-(\S+)$/)) + # save iface name from file name. One will be used if iface name not defined inside config. + dirty_iface_name = m[1].strip + end + + # Convert the data into key/value pairs + pair_regex = %r/^\s*([\w+\-]+)\s+(.*)\s*$/ + lines.each do |line| + if (m = line.match(pair_regex)) + key = m[1].strip + val = m[2].strip + case key + # Ubuntu has non-linear config format. Some options should be calculated evristically + when /(auto|allow-ovs)/ + hash[$1] = true + hash['if_provider'] = $1 # temporary store additional data for self.check_if_provider + if ! hash.has_key?('iface') + # setup iface name if it not given in iface directive + mm = val.split(/\s+/) + hash['iface'] = mm[0] + end + when /iface/ + mm = val.split(/\s+/) + hash['iface'] = mm[0] + hash['method'] = mm[2] + # if hash['iface'] =~ /^br.*/i + # # todo(sv): Make more powerful methodology for recognizind Bridges. + # hash['if_type'] = :bridge + # end + when /bridge-ports/ + hash['if_type'] = :bridge + hash[key] = val + when /bond-(slaves|mode)/ + hash['if_type'] = :bond + hash[key] = val + else + hash[key] = val + end + else + raise Puppet::Error, %{#{filename} is malformed; "#{line}" did not match "#{pair_regex.to_s}"} + end + hash + end + # set mostly low-priority interface name if not given in config file + hash['iface'] ||= dirty_iface_name + + props = self.mangle_properties(hash) + props.merge!({:family => :inet}) + + # collect properties, defined as repeatable strings + collected=[] + lines.each do |line| + rv = [] + collected_properties.each_pair do |r_name, rule| + if rg=line.match(rule[:detect_re]) + props[r_name] ||= [] + props[r_name] << rg[rule[:detect_shift]..-1] + collected << r_name if !collected.include? r_name + next + end + end + end + # mangle collected properties if ones has specific method for it + collected.each do |prop_name| + mangle_method_name="mangle__#{prop_name}" + rv = (self.respond_to?(mangle_method_name) ? self.send(mangle_method_name, props[prop_name]) : props[prop_name]) + props[prop_name] = rv if ! ['', 'absent'].include? rv.to_s.downcase + end + + + # The FileMapper mixin expects an array of providers, so we return the + # single interface wrapped in an array + rv = (self.check_if_provider(props) ? [props] : []) + debug("parse_file('#{filename}'): #{props.inspect}") + rv + end + + def self.check_if_provider(if_data) + raise Puppet::Error, "self.check_if_provider(if_data) Should be implemented in more specific class." + end + + def self.mangle_properties(pairs) + props = {} + + # Unquote all values + pairs.each_pair do |key, val| + next if ! (val.is_a? String or val.is_a? Symbol) + if (munged = val.to_s.gsub(/['"]/, '')) + pairs[key] = munged + end + end + + # For each interface attribute that we recognize it, add the value to the + # hash with our expected label + property_mappings.each_pair do |type_name, in_config_name| + if (val = pairs[in_config_name]) + # We've recognized a value that maps to an actual type property, delete + # it from the pairs and copy it as an actual property + pairs.delete(in_config_name) + mangle_method_name="mangle__#{type_name}" + if self.respond_to?(mangle_method_name) + rv = self.send(mangle_method_name, val) + else + rv = val + end + props[type_name] = rv if ! [nil, :absent].include? rv + end + end + + #!# # For all of the remaining values, blindly toss them into the options hash. + #!# props[:options] = pairs if ! pairs.empty? + + boolean_properties.each do |bool_property| + if props[bool_property] + props[bool_property] = ! (props[bool_property] =~ /^\s*(yes|on)\s*$/i).nil? + else + props[bool_property] = :absent + end + end + + props + end + + def self.mangle__method(val) + val.to_sym + end + + def self.mangle__if_type(val) + val.downcase.to_sym + end + + def self.mangle__gateway_metric(val) + (val.to_i == 0 ? :absent : val.to_i) + end + + def self.mangle__bridge_ports(val) + val.split(/[\s,]+/).sort + end + + def self.mangle__bond_slaves(val) + val.split(/[\s,]+/).sort + end + + def self.mangle__routes(data) + # incoming data is list of 3-element lists: + # [network, gateway, metric] + # metric is optional + rv = {} + data.each do |d| + name = L23network.get_route_resource_name(d[0], d[2]) + rv[name] = { + :destination => d[0], + :gateway => d[1] + } + rv[name][:metric] = d[2] if d[2] + end + return rv + end + + def self.mangle__ethtool(data) + # incoming data is list of 3-element lists: + # [key, interface, abbrv, value] + rv = {} + data.each do |record| + # use .reject bellow for compatibilities with ruby 1.8 + section = L23network.ethtool_name_commands_mapping.reject{|k,v| v['__section_key_set__']!=record[0]} + next if section.empty? + section_name = section.keys[0] + key_fullname = section[section_name].reject{|k, v| v!=record[2]}.to_a + next if key_fullname.empty? + key_fullname = key_fullname[0][0] + next if key_fullname.to_s == '' + rv[section_name] ||= {} + rv[section_name][key_fullname] = (record[3]=='on') + end + return rv + end + + + ### + # Hash to file + + def self.format_file(filename, providers) + if providers.length == 0 + return "" + elsif providers.length > 1 + raise Puppet::DevError, "Unable to support multiple interfaces [#{providers.map(&:name).join(',')}] in a single file #{filename}" + end + + content = [] + provider = providers[0] + + # Add onboot interfaces + if provider.onboot + content << "#{property_mappings[:onboot]} #{provider.name}" + end + + # Add iface header + content << "iface #{provider.name} inet #{provider.method}" + + # Map everything to a flat hash + #props = (provider.options || {}) + props = {} + + property_mappings.keys.select{|v| ! properties_fake.include?(v)}.each do |type_name| + val = provider.send(type_name) + if val and val.to_s != 'absent' + props[type_name] = val + end + end + + debug("format_file('#{filename}')::properties: #{props.inspect}") + pairs = self.unmangle_properties(provider, props) + + pairs.each_pair do |key, val| + content << "#{key} #{val}" if ! val.nil? + end + + #add to content unmangled collected-properties + collected_properties.keys.each do |type_name| + data = provider.send(type_name) + if !(data.nil? or data.empty?) + mangle_method_name="unmangle__#{type_name}" + if self.respond_to?(mangle_method_name) + rv = self.send(mangle_method_name, provider, data) + end + content += rv if ! (rv.nil? or rv.empty?) + end + end + + + debug("format_file('#{filename}')::content: #{content.inspect}") + content << '' + content.join("\n") + end + + + def self.unmangle_properties(provider, props) + pairs = {} + + boolean_properties.each do |bool_property| + if ! props[bool_property].nil? + props[bool_property] = ((props[bool_property].to_s.to_sym == :true) ? 'yes' : 'no') + end + end + + #Unmangling values for ordinary properties. + property_mappings.each_pair do |type_name, in_config_name| + if (val = props[type_name]) + props.delete(type_name) + mangle_method_name="unmangle__#{type_name}" + if self.respond_to?(mangle_method_name) + rv = self.send(mangle_method_name, provider, val) + else + rv = val + end + pairs[in_config_name] = rv if ! [nil, :absent].include? rv + end + end + + pairs + end + + def self.unmangle__ipaddr(provider, val) + (val.to_s.downcase == 'dhcp') ? nil : val + end + + def self.unmangle__if_type(provider, val) + # in Debian family interface config file don't contains declaration of interface type + nil + end + + def self.unmangle__gateway_metric(provider, val) + (val.to_i == 0 ? :absent : val.to_i) + end + + def self.unmangle__bridge_ports(provider, val) + if val.size < 1 or [:absent, :undef].include? Array(val)[0].to_sym + nil + else + val.sort.join(' ') + end + end + + def self.unmangle__bond_master(provider, val) + if [:none, :absent, :undef].include? val.to_sym + nil + else + val + end + end + + def self.unmangle__bond_slaves(provider, val) + if val.size < 1 or [:absent, :undef].include? Array(val)[0].to_sym + nil + else + val.sort.join(' ') + end + end + + def self.unmangle__routes(provider, data) + # should generate set of lines: + # "post-up ip route add % via % | true" + return [] if ['', 'absent'].include? data.to_s + rv = [] + data.each_pair do |name, rou| + rv << "post-up ip route add #{rou[:destination]} via #{rou[:gateway]} | true # #{name}" + end + rv + end + + def self.unmangle__ethtool(provider, data) + # should generate set of lines: + # "post-up ethtool -K %interface_name% property [on|off]" + return [] if ['', 'absent'].include? data.to_s + rv = [] + data.each do |section_name, rules| + next if L23network.ethtool_name_commands_mapping[section_name].nil? + section_key = L23network.ethtool_name_commands_mapping[section_name]['__section_key_set__'] + next if section_key.nil? + rules.each do |k,v| + next if L23network.ethtool_name_commands_mapping[section_name][k].nil? + iface=provider.name + val = (v==true ? 'on' : 'off') + rv << "post-up ethtool #{section_key} #{iface} #{L23network.ethtool_name_commands_mapping[section_name][k]} #{val} | true # #{k}" + end + end + return rv + end + +end +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/provider/l2_base.rb b/lib/puppet/provider/l2_base.rb new file mode 100644 index 0000000..f27e7f7 --- /dev/null +++ b/lib/puppet/provider/l2_base.rb @@ -0,0 +1,628 @@ +require 'puppetx/l23_utils' +require 'puppetx/l23_ethtool_name_commands_mapping' +require 'yaml' + +class Puppet::Provider::L2_base < Puppet::Provider + + def self.ovs_vsctl(*cmd) + actual_cmd = ['ovs-vsctl'] + Array(*cmd) + begin + ff = IO.popen(actual_cmd.join(' ')) + rv = ff.readlines().map{|l| l.chomp()} + rescue + rv = nil + end + return rv + end + + def self.prefetch(resources) + interfaces = instances + resources.keys.each do |name| + if provider = interfaces.find{ |ii| ii.name == name } + resources[name].provider = provider + end + end + end + + # --------------------------------------------------------------------------- + + def self.iface_exist?(iface) + File.exist? "/sys/class/net/#{iface}" + end + + def self.get_lnx_vlan_interfaces + # returns hash, that contains ports (interfaces) configuration. + # i.e { + # eth0.101 => { :vlan_dev => 'eth0', :vlan_id => 101, vlan_mode => 'eth' }, + # vlan102 => { :vlan_dev => 'eth0', :vlan_id => 102, vlan_mode => 'vlan' }, + # } + # + vlan_ifaces = {} + rc_c = /([\w+\.\-]+)\s*\|\s*(\d+)\s*\|\s*([\w+\-]+)/ + File.open("/proc/net/vlan/config", "r").each do |line| + if (rv=line.match(rc_c)) + vlan_ifaces[rv[1]] = { + :vlan_dev => rv[3], + :vlan_id => rv[2], + :vlan_mode => (rv[1].match('\.').nil? ? 'vlan' : 'eth' ) + } + end + end + return vlan_ifaces + end + + def self.get_lnx_ports + # returns hash, that contains ports (interfaces) configuration. + # i.e { + # eth0 => { :mtu => 1500, :if_type => :ethernet, port_type => lnx:eth:unremovable }, + # } + # + # 'unremovable' flag for port_type means, that this port is a more complicated thing, + # than just a port and can't be removed just as port. For example you can't remove bond + # as port. You should remove it as bond. + # + port = {} + # + # parse 802.1q vlan interfaces from /proc + vlan_ifaces = self.get_lnx_vlan_interfaces() + # Fetch information about interfaces, visible in network namespace from /sys/class/net + interfaces = Dir['/sys/class/net/*'].select{ |f| File.symlink? f} + interfaces.each do |if_dir| + if_name = if_dir.split('/')[-1] + port[if_name] = { + :name => if_name, + :port_type => [], + :onboot => self.get_iface_state(if_name), + :ethtool => nil, + :mtu => File.open("#{if_dir}/mtu").read.chomp.to_i, + :provider => (if_name == 'ovs-system') ? 'ovs' : 'lnx' , + } + # determine port_type for this iface + if File.directory? "#{if_dir}/bonding" + # This interface is a baster of bond, get bonding properties + port[if_name][:slaves] = File.open("#{if_dir}/bonding/slaves").read.chomp.strip.split(/\s+/).sort + port[if_name][:port_type] << 'bond' << 'unremovable' + elsif File.directory? "#{if_dir}/bridge" and File.directory? "#{if_dir}/brif" + # this interface is a bridge, get bridge properties + port[if_name][:slaves] = Dir["#{if_dir}/brif/*"].map{|f| f.split('/')[-1]}.sort + port[if_name][:port_type] << 'bridge' << 'unremovable' + else + #pass + end + # Check, whether this interface is a slave of anything + if File.symlink?("#{if_dir}/master") + port[if_name][:has_master] = File.readlink("#{if_dir}/master").split('/')[-1] + end + # Check, whether this interface is a subinterface + if vlan_ifaces.has_key? if_name + # this interface is a 802.1q subinterface + port[if_name].merge! vlan_ifaces[if_name] + port[if_name][:port_type] << 'vlan' + end + end + # Check, whether port is a slave of anything another + port.keys.each do |p_name| + if port[p_name].has_key? :has_master + master = port[p_name][:has_master] + #debug("m='#{master}', name='#{p_name}', props=#{port[p_name]}") + master_flags = port[master][:port_type] + if master_flags.include? 'bond' + # this port is a bond_member + port[p_name][:bond_master] = master + port[p_name][:port_type] << 'bond-slave' + elsif master_flags.include? 'bridge' + # this port is a member of bridge + port[p_name][:bridge] = master + port[p_name][:port_type] << 'bridge-slave' + elsif master == 'ovs-system' + port[p_name][:port_type] << 'ovs-affected' + else + #pass + end + port[p_name].delete(:has_master) + end + end + return port + end + + # --------------------------------------------------------------------------- + def self.ovs_parse_opthash(hh) + #if !(hh=~/^['"]/ and hh=~/['"]$/) + rv = {} + if hh =~ /^\{(.*)\}$/ + $1.split(/\s*\,\s*/).each do |pair| + k,v = pair.split('=') + #debug("===#{k}===#{v}===") + rv[k.tr("'\"",'').to_sym] = v.nil? ? nil : v.tr("'\"",'') + end + end + return rv + end + + def self.get_ovs_bridges + # return OVS interfaces hash if it possible + + vsctl_list_bridges = ovs_vsctl(['list', 'Bridge']) + if vsctl_list_bridges.nil? + debug("Can't find OVS ports, because error while 'ovs-vsctl list Bridge' execution") + return {} + end + vsctl_list_bridges << :EOF # last section of output should be processsed anyway. + # + buff = {} + rv = {} + # parse ovs-vsctl output and find OVS and OVS-affected interfaces + vsctl_list_bridges.each do |line| + if line =~ /(\w+)\s*\:\s*(.*)\s*$/ + key = $1.tr("'\"",'') + val = $2.tr("'\"",'') + buff[key] = (val == '[]' ? '' : val) + elsif line =~ /^\s*$/ or line == :EOF + stp_enable = buff['stp_enable'] || '' + rv[buff['name']] = { + :stp => stp_enable.downcase == 'true', + :vendor_specific => { + :external_ids => ovs_parse_opthash(buff['external_ids']), + :other_config => ovs_parse_opthash(buff['other_config']), + :status => ovs_parse_opthash(buff['status']), + } + } + debug("Found OVS br: '#{buff['name']}' with properties: #{rv[buff['name']]}") + buff = {} + else + debug("Output of 'ovs-vsctl list Bridge' contain misformated line: '#{line}'") + end + end + return rv + end + + def self.get_ovs_ports + # return OVS interfaces hash if it possible + vsctl_list_ports = ovs_vsctl(['list', 'Port']) + if vsctl_list_ports.nil? + debug("Can't find OVS ports, because error while 'ovs-vsctl list Port' execution") + return {} + end + vsctl_list_ports << :EOF # last section of output should be processsed anyway. + # + buff = {} + rv = {} + # parse ovs-vsctl output and find OVS and OVS-affected interfaces + vsctl_list_ports.each do |line| + if line =~ /(\w+)\s*\:\s*(.*)\s*$/ + key = $1.tr("'\"",'') + val = $2.tr("'\"",'') + buff[key] = val == '[]' ? '' : val + elsif line =~ /^\s*$/ or line == :EOF + rv[buff['name']] = { + :vendor_specific => { + :other_config => ovs_parse_opthash(buff['other_config']), + :status => ovs_parse_opthash(buff['status']), + } + } + rv[buff['name']][:vlan_id] = buff['tag'] if ! (buff['tag'].nil? or buff['tag'].empty?) + rv[buff['name']][:trunks] = buff['trunks'].tr("[]",'').split(/[\,\s]+/) if ! (buff['trunks'].nil? or buff['trunks'].empty?) + debug("Found OVS port '#{buff['name']}' with properties: #{rv[buff['name']]}") + buff = {} + else + debug("Output of 'ovs-vsctl list Port' contain misformated line: '#{line}'") + end + end + return rv + end + + def self.get_ovs_interfaces + # return OVS interfaces hash if it possible + vsctl_list_interfaces = ovs_vsctl(['list', 'Interface']) + if vsctl_list_interfaces.nil? + debug("Can't find OVS interfaces, because error while 'ovs-vsctl list Interface' execution") + return {} + end + vsctl_list_interfaces << :EOF # last section of output should be processsed anyway. + # + buff = {} + rv = {} + # parse ovs-vsctl output and find OVS and OVS-affected interfaces + vsctl_list_interfaces.each do |line| + if line =~ /(\w+)\s*\:\s*(.*)\s*$/ + key = $1.tr("'\"",'') + val = $2.tr("'\"",'') + buff[key] = val == '[]' ? '' : val + elsif line =~ /^\s*$/ or line == :EOF + rv[buff['name']] = { + :mtu => buff['mtu'], + :port_type => (buff['type'].nil? or buff['type'].empty?) ? [] : [buff['type']], + :vendor_specific => { + :status => ovs_parse_opthash(buff['status']), + } + } + driver = rv[buff['name']][:vendor_specific][:status][:driver_name] + if driver.nil? or driver.empty? or driver == 'openvswitch' + rv[buff['name']][:provider] = 'ovs' + else + rv[buff['name']][:provider] = nil + end + debug("Found OVS interface '#{buff['name']}' with properties: #{rv[buff['name']]}") + buff = {} + else + debug("Output of 'ovs-vsctl list Interface' contain misformated line: '#{line}'") + end + end + return rv + end + + def self.ovs_vsctl_show + content = ovs_vsctl('show') + if content.nil? + debug("Can't get OVS configuration, because error while 'ovs-vsctl show' execution") + return {} + end + bridges = get_ovs_bridges() + ports = get_ovs_ports() + interfaces = get_ovs_interfaces() + ovs_config = { + :port => {}, + :interface => {}, + #:bond => {}, # bond in ovs is a internal only port !!! + :bridge => {}, + :jack => {} # jack of ovs patchcord (patchcord is a pair of ports with type 'patch') + } + _br = nil + _po = nil + _if = nil + #_ift = nil + content.each do |line| + line.rstrip! + case line + when /^\s+Bridge\s+"?([\w\-\.]+)\"?$/ + _br = $1 + _po = nil + _if = nil + ovs_config[:bridge][_br] = { + :port_type => ['bridge'], + :br_type => 'ovs', + :provider => 'ovs' + } + if bridges.has_key? _br + ovs_config[:bridge][_br].merge! bridges[_br] + end + when /^\s+Port\s+"?([\w\-\.]+)\"?$/ + next if _br.nil? + _po = $1 + _if = nil + ovs_config[:port][_po] = { + :bridge => _br, + :port_type => [], + #:provider => 'ovs' + } + if ports.has_key? _po + ovs_config[:port][_po].merge! ports[_po] + end + if _po == _br + ovs_config[:port][_po][:port_type] << 'bridge' + end + when /^\s+Interface\s+"?([\w\-\.]+)\"?$/ + _if = $1 + ovs_config[:interface][_if] = { + :port => _po, + } + if interfaces.has_key? _if + ovs_config[:interface][_if].merge! interfaces[_if] + end + #todo(sv): Check interface driver from Interfaces table + ovs_config[:port][_po][:provider] = ovs_config[:interface][_if][:provider] + when /^\s+type:\s+"?([\w\-\.]+)\"?$/ + ovs_config[:interface][_if].merge!({ + :type => $1 + }) + when /^\s+options:\s+\{(.+)\}\s*$/ + opts = $1.split(/[\s\,]+/).map{|o| o.split('=')}.reduce({}){|h,p| h.merge(p[0] => p[1].tr('"',''))} + ovs_config[:interface][_if].merge!({ + :options => opts + }) + else + #debug("Misformated line for br='#{_br}', po='#{_po}', if='#{_if}' => '#{line}'") + end + end + ovs_config[:port].keys.each do |p_name| + # didn't use .select{...} here for backward compatibility with ruby 1.8 + ifaces = ovs_config[:interface].reject{|k,v| v[:port]!=p_name} + iface = ifaces[ifaces.keys[0]] + if ifaces.size > 1 + # Bond found + #ovs_config[:bond][p_name] = ovs_config[:port][p_name] + #ovs_config[:port].delete(p_name) + ovs_config[:port][p_name][:port_type] << 'bond' + ovs_config[:port][p_name][:provider] = 'ovs' + elsif iface[:type] == 'patch' + ovs_config[:port][p_name][:port_type] << 'jack' + elsif iface[:type] == 'internal' + ovs_config[:port][p_name][:port_type] << 'internal' + else + # ordinary interface found + # pass + end + # get mtu value (from one of interfaces if bond) and up it to port layer + k = ifaces.keys + if k.size > 0 + ovs_config[:port][p_name][:mtu] = ifaces[k[0]][:mtu] + end + # fix port-type=vlan for tagged ports + if !ovs_config[:port][p_name][:vlan_id].nil? + ovs_config[:port][p_name][:port_type] << 'vlan' + end + end + debug("VSCTL-SHOW: #{ovs_config.to_yaml.gsub('!ruby/sym ',':')}") + return ovs_config + end + # --------------------------------------------------------------------------- + + def self.get_bridge_list + # search all (LXN and OVS) bridges on the host, and return hash with mapping + # bridge_name => { bridge options } + # + bridges = {} + # obtain OVS bridges list + re_c = /^\s*([\w\-]+)/ + listbr = ovs_vsctl('list-br') + if listbr.nil? + debug("No OVS bridges found, because error while 'ovs-vsctl list-br' execution") + else + listbr.select{|l| l.match(re_c)}.collect{|a| $1 if a.match(re_c)}.each do |br_name| + br_name.strip! + bridges[br_name] = { + :br_type => :ovs + } + end + end + # obtain LNX bridges list + re_c = /([\w\-]+)\s+\d+/ + begin + # todo(sv): using port_list instead fork subprocess + brctl('show').split(/\n+/).select{|l| l.match(re_c)}.collect{|a| $1 if a.match(re_c)}.each do |br_name| + br_name.strip! + bridges[br_name] = { + :stp => (File.open("/sys/class/net/#{br_name}/bridge/stp_state").read.strip.to_i == 1), + :external_ids => :absent, + :vendor_specific => {}, + :br_type => :lnx + } + end + rescue + debug("No LNX bridges found, because error while 'brctl show' execution") + end + return bridges + end + + def self.get_ovs_port_bridges_pairs + # returns hash, which map ports to it's bridge. + # i.e { + # qg37f65 => { :bridge => 'br-ex', :br_type => :ovs }, + # } + # + port_mappings = {} + ovs_bridges = ovs_vsctl('list-br') + if ovs_bridges.nil? + debug("No OVS bridges found, because error while 'ovs-vsctl list-br' execution") + return {} + end + ovs_bridges.select{|l| l.match(/^\s*[\w\-]+/)}.each do |br_name| + br_name.strip! + ovs_portlist = ovs_vsctl(['list-ports', br_name]).select{|l| l.match(/^\s*[\w\-]+\s*/)} + #todo: handle error + ovs_portlist.each do |port_name| + port_name.strip! + port_mappings[port_name] = { + :bridge => br_name, + :br_type => :ovs + } + end + # bridge also a port, but it don't show itself by list-ports, adding it manually + port_mappings[br_name] = { + :bridge => br_name, + :br_type => :ovs + } + end + return port_mappings + end + + def self.get_lnx_port_bridges_pairs + # returns hash, which map ports to it's bridge. + # i.e { + # 'eth0' => { :bridge => 'br0', :br_type => :lnx }, + # } + # This function returns all visible in default namespace ports + # (lnx and ovs (with type internal)) included to the lnx bridge + # + begin + brctl_show = brctl('show').split(/\n+/).select{|l| l.match(/^[\w\-]+\s+\d+/) or l.match(/^\s+[\w\.\-]+/)} + rescue + debug("No LNX bridges found, because error while 'brctl show' execution") + return {} + end + port_mappings = {} + br_name = nil + brctl_show.each do |line| + line.rstrip! + case line + when /^([\w\-]+)\s+[\d\.abcdef]+\s+(yes|no)\s+([\w\-\.]+$)/i + br_name = $1 + port_name = $3 + when /^\s+([\w\.\-]+)$/ + #br_name using from previous turn + port_name = $1 + else + next + end + if br_name + port_mappings[port_name] = { + :bridge => br_name, + :br_type => :lnx + } + end + end + debug("LNX ports to bridges mapping: #{port_mappings.to_yaml.gsub('!ruby/sym ',':')}") + return port_mappings + end + + def self.get_port_bridges_pairs + # returns hash, which map ports to it's bridge. + # i.e { + # eth0 => { :bridge => 'br0', :br_type => :lnx }, + # qg37f65 => { :bridge => 'br-ex', :br_type => :ovs }, + # } + # This function returns all visible in default namespace ports + # (lnx and ovs (with type internal)) included to the lnx bridge + # + # If port included to both bridges (ovs and lnx at one time), + # i.e. using as patchcord between bridges -- this port will be + # assigned to lnx-type bridge + # + port_bridges_hash = self.get_ovs_port_bridges_pairs() # LNX bridges should overwrite OVS + port_bridges_hash.merge! self.get_lnx_port_bridges_pairs() # because by design! + end + + def self.get_bridges_order_for_patch(bridges) + # if given two OVS bridges -- we should sort it by name + # if given OVS and LNX bridges -- OVS should be first. + br_type = [] + [0,1].each do |i| + br_type << (File.directory?("/sys/class/net/#{bridges[i]}/bridge") ? 'lnx' : 'ovs' ) + end + if br_type[0] == br_type[1] + rv = bridges.sort() + elsif br_type[0] == 'ovs' + rv = [bridges[0],bridges[1]] + else + rv = [bridges[1],bridges[0]] + end + return rv + end + + # --------------------------------------------------------------------------- + + def self.get_lnx_bonds + # search all LXN bonds on the host, and return hash with + # bond_name => { bond options } + # + bond = {} + bondlist = File.open("/sys/class/net/bonding_masters").read.chomp.split(/\s+/).sort + bondlist.each do |bond_name| + mode = File.open("/sys/class/net/#{bond_name}/bonding/mode").read.split(/\s+/)[0] + bond[bond_name] = { + :mtu => File.open("/sys/class/net/#{bond_name}/mtu").read.chomp.to_i, + :slaves => File.open("/sys/class/net/#{bond_name}/bonding/slaves").read.chomp.split(/\s+/).sort, + :bond_properties => { + :mode => mode, + :miimon => File.open("/sys/class/net/#{bond_name}/bonding/miimon").read.chomp, + } + } + if ['802.3ad', 'balance-xor', 'balance-tlb', 'balance-alb'].include? mode + xmit_hash_policy = File.open("/sys/class/net/#{bond_name}/bonding/xmit_hash_policy").read.split(/\s+/)[0] + bond[bond_name][:bond_properties][:xmit_hash_policy] = xmit_hash_policy + end + if mode=='802.3ad' + lacp_rate = File.open("/sys/class/net/#{bond_name}/bonding/lacp_rate").read.split(/\s+/)[0] + bond[bond_name][:bond_properties][:lacp_rate] = lacp_rate + end + bond[bond_name][:onboot] = !self.get_iface_state(bond_name).nil? + end + return bond + end + + def self.lnx_bond_allowed_properties + { + :active_slave => {}, + :ad_select => {}, + :all_slaves_active => {}, + :arp_interval => {}, + :arp_ip_target => {}, + :arp_validate => {}, + :arp_all_targets => {}, + :downdelay => {}, + :updelay => {}, + :fail_over_mac => {}, + :lacp_rate => {:need_reassemble => true}, + :miimon => {}, + :min_links => {}, + :mode => {:need_reassemble => true}, + :num_grat_arp => {}, + :num_unsol_na => {}, + :packets_per_slave => {}, + :primary => {}, + :primary_reselect => {}, + :tlb_dynamic_lb => {}, + :use_carrier => {}, + :xmit_hash_policy => {}, + :resend_igmp => {}, + :lp_interval => {} + } + end + def self.lnx_bond_allowed_properties_list + self.lnx_bond_allowed_properties.keys.sort + end + + def self.ovs_bond_allowed_properties + { + :downdelay => {:property => 'bond_downdelay'}, + :updelay => {:property => 'bond_updelay'}, + :use_carrier => {:property => 'other_config:bond-detect-mode', + :override_integer => ['miimon', 'carrier'] }, + :mode => {:property => 'bond_mode', + :allow => ['balance-slb', 'active-backup', 'balance-tcp', 'stable'] }, + :lacp => {:property => 'lacp', + :allow => ['off', 'active', 'passive'] }, + :lacp_rate => {:property => 'other_config:lacp_time'}, + :miimon => {:property => 'other_config:bond-miimon-interval'}, + :slb_rebalance_interval => {:property => 'other_config:bond-rebalance-interval'}, + } + end + def self.ovs_bond_allowed_properties_list + self.ovs_bond_allowed_properties.keys.sort + end + + + def self.get_iface_state(iface) + # returns: + # true -- interface in UP state + # false -- interface in UP state, but no-carrier + # nil -- interface in DOWN state + begin + 1 == File.open("/sys/class/net/#{iface}/carrier").read.chomp.to_i + rescue + # if interface is down, this file can't be read + nil + end + end + + # --------------------------------------------------------------------------- + + def self.get_ethtool_name_commands_mapping + L23network.ethtool_name_commands_mapping + end + + def self.get_iface_ethtool_hash(if_name, empty_return = {}) + tmp = {} + #todo(sv): wrap to begin--resque + ethtool_k = ethtool_cmd('-k', if_name) + ethtool_k.split(/\n+/).select{|l| !l.match(/(^\s+|\[fixed\]|^Features)/)}.map{|x| x.split(/[\s\:]+/)}.each do |p| + tmp[p[0]] = (p[1] == 'on') + end + return { + 'offload' => tmp || empty_return + } + end + + # --------------------------------------------------------------------------- + + def self.set_mtu(iface, mtu=1500) + if File.symlink?("/sys/class/net/#{iface}") + debug("Set MTU to '#{mtu}' for interface '#{iface}'") + File.open("/sys/class/net/#{iface}/mtu", "a") { |f| f.write(mtu) } + end + end + +end + + +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l2_bond/lnx.rb b/lib/puppet/provider/l2_bond/lnx.rb new file mode 100644 index 0000000..4ce9ead --- /dev/null +++ b/lib/puppet/provider/l2_bond/lnx.rb @@ -0,0 +1,201 @@ +# Native linux bonding implementation +# INspired by: https://www.kernel.org/doc/Documentation/networking/bonding.txt +# + +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/lnx_base') + +Puppet::Type.type(:l2_bond).provide(:lnx, :parent => Puppet::Provider::Lnx_base) do + defaultfor :osfamily => :linux + commands :iproute => 'ip', + :ethtool_cmd => 'ethtool', + :brctl => 'brctl' + + + def self.prefetch(resources) + interfaces = instances + resources.keys.each do |name| + if provider = interfaces.find{ |ii| ii.name == name } + resources[name].provider = provider + end + end + end + + def self.instances + bonds ||= self.get_lnx_bonds() + debug("bonds found: #{bonds.keys}") + rv = [] + bonds.each_pair do |bond_name, bond_props| + props = { + :ensure => :present, + :name => bond_name, + :vendor_specific => {} + } + props.merge! bond_props + # # get bridge if port included to it + # if ! port_bridges_hash[if_name].nil? + # props[:bridge] = port_bridges_hash[if_name][:bridge] + # end + # # calculate port_type field + # if !bridges[if_name].nil? + # case bridges[if_name][:br_type] + # when :ovs + # props[:port_type] = 'ovs:br:unremovable' + # when :lnx + # props[:port_type] = 'lnx:br:unremovable' + # else + # # pass + # end + # end + debug("PREFETCHED properties for '#{bond_name}': #{props}") + rv << new(props) + end + rv + end + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + open('/sys/class/net/bonding_masters', 'a') do |f| + f << "+#{@resource[:name]}" + end + end + + def destroy + debug("DESTROY resource: #{@resource}") + open('/sys/class/net/bonding_masters', 'a') do |f| + f << "-#{@resource[:name]}" + end + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + # + # FLUSH changed properties + if @property_flush.has_key? :slaves + runtime_slave_ports = File.open("/sys/class/net/#{@resource[:bond]}/bonding/slaves", "r").read.split(/\s+/) + if @property_flush[:slaves].nil? or @property_flush[:slaves] == :absent + debug("Remove all slave ports from bond '#{@resource[:bond]}'") + rm_slave_list = runtime_slave_ports + else + rm_slave_list = runtime_slave_ports - @property_flush[:slaves] + debug("Remove '#{rm_slave_list.join(',')}' ports from bond '#{@resource[:bond]}'") + rm_slave_list.each do |slave| + iproute('link', 'set', 'dev', slave, 'down') # need by kernel requirements by design. undocumented :( + File.open("/sys/class/net/#{@resource[:bond]}/bonding/slaves", "a") {|f| f << "-#{slave}"} + end + # add interfaces to bond + (@property_flush[:slaves] - runtime_slave_ports).each do |slave| + iproute('link', 'set', 'dev', slave, 'down') # need by kernel requirements by design. undocumented :( + debug("Add interface '#{slave}' to bond '#{@resource[:bond]}'") + File.open("/sys/class/net/#{@resource[:bond]}/bonding/slaves", "a") {|f| f << "+#{slave}"} + end + end + end + if @property_flush.has_key? :bond_properties + # change bond_properties + need_reassemble = [:mode, :lacp_rate] + #todo(sv): inplement re-assembling only if it need + #todo(sv): re-set only delta between reality and requested + runtime_bond_state = !self.class.get_iface_state(@resource[:bond]).nil? + runtime_slave_ports = File.open("/sys/class/net/#{@resource[:bond]}/bonding/slaves", "r").read.split(/\s+/) + runtime_slave_ports.each do |eth| + # for most bond options we should disassemble bond before re-configuration. In the kernel module documentation + # says, that bond interface should be downed, but it's not enouth. + File.open("/sys/class/net/#{@resource[:bond]}/bonding/slaves", "a") {|f| f << "-#{eth}"} + end + iproute('link', 'set', 'dev', @resource[:bond], 'down') + @property_flush[:bond_properties].each_pair do |prop, val| + if self.class.lnx_bond_allowed_properties_list.include? prop.to_sym + act_val = val.to_s + else + debug("Unsupported property '#{prop}' for bond '#{@resource[:bond]}'") + act_val = nil + end + if act_val + debug("Set property '#{prop}' to '#{act_val}' for bond '#{@resource[:bond]}'") + File.open("/sys/class/net/#{@resource[:bond]}/bonding/#{prop}", 'a') {|f| f << "#{act_val.to_s}"} + end + end + # re-assemble bond after configuration + iproute('link', 'set', 'dev', @resource[:bond], 'up') if runtime_bond_state + runtime_slave_ports.each do |eth| + File.open("/sys/class/net/#{@resource[:bond]}/bonding/slaves", "a") {|f| f << "+#{eth}"} + end + end + if @property_flush.has_key? :bridge + # get actual bridge-list. We should do it here, + # because bridge may be not existing at prefetch stage. + @bridges ||= self.class.get_bridge_list() + debug("Actual-bridge-list: #{@bridges}") + port_bridges_hash = self.class.get_port_bridges_pairs() + debug("Actual-port-bridge-mapping: '#{port_bridges_hash}'") # it should removed from LNX + # + # remove interface from old bridge + runtime_bond_state = !self.class.get_iface_state(@resource[:bond]).nil? + iproute('--force', 'link', 'set', 'dev', @resource[:bond], 'down') + if ! port_bridges_hash[@resource[:bond]].nil? + br_name = port_bridges_hash[@resource[:bond]][:bridge] + if br_name != @resource[:bond] + # do not remove bridge-based interface from his bridge + case port_bridges_hash[@resource[:bond]][:br_type] + when :ovs + ovs_vsctl(['del-port', br_name, @resource[:bond]]) + # todo catch exception + when :lnx + brctl('delif', br_name, @resource[:bond]) + # todo catch exception + else + #pass + end + end + end + # add port to the new bridge + if !@property_flush[:bridge].nil? and @property_flush[:bridge].to_sym != :absent + case @bridges[@property_flush[:bridge]][:br_type] + when :ovs + ovs_vsctl(['add-port', @property_flush[:bridge], @resource[:bond]]) + when :lnx + brctl('addif', @property_flush[:bridge], @resource[:bond]) + else + #pass + end + end + iproute('link', 'set', 'dev', @resource[:bond], 'up') if runtime_bond_state + debug("Change bridge") + end + if @property_flush[:onboot] + iproute('link', 'set', 'dev', @resource[:bond], 'up') + end + if !['', 'absent'].include? @property_flush[:mtu].to_s + self.class.set_mtu(@resource[:bond], @property_flush[:mtu]) + end + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + def slaves + @property_hash[:slaves] || :absent + end + def slaves=(val) + @property_flush[:slaves] = val + end + + def bond_properties + @property_hash[:bond_properties] || :absent + end + def bond_properties=(val) + @property_flush[:bond_properties] = val + end + + def interface_properties + @property_hash[:interface_properties] || :absent + end + def interface_properties=(val) + @property_flush[:interface_properties] = val + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l2_bond/ovs.rb b/lib/puppet/provider/l2_bond/ovs.rb new file mode 100644 index 0000000..a045e7b --- /dev/null +++ b/lib/puppet/provider/l2_bond/ovs.rb @@ -0,0 +1,112 @@ +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/ovs_base') + +Puppet::Type.type(:l2_bond).provide(:ovs, :parent => Puppet::Provider::Ovs_base) do + commands :vsctl => 'ovs-vsctl', + :ethtool_cmd => 'ethtool', + :iproute => 'ip' + + + # def self.add_unremovable_flag(port_props) + # # calculate 'unremovable' flag. Should be re-defined in chield providers + # if port_props[:port_type].include? 'bridge' or port_props[:port_type].include? 'bond' + # port_props[:port_type] << 'unremovable' + # end + # end + + def self.get_instances(big_hash) + # didn't use .select{...} here for backward compatibility with ruby 1.8 + big_hash[:port].reject{|k,v| !v[:port_type].include?('bond')} + end + + #----------------------------------------------------------------- + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + # + cmd = ["add-bond", @resource[:bridge], @resource[:bond], @resource[:interface]] + begin + vsctl(cmd) + rescue Puppet::ExecutionFailure => error + raise Puppet::ExecutionFailure, "Can't add bond '#{@resource[:bond]}'\n#{error}" + end + # # set interface properties + # if @resource[:interface_properties] + # for option in @resource[:interface_properties] + # begin + # vsctl('--', "set", "Interface", @resource[:interface], option.to_s) + # rescue Puppet::ExecutionFailure => error + # raise Puppet::ExecutionFailure, "Interface '#{@resource[:interface]}' can't set option '#{option}':\n#{error}" + # end + # end + # end + end + + def destroy + vsctl("del-port", @resource[:bridge], @resource[:interface]) + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + if @property_flush.has_key? :slaves + warn("Do nothing, OVS don't allow change bond slaves for existing bond ('#{@resource[:bond]}').") + # But we can implement this undocumented hack later + # ovs-vsctl add-port br3 ee2 + # ovs-vsctl list interface ee2 # get uuid for port + # ovs-vsctl -- set port bond3 'interfaces=[0e6a0107-d0c7-49a6-93c7-41fe23e61c2c, 2c21e847-05ea-4b11-bde2-bb19e2d0ca56]' + # ovs-vsctl show + # ovs-vsctl del-port ee2 # ignore error + # ovs-vsctl show + end + if @property_flush.has_key? :bond_properties + # change bond_properties + allowed_properties = self.class.ovs_bond_allowed_properties() + @property_flush[:bond_properties].each_pair do |prop, val| + if self.class.ovs_bond_allowed_properties_list.include? prop.to_sym + act_val = val.to_s + else + warn("Unsupported property '#{prop}' for bond '#{@resource[:bond]}'") + act_val = nil + end + if act_val + debug("Set property '#{prop}' to '#{act_val}' for bond '#{@resource[:bond]}'") + if allowed_properties[prop.to_sym][:property] + # just setup property in OVSDB + if allowed_properties[prop.to_sym][:allow] and ! allowed_properties[prop.to_sym][:allow].include? val + warn("Unsupported value '#{val}' for property '#{prop}' for bond '#{@resource[:bond]}'.\nAllowed modes: #{allowed_properties[prop.to_sym][:allow]}") + val = nil + end + if allowed_properties[prop.to_sym][:override_integer] + # override property if it should be given as string for ovs and as integer for native linux + val = allowed_properties[prop.to_sym][:override_integer][val.to_i] || allowed_properties[prop.to_sym][:override_integer][0] + end + vsctl('--', "set", "Port", @resource[:bond], "#{allowed_properties[prop.to_sym][:property]}=#{val}") if ! val.nil? + end + end + end + end + # + if @property_flush.has_key? :mtu + debug("Do nothing, because for OVS bonds MTU can be changed only for slave interfaces.") + end + end + end + + #----------------------------------------------------------------- + def bond_properties + @property_hash[:bond_properties] || :absent + end + def bond_properties=(val) + @property_flush[:bond_properties] = val + end + + def interface_properties + @property_hash[:interface_properties] || :absent + end + def interface_properties=(val) + @property_flush[:interface_properties] = val + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l2_bridge/lnx.rb b/lib/puppet/provider/l2_bridge/lnx.rb new file mode 100644 index 0000000..0404686 --- /dev/null +++ b/lib/puppet/provider/l2_bridge/lnx.rb @@ -0,0 +1,96 @@ +# Native linux bridging implementation +# Inspired by: +# * https://www.kernel.org/doc/Documentation/networking/bridge.txt +# * http://www.linuxfoundation.org/collaborate/workgroups/networking/bridge +# + +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/lnx_base') + +Puppet::Type.type(:l2_bridge).provide(:lnx, :parent => Puppet::Provider::Lnx_base) do + defaultfor :osfamily => :linux + commands :brctl => 'brctl', + :ethtool_cmd => 'ethtool', + :iproute => 'ip' + + def self.instances + rv = [] + get_bridge_list().each_pair do |bridge, props| + debug("prefetching '#{bridge}'") + br_props = { + :ensure => :present, + :name => bridge, + } + br_props.merge! props + if props[:br_type] == :lnx + #br_props[:provider] = 'lnx' + #props[:port_type] = props[:port_type].insert(0, 'ovs').join(':') + rv << new(br_props) + debug("PREFETCH properties for '#{bridge}': #{br_props}") + else + debug("SKIP properties for '#{bridge}': #{br_props}") + end + end + rv + end + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + begin + brctl('addbr', @resource[:bridge]) + rescue + # Some time interface may be created by OS init scripts. It's a normal for Ubuntu. + raise if ! self.class.iface_exist? @resource[:bridge] + notice("'#{@resource[:bridge]}' already created by ghost event.") + end + iproute('link', 'set', 'up', 'dev', @resource[:bridge]) + end + + def destroy + iproute('link', 'set', 'down', 'dev', @resource[:bridge]) + brctl('delbr', @resource[:bridge]) + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + # + # FLUSH changed properties + if @property_flush.has_key? :stp + effective_stp = (@property_flush[:stp].to_s == 'true' ? 1 : 0) + File.open("/sys/class/net/#{@resource[:bridge]}/bridge/stp_state", "a") {|f| f << effective_stp} + end + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + def br_type + @property_hash[:br_type] || :absent + end + def br_type=(val) + @property_flush[:br_type] = val + end + + # external IDs not supported + def external_ids + :absent + end + def external_ids=(value) + {} + end + + def stp + # puppet has internal transformation, and we shouldn't use boolean values. Use symbols -- it works stable!!! + @property_hash[:stp].to_s.to_sym + end + def stp=(val) + @property_flush[:stp] = (val.to_s.downcase.to_sym==:true) + end + + #----------------------------------------------------------------- + + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l2_bridge/ovs.rb b/lib/puppet/provider/l2_bridge/ovs.rb new file mode 100644 index 0000000..2865cc6 --- /dev/null +++ b/lib/puppet/provider/l2_bridge/ovs.rb @@ -0,0 +1,103 @@ +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/ovs_base') + +Puppet::Type.type(:l2_bridge).provide(:ovs, :parent => Puppet::Provider::Ovs_base) do + commands :vsctl => 'ovs-vsctl', + :ethtool_cmd => 'ethtool', + :brctl => 'brctl', + :iproute => 'ip' + + def self.skip_port_for?(port_props) + port_props[:br_type] != 'ovs' + end + + def self.get_instances(big_hash) + big_hash[:bridge] + end + + # def self.instances + # rv = super + # #debug("#{rv.inspect}") + # end + + #----------------------------------------------------------------- + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + # + vsctl('add-br', @resource[:bridge]) + iproute('link', 'set', 'up', 'dev', @resource[:bridge]) + notice("bridge '#{@resource[:bridge]}' created.") + end + + def destroy + iproute('link', 'set', 'down', 'dev', @resource[:bridge]) + vsctl("del-br", @resource[:bridge]) + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + # + # FLUSH changed properties + if @property_flush.has_key? :stp + vsctl('set', 'Bridge', @resource[:bridge], "stp_enable=#{@property_flush[:stp]}") + end + if @property_flush.has_key? :external_ids + old_ids = (@old_property_hash[:external_ids] || {}) + new_ids = @property_flush[:external_ids] + #todo(sv): calculate deltas and remove unnided. + new_ids.each_pair do |k,v| + if ! old_ids.has_key?(k) + vsctl("br-set-external-id", @resource[:bridge], k, v) + end + end + end + # + @property_hash = resource.to_hash + end + end + + + #----------------------------------------------------------------- + def br_type + @property_hash[:br_type] || :absent + end + def br_type=(val) + @property_flush[:br_type] = val + end + + def external_ids + # result = vsctl("br-get-external-id", @resource[:bridge]) + vs = (@property_hash[:vendor_specific] || {}) + result = (vs[:external_ids] || '') + return result #.split("\n").join(",") + end + def external_ids=(val) + @property_flush[:external_ids] = val + end + + def vendor_specific + @property_hash[:vendor_specific] || :absent + end + def vendor_specific=(val) + @property_flush[:vendor_specific] = val + end + + def stp + # puppet has internal trancformation, and we shouldn't use boolean values. it works unstable!!! + @property_hash[:stp].to_s.to_sym + end + def stp=(val) + @property_flush[:stp] = (val.to_s.downcase.to_sym==:true) + end + + #----------------------------------------------------------------- + + def _split(string, splitter=",") + return Hash[string.split(splitter).map{|i| i.split("=")}] + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l2_ovs_bond/ovs.rb b/lib/puppet/provider/l2_ovs_bond/ovs.rb deleted file mode 100644 index 65b5de5..0000000 --- a/lib/puppet/provider/l2_ovs_bond/ovs.rb +++ /dev/null @@ -1,50 +0,0 @@ -Puppet::Type.type(:l2_ovs_bond).provide(:ovs) do - optional_commands( - :vsctl => "/usr/bin/ovs-vsctl", - :appctl => "/usr/bin/ovs-appctl" - ) - - def _exists(bond) - begin - appctl('bond/show', bond) - return true - rescue Puppet::ExecutionFailure - return false - end - end - - def exists? - _exists(@resource[:bond]) - end - - def create - if _exists(@resource[:bond]) - msg = "Bond '#{@resource[:bond]}' already exists" - if @resource[:skip_existing] - notice("#{msg}, skip creating.") - else - fail("#{msg}.") - end - end - - bond_create_cmd = ['add-bond', @resource[:bridge], @resource[:bond]] + Array(@resource[:ports]) - if ! @resource[:properties].empty? - bond_create_cmd += Array(@resource[:properties]) - end - begin - vsctl(bond_create_cmd) - rescue Puppet::ExecutionFailure => error - notice(">>>#{bond_create_cmd.join(',')}<<<") - fail("Can't create bond '#{@resource[:bond]}' (ports: #{@resource[:ports].join(',')}) for bridge '#{@resource[:bridge]}'.\n#{error}") - end - end - - def destroy - begin - vsctl("del-port", @resource[:bridge], @resource[:bond]) - rescue Puppet::ExecutionFailure - fail("Can't remove bond '#{@resource[:bond]}' from bridge '#{@resource[:bridge]}'.") - end - end - -end diff --git a/lib/puppet/provider/l2_ovs_bridge/ovs.rb b/lib/puppet/provider/l2_ovs_bridge/ovs.rb deleted file mode 100644 index e42c457..0000000 --- a/lib/puppet/provider/l2_ovs_bridge/ovs.rb +++ /dev/null @@ -1,85 +0,0 @@ -Puppet::Type.type(:l2_ovs_bridge).provide(:ovs) do - optional_commands :vsctl => "/usr/bin/ovs-vsctl" - - def exists? - vsctl("br-exists", @resource[:bridge]) - rescue Puppet::ExecutionFailure - return false - end - - def create - begin - vsctl('br-exists', @resource[:bridge]) - if @resource[:skip_existing] - notice("Bridge '#{@resource[:bridge]}' already exists, skip creating.") - #external_ids = @resource[:external_ids] if @resource[:external_ids] - set_port_properties() - set_interface_properties() - return true - else - raise Puppet::ExecutionFailure, "Bridge '#{@resource[:bridge]}' already exists." - end - rescue Puppet::ExecutionFailure - # pass - notice("Bridge '#{@resource[:bridge]}' not exists, creating...") - end - vsctl('add-br', @resource[:bridge]) - notice("bridge '#{@resource[:bridge]}' created.") - set_port_properties() - set_interface_properties() - external_ids = @resource[:external_ids] if @resource[:external_ids] - end - - def destroy - vsctl("del-br", @resource[:bridge]) - end - - def _split(string, splitter=",") - return Hash[string.split(splitter).map{|i| i.split("=")}] - end - - def external_ids - result = vsctl("br-get-external-id", @resource[:bridge]) - return result.split("\n").join(",") - end - - def external_ids=(value) - old_ids = _split(external_ids) - new_ids = _split(value) - - new_ids.each_pair do |k,v| - unless old_ids.has_key?(k) - vsctl("br-set-external-id", @resource[:bridge], k, v) - end - end - end - - private - - def set_port_properties() - if @resource[:interface_properties] - for option in Array(@resource[:interface_properties]) - begin - vsctl('--', "set", "Port", @resource[:bridge], option.to_s) - rescue Puppet::ExecutionFailure => error - raise Puppet::ExecutionFailure, "Interface '#{@resource[:bridge]}' can't set option '#{option}':\n#{error}" - end - end - end - return true - end - - def set_interface_properties() - if @resource[:interface_properties] - for option in Array(@resource[:interface_properties]) - begin - vsctl('--', "set", "Interface", @resource[:bridge], option.to_s) - rescue Puppet::ExecutionFailure => error - raise Puppet::ExecutionFailure, "Interface '#{@resource[:bridge]}' can't set option '#{option}':\n#{error}" - end - end - end - return true - end - -end diff --git a/lib/puppet/provider/l2_ovs_port/ovs.rb b/lib/puppet/provider/l2_ovs_port/ovs.rb deleted file mode 100644 index 1c6f991..0000000 --- a/lib/puppet/provider/l2_ovs_port/ovs.rb +++ /dev/null @@ -1,55 +0,0 @@ -Puppet::Type.type(:l2_ovs_port).provide(:ovs) do - optional_commands :vsctl => "/usr/bin/ovs-vsctl" - - def exists? - vsctl("list-ports", @resource[:bridge]).include? @resource[:interface] - end - - def create - begin - vsctl('port-to-br', @resource[:interface]) - if @resource[:skip_existing] - return true - else - raise Puppet::ExecutionFailure, "Port '#{@resource[:interface]}' already exists." - end - rescue Puppet::ExecutionFailure - # pass - end - # Port create begins from definition brodge and port - cmd = [@resource[:bridge], @resource[:interface]] - # add port properties (k/w) to command line - if @resource[:port_properties] - for option in Array(@resource[:port_properties]) - cmd += [option] - end - end - # set interface type - #TODO: implement type=>patch sintax as type=>'patch:peer-name' - if @resource[:type] and @resource[:type].to_s != '' - tt = "type=" + @resource[:type].to_s - cmd += ['--', "set", "Interface", @resource[:interface], tt] - end - # executing OVS add-port command - cmd = ["add-port"] + cmd - begin - vsctl(cmd) - rescue Puppet::ExecutionFailure => error - raise Puppet::ExecutionFailure, "Can't add port '#{@resource[:interface]}'\n#{error}" - end - # set interface properties - if @resource[:interface_properties] - for option in Array(@resource[:interface_properties]) - begin - vsctl('--', "set", "Interface", @resource[:interface], option.to_s) - rescue Puppet::ExecutionFailure => error - raise Puppet::ExecutionFailure, "Interface '#{@resource[:interface]}' can't set option '#{option}':\n#{error}" - end - end - end - end - - def destroy - vsctl("del-port", @resource[:bridge], @resource[:interface]) - end -end diff --git a/lib/puppet/provider/l2_patch/ovs.rb b/lib/puppet/provider/l2_patch/ovs.rb new file mode 100644 index 0000000..4cad825 --- /dev/null +++ b/lib/puppet/provider/l2_patch/ovs.rb @@ -0,0 +1,188 @@ +require 'puppetx/l23_utils' +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/ovs_base') + +Puppet::Type.type(:l2_patch).provide(:ovs, :parent => Puppet::Provider::Ovs_base) do + commands :vsctl => 'ovs-vsctl', + :ethtool_cmd => 'ethtool', + :brctl => 'brctl', + :iproute => 'ip' + + + def self.instances + vsctl_show = ovs_vsctl_show() + lnx_port_br_mapping = get_lnx_port_bridges_pairs() + jacks = [] + # didn't use .select{...} here for backward compatibility with ruby 1.8 + vsctl_show[:port].reject{|k,v| !(v[:port_type] & ['jack','internal']).any?}.each_pair do |p_name, p_props| + props = { + :name => p_name, + } + props.merge! p_props + if props[:port_type].include? 'jack' + debug("found jack '#{p_name}'") + # get 'peer' property and copy to jack + # didn't use .select{...} here for backward compatibility with ruby 1.8 + ifaces = vsctl_show[:interface].reject{|k,v| v[:port]!=p_name} + iface = ifaces[ifaces.keys[0]] + props[:peer] = (iface.has_key?(:options) ? iface[:options]['peer'] : nil) + elsif props[:port_type].include? 'internal' + debug("found 'internal' ovs port '#{p_name}'") + props[:cross] = true + else + #pass + end + jacks << props + end + # search pairs of jacks and make patchcord resources + patches = [] + skip = [] + mtu = nil + jacks.each do |jack| + next if skip.include? jack[:name] + if jack[:cross] + # process 'cross' patch between OVS and LNX bridge + peer = lnx_port_br_mapping[jack[:name]] + next if peer.nil? + _bridges = [jack[:bridge], peer[:bridge]] # no sort here!!! architecture limitation -- ovs brodge always first! + _tails = [jack[:name], jack[:name]] + mtu = File.open("/sys/class/net/#{jack[:name]}/mtu").read.chomp.to_i + else + # process patch between two OVS bridges + next if jack[:peer].nil? + found_peer = jacks.select{|j| j[:name]==jack[:peer]} + next if found_peer.empty? + peer = found_peer[0] + _bridges = [jack[:bridge], peer[:bridge]].sort + _tails = ([jack[:bridge], peer[:bridge]] == _bridges ? [jack[:name], peer[:name]] : [peer[:name], jack[:name]]) + end + props = { + :ensure => :present, + :name => L23network.get_patch_name([jack[:bridge],peer[:bridge]]), + :bridges => _bridges, + :jacks => _tails, + :mtu => mtu, + :cross => jack[:cross], + :provider => 'ovs' + } + debug("PREFETCH properties for '#{props[:name]}': #{props}") + patches << new(props) + skip << peer[:name] + end + return patches #.map{|x| new(x)} + end + + #----------------------------------------------------------------- + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + bridges = self.class.get_bridges_order_for_patch(@resource[:bridges]) + @property_flush[:bridges] = bridges + # + debug("Bridges: '#{bridges.join(', ')}.") + if File.directory?("/sys/class/net/#{bridges[1]}/bridge") + # creating 'cross' OVS-to-lnx patchcord + lnx_port_br_mapping = self.class.get_lnx_port_bridges_pairs() + jack = L23network.get_lnx_jack_name(bridges[0]) + vsctl('--may-exist', 'add-port', bridges[0], jack, '--', 'set', 'Interface', jack, 'type=internal') + if lnx_port_br_mapping.has_key? jack and lnx_port_br_mapping[jack][:bridge] != bridges[1] + # eject lnx-side jack from bridge, if jack aldeady a member + brctl('delif', lnx_port_br_mapping[jack][:bridge], jack) + lnx_port_br_mapping.delete(jack) + end + if !lnx_port_br_mapping.has_key? jack + begin + brctl('addif', bridges[1], jack) + rescue Exception => e + if e.to_s =~ /device\s+#{jack}\s+is\s+already\s+a\s+member\s+of\s+a\s+bridge/ + notice("'#{jack}' already addeded to '#{bridges[1]}' by ghost event.") + else + raise + end + end + end + else + # creating OVS-to-OVS patchcord + jacks = [] + jacks << L23network.get_ovs_jack_name(bridges[1]) + jacks << L23network.get_ovs_jack_name(bridges[0]) + #todo(sv): make type and peer change in flush + cmds = [] + cmds << ['--may-exist', 'add-port', bridges[0], jacks[0], '--', 'set', 'Interface', jacks[0], 'type=patch', "option:peer=#{jacks[1]}"] + cmds << ['--may-exist', 'add-port', bridges[1], jacks[1], '--', 'set', 'Interface', jacks[1], 'type=patch', "option:peer=#{jacks[0]}"] + cmds.each do |cmd| + begin + vsctl(cmd) + rescue Puppet::ExecutionFailure => error + raise Puppet::ExecutionFailure, "Can't add jack for patchcord '#{@resource[:name]}'\n#{error}" + end + end + end + end + + def destroy + if File.directory?("/sys/class/net/#{@resource[:bridges][1]}/bridge") + # removing 'cross' OVS-to-lnx patchcord + jack = L23network.get_lnx_jack_name(@resource[:bridges][0]) + if File.symlink?("/sys/class/net/#{@resource[:bridges][1]}/brif/#{jack}") + brctl('delif', @resource[:bridges][1], jack) + end + vsctl('del-port', @resource[:bridges][0], jack) + else + # removing OVS-to-OVS patchcord + bridges = @resource[:bridges].sort + jacks = [] + jacks << L23network.get_ovs_jack_name(bridges[1]) + jacks << L23network.get_ovs_jack_name(bridges[0]) + cmds = [] + cmds << ['del-port', bridges[0], jacks[0]] + cmds << ['del-port', bridges[1], jacks[1]] + cmds.each do |cmd| + begin + vsctl(cmd) + rescue Puppet::ExecutionFailure => error + raise Puppet::ExecutionFailure, "Can't remove jack for patchcord '#{@resource[:name]}'\n#{error}" + end + end + end + end + + def flush + if !@property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + if !['', 'absent'].include? @property_flush[:mtu].to_s + # 'absent' is a synonym 'do-not-touch' for MTU + @resource[:jacks].uniq.each do |iface| + self.class.set_mtu(iface, @property_flush[:mtu]) + end + end + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + + def bridges + self.class.get_bridges_order_for_patch(@property_hash[:bridges]) + end + def bridges=(val) + @property_flush[:bridges] = self.class.get_bridges_order_for_patch(val) + end + + def jacks + @property_hash[:jacks] + end + def jacks=(val) + nil + end + + def cross + @property_hash[:cross] + end + def cross=(val) + nil + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l2_port/lnx.rb b/lib/puppet/provider/l2_port/lnx.rb new file mode 100644 index 0000000..aae34fa --- /dev/null +++ b/lib/puppet/provider/l2_port/lnx.rb @@ -0,0 +1,205 @@ +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/lnx_base') + +Puppet::Type.type(:l2_port).provide(:lnx, :parent => Puppet::Provider::Lnx_base) do + defaultfor :osfamily => :linux + commands :iproute => 'ip', + :ethtool_cmd => 'ethtool', + :brctl => 'brctl', + :pkill => 'pkill' + + + def self.instances + rv = [] + #todo: what do with OVS ports, inserted in LNX bridge? i.e. port located in two bridges. + ports = get_lnx_ports() + ovs_interfaces = get_ovs_interfaces() + ports.each_pair do |if_name, if_props| + props = { + :ensure => :present, + :name => if_name, + :vendor_specific => {} + } + debug("prefetching interface '#{if_name}'") + props.merge! if_props + props[:ethtool] = get_iface_ethtool_hash(if_name, nil) + # add PROVIDER prefix to port type flags and convert port_type to string + if ovs_interfaces.has_key? if_name and ovs_interfaces[if_name][:port_type].is_a? Array and ovs_interfaces[if_name][:port_type].include? 'internal' + if_provider = ovs_interfaces[if_name][:provider] + props[:port_type] = ovs_interfaces[if_name][:port_type] + props[:provider] = ovs_interfaces[if_name][:provider] + else + if_provider = props[:provider] + end + props[:port_type] = props[:port_type].insert(0, if_provider).join(':') + if if_provider == 'lnx' + rv << new(props) + debug("PREFETCH properties for '#{if_name}': #{props}") + else + debug("SKIP properties for '#{if_name}': #{props}") + end + end + return rv + end + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + # todo: divide simple creating interface and vlan + begin + iproute('link', 'add', 'link', @resource[:vlan_dev], 'name', @resource[:interface], 'type', 'vlan', 'id', @resource[:vlan_id]) + rescue + # Some time interface may be created by OS init scripts. It's a normal for Ubuntu. + raise if ! self.class.iface_exist? @resource[:interface] + notice("'#{@resource[:interface]}' already created by ghost event.") + end + end + + def destroy + debug("DESTROY resource: #{@resource}") + # todo: Destroing of L2 resource -- is a putting interface to the DOWN state. + # Or remove, if ove a vlan interface + #iproute('--force', 'addr', 'flush', 'dev', @resource[:interface]) + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + # If port is configured by dhcp, dhclient process could exist hence we have to kill it before configuring + begin + pkill('-KILL', '-f', "dhclient.*#{@resource[:interface]}$") + rescue + notice("'#{@resource[:interface]}' does not have any running dhclient processes") + end + # + # FLUSH changed properties + if @property_flush.has_key? :bond_master + bond = @old_property_hash[:bond_master] + # putting interface to the down-state, because add/remove upped interface impossible. undocumented kern.behavior. + iproute('--force', 'link', 'set', 'dev', @resource[:interface], 'down') + if bond and bond != :absent + # remove interface from bond, if one included to it + debug("Remove interface '#{@resource[:interface]}' from bond '#{bond}'.") + File.open("/sys/class/net/#{@resource[:interface]}/master/bonding/slaves", "a") {|f| f << "-#{@resource[:interface]}"} + end + if ! @property_flush[:bond_master].nil? and @property_flush[:bond_master] != :absent + # add interface as slave to bond + debug("Add interface '#{@resource[:interface]}' to bond '#{@property_flush[:bond_master]}'.") + File.open("/sys/class/net/#{@property_flush[:bond_master]}/bonding/slaves", "a") {|f| f << "+#{@resource[:interface]}"} + else + # port no more member of any bonds + @property_flush[:port_type] = nil + end + end + if @property_flush.has_key? :bridge + # get actual bridge-list. We should do it here, + # because bridge may be not existing at prefetch stage. + @bridges ||= self.class.get_bridge_list # resource port can't change bridge list + debug("Actual-bridge-list: #{@bridges}") + port_bridges_hash = self.class.get_port_bridges_pairs() + debug("Actual-port-bridge-mapping: '#{port_bridges_hash}'") # it should removed from LNX + # + #Flush ipaddr and routes for interface, thah adding to the bridge + iproute('route', 'flush', 'dev', @resource[:interface]) + iproute('addr', 'flush', 'dev', @resource[:interface]) + iproute('--force', 'link', 'set', 'dev', @resource[:interface], 'down') + # remove interface from old bridge + if ! port_bridges_hash[@resource[:interface]].nil? + br_name = port_bridges_hash[@resource[:interface]][:bridge] + br_type = port_bridges_hash[@resource[:interface]][:br_type] + if br_name != @resource[:interface] + # do not remove bridge-based interface from his bridge + case br_type + when :ovs + ovs_vsctl(['del-port', br_name, @resource[:interface]]) + when :lnx + brctl('delif', br_name, @resource[:interface]) + else + #pass + end + end + end + # add port to the new bridge + if !@property_flush[:bridge].nil? and @property_flush[:bridge].to_sym != :absent + case @bridges[@property_flush[:bridge]][:br_type] + when :ovs + ovs_vsctl(['add-port', @property_flush[:bridge], @resource[:interface]]) + when :lnx + begin + brctl('addif', @property_flush[:bridge], @resource[:interface]) + rescue + # Sometimes interface may be automatically added to bridge if config file exists before interface creation, + # especially vlan interfaces. It appears on CentOS. + raise if ! File.exist? "/sys/class/net/#{@property_flush[:bridge]}/brif/#{@resource[:interface]}" + notice("'#{@resource[:interface]}' is already a member of a bridge '#{@property_flush[:bridge]}'.") + end + else + #pass + end + end + iproute('link', 'set', 'dev', @resource[:interface], 'up') + debug("Change bridge") + end + if @property_flush.has_key? :ethtool and @property_flush[:ethtool].is_a? Hash + @property_flush[:ethtool].each_pair do |section, pairs| + debug("Setup '#{section}' by ethtool for interface '#{@resource[:interface]}'.") + optmaps = self.class.get_ethtool_name_commands_mapping[section] + if optmaps + pairs.each_pair do |k,v| + if optmaps.has_key? k + _cmd = [optmaps['__section_key_set__'], @resource[:interface], optmaps[k], v ? 'on':'off'] + begin + ethtool_cmd(_cmd) + rescue Exception => e + warn("Non-fatal error: #{e.to_s}") + end + end + end + else + warn("No mapping for ethtool section '#{section}' for interface '#{@resource[:interface]}'.") + end + end + end + if ! @property_flush[:onboot].nil? + # Should be after bond, because interface may auto-upped while added to the bond + debug("Setup UP state for interface '#{@resource[:interface]}'.") + iproute('link', 'set', 'dev', @resource[:interface], 'up') + end + if !['', 'absent'].include? @property_flush[:mtu].to_s + self.class.set_mtu(@resource[:interface], @property_flush[:mtu]) + end + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + def vlan_dev + @property_hash[:vlan_dev] || :absent + end + def vlan_dev=(val) + @property_flush[:vlan_dev] = val + end + + def vlan_id + @property_hash[:vlan_id] || :absent + end + def vlan_id=(val) + @property_flush[:vlan_id] = val + end + + def vlan_mode + @property_hash[:vlan_mode] || :absent + end + def vlan_mode=(val) + @property_flush[:vlan_mode] = val + end + + def bond_master + @property_hash[:bond_master] || :absent + end + def bond_master=(val) + @property_flush[:bond_master] = val + end + +end +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/provider/l2_port/ovs.rb b/lib/puppet/provider/l2_port/ovs.rb new file mode 100644 index 0000000..d09f969 --- /dev/null +++ b/lib/puppet/provider/l2_port/ovs.rb @@ -0,0 +1,100 @@ +require File.join(File.dirname(__FILE__), '..','..','..','puppet/provider/ovs_base') + +Puppet::Type.type(:l2_port).provide(:ovs, :parent => Puppet::Provider::Ovs_base) do + commands :vsctl => 'ovs-vsctl', + :ethtool_cmd => 'ethtool', + :iproute => 'ip' + + + def self.add_unremovable_flag(port_props) + # calculate 'unremovable' flag. Should be re-defined in chield providers + if port_props[:port_type].include? 'bridge' or port_props[:port_type].include? 'bond' + port_props[:port_type] << 'unremovable' + end + end + + def self.get_instances(big_hash) + big_hash[:port] + end + + #----------------------------------------------------------------- + + def create + debug("CREATE resource: #{@resource}") + @old_property_hash = {} + @property_flush = {}.merge! @resource + # + cmd = ["add-port", @resource[:bridge], @resource[:interface]] + # # tag and trunks for port + # port_properties = @resource[:port_properties] + # if ![nil, :absent].include? @resource[:vlan_id] and @resource[:vlan_id] > 0 + # port_properties << "tag=#{@resource[:vlan_id]}" + # end + # if ![nil, :absent].include? @resource[:trunks] and !@resource[:trunks].empty? + # port_properties.insert(-1, "trunks=[#{@resource[:trunks].join(',')}]") + # end + # Port create begins from definition brodge and port + # # add port properties (k/w) to command line + # if not port_properties.empty? + # for option in port_properties + # cmd.insert(-1, option) + # end + # end + # set interface type + if @resource[:type] and (@resource[:type].to_s != '' or @resource[:type].to_s != :absent) + tt = "type=" + @resource[:type].to_s + else + tt = "type=internal" + end + cmd += ['--', "set", "Interface", @resource[:interface], tt] + # executing OVS add-port command + begin + vsctl(cmd) + rescue Puppet::ExecutionFailure => error + raise Puppet::ExecutionFailure, "Can't add port '#{@resource[:interface]}'\n#{error}" + end + # # set interface properties + # if @resource[:interface_properties] + # for option in @resource[:interface_properties] + # begin + # vsctl('--', "set", "Interface", @resource[:interface], option.to_s) + # rescue Puppet::ExecutionFailure => error + # raise Puppet::ExecutionFailure, "Interface '#{@resource[:interface]}' can't set option '#{option}':\n#{error}" + # end + # end + # end + end + + def destroy + vsctl("del-port", @resource[:bridge], @resource[:interface]) + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + if !['', 'absent'].include? @property_flush[:mtu].to_s + self.class.set_mtu(@resource[:interface], @property_flush[:mtu]) + end + if @property_flush.has_key? :vlan_id + if !@property_flush[:vlan_id].nil? and @property_flush[:vlan_id] != :absent + vsctl('set', 'Port', @resource[:interface], "tag=#{@property_flush[:vlan_id].to_i}") + else + # remove 802.1q tag + vsctl('set', 'Port', @resource[:interface], "tag='[]'") + end + end + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + + def ethtool + @property_hash[:ethtool] || nil + end + def ethtool=(val) + @property_flush[:ethtool] = val + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l3_if_downup/ruby.rb b/lib/puppet/provider/l3_if_downup/ruby.rb deleted file mode 100644 index 9d4b44d..0000000 --- a/lib/puppet/provider/l3_if_downup/ruby.rb +++ /dev/null @@ -1,171 +0,0 @@ -require 'ipaddr' - -begin - require 'util/netstat.rb' -rescue LoadError => e - # puppet apply does not add module lib directories to the $LOAD_PATH (See - # #4248). It should (in the future) but for the time being we need to be - # defensive which is what this rescue block is doing. - rb_file = File.join(File.dirname(__FILE__), 'util', 'netstat.rb') - load rb_file if File.exists?(rb_file) or raise e -end - -def get_gateway() - Facter::Util::NetStat.get_route_value('default', 'gw') || Facter::Util::NetStat.get_route_value('0.0.0.0', 'gw') -end - -def find_gateway(interface, if_file) - rv = nil - def_route = get_gateway() - if def_route and if_file - ifile = /\[(\S+)\]/.match(if_file.to_s()) - if ifile - #notice("RT-def-route: '#{def_route}' int_file: '#{ifile}'") ################ - ifile = ifile[1] - begin - File.open(ifile, 'r').each() do |line| - gate = /gateway\s+(\d+\.\d+\.\d+\.\d+)/.match(line.to_s()) || /GATEWAY\s*=\s*(\d+\.\d+\.\d+\.\d+)/.match(line.to_s()) - if gate - gate = gate[1] - #notice("IN-FILE-GATE: '#{gate}'") - if gate == def_route - rv = gate - #notice("IN-FILE-GATE: '#{gate}' rv = gate") - end - end - end - rescue - notice("Can't open file '#{ifile}'") - end - end - else - notice("Default route: UNKNOWN") - end - return rv -end - -Puppet::Type.type(:l3_if_downup).provide(:ruby) do - confine :osfamily => [:debian, :redhat] - optional_commands( - :ifup => 'ifup', - :ifdn => 'ifdown', - :ip => "ip", - :kill => "kill", - :ps => "ps", - :ping => "ping" - ) - - def ping_ip(ipaddr,timeout) - end_time = Time.now.to_i + timeout - rv = false - loop do - begin - ping(['-c1',ipaddr]) - rv = true - break - rescue Puppet::ExecutionFailure => e - current_time = Time.now.to_i - if current_time > end_time - break - else - wa = end_time - current_time - notice("Host #{ipaddr} not answered. Wait up to #{wa} sec.") - #notice e.message - end - sleep(0.5) # do not remove!!! It's a positive brake! - end - end - return rv - end - - def restart() - begin # downing inteface - ifdn(@resource[:interface]) - notice("Interface '#{@resource[:interface]}' down.") - sleep @resource[:sleep_time] - rescue Puppet::ExecutionFailure - notice("Can't put interface '#{@resource[:interface]}' to DOWN state.") - end - if @resource[:kill_dhclient] and Facter.value(:osfamily) == 'Debian' - # kill forgotten dhclient in Ubuntu - dhclient = @resource[:dhclient_name] - iface = @resource[:interface] - ps('axf').each_line do |line| - rg = line.match("^\s*([0-9]+)\s+.*#{dhclient}\s+.*(\s#{iface})") - if rg - begin - kill(['-9',rg[1]]) - notice("Killed forgotten #{dhclient} with PID=#{rg[1]} succeffuly...") - sleep @resource[:sleep_time] - rescue Puppet::ExecutionFailure - notice("Can't kill #{dhclient} with PID=#{rg[1]}") - end - end - end - end - if @resource[:flush] # Flushing IP addresses from interface - begin - ip(['addr', 'flush', @resource[:interface]]) - notice("Interface '#{@resource[:interface]}' flush.") - sleep @resource[:sleep_time] - rescue Puppet::ExecutionFailure - notice("Can't flush interface '#{@resource[:interface]}'.") - end - end - return true if @resource[:onlydown] - begin # Put interface to UP state - ifup(@resource[:interface]) - notice("Interface '#{@resource[:interface]}' up.") - if @resource[:check_by_ping] == 'gateway' - # find gateway for interface and ping it - ip_to_ping = find_gateway(@resource[:interface], @resource[:subscribe]) - if ip_to_ping - notice("Interface '#{@resource[:interface]}' Gateway #{ip_to_ping} will be pinged. Wait up to #{@resource[:check_by_ping_timeout]} sec.") - rv = self.ping_ip(ip_to_ping, @resource[:check_by_ping_timeout].to_i) - if rv - notice("Interface '#{@resource[:interface]}' #{ip_to_ping} is OK") - else - notice("Interface '#{@resource[:interface]}' #{ip_to_ping} no answer :(") - end - end - elsif @resource[:check_by_ping] == 'none' - #pass - notice("Interface '#{@resource[:interface]}' Don't checked.") - else - # IP address given - notice("Interface '#{@resource[:interface]}' IP #{@resource[:check_by_ping]} will be pinged. Wait up to #{@resource[:check_by_ping_timeout]} sec.") - rv = self.ping_ip(@resource[:check_by_ping], @resource[:check_by_ping_timeout].to_i) - if rv - notice("Interface '#{@resource[:interface]}' #{@resource[:check_by_ping]} is OK") - else - notice("Interface '#{@resource[:interface]}' #{@resource[:check_by_ping]} no answer :(") - end - end - notice("Interface '#{@resource[:interface]}' done.") - rescue Puppet::ExecutionFailure - notice("Can't put interface '#{@resource[:interface]}' to UP state.") - end - end - - def create() - if ! @resource[:refreshonly] - restart() - end - end - - def destroy() - end - - # def self.instances - # if_list = [] - # File.open("/proc/net/dev", "r") do |raw_iflist| - # while (line = raw_iflist.gets) - # rg = line.match('^\s*([0-9A-Za-z\.\-\_]+):') - # if rg - # if_list.push(rg[1].to_sym) - # end - # end - # end - # return if_list - # end -end diff --git a/lib/puppet/provider/l3_if_downup/util/netstat.rb b/lib/puppet/provider/l3_if_downup/util/netstat.rb deleted file mode 100644 index 5faa7a0..0000000 --- a/lib/puppet/provider/l3_if_downup/util/netstat.rb +++ /dev/null @@ -1,71 +0,0 @@ -module Facter::Util::NetStat - def self.column_map - { - :bsd => { - :aliases => [:sunos, :freebsd, :netbsd, :darwin], - :dest => 0, - :gw => 1, - :iface => 5 - }, - :linux => { - :dest => 0, - :gw => 1, - :iface => 7 - }, - :openbsd => { - :dest => 0, - :gw => 1, - :iface => 6 - } - } - end - - def self.supported_platforms - column_map.inject([]) do |result, tmp| - key, map = tmp - if map[:aliases] - result += map[:aliases] - else - result << key - end - result - end - end - - def self.get_ipv4_output - output = "" - case Facter.value(:kernel) - when 'SunOS', 'FreeBSD', 'NetBSD', 'OpenBSD' - output = %x{/usr/bin/netstat -rn -f inet} - when 'Darwin' - output = %x{/usr/sbin/netstat -rn -f inet} - when 'Linux' - output = %x{/bin/netstat -rn -A inet} - end - output - end - - def self.get_route_value(route, label) - tmp1 = [] - - kernel = Facter.value(:kernel).downcase.to_sym - - # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. - unless map = column_map[kernel] || column_map.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } - return nil - end - - c1 = map[:dest] - c2 = map[label.to_sym] - - ((output = get_ipv4_output).respond_to?(:lines) ? output.lines.to_a : output.to_a).collect { |s| s.split}.each { |a| - if a[c1] == route - tmp1 << a[c2] - end - } - - if tmp1 - return tmp1.shift - end - end -end diff --git a/lib/puppet/provider/l3_ifconfig/lnx.rb b/lib/puppet/provider/l3_ifconfig/lnx.rb new file mode 100644 index 0000000..321475a --- /dev/null +++ b/lib/puppet/provider/l3_ifconfig/lnx.rb @@ -0,0 +1,243 @@ +Puppet::Type.type(:l3_ifconfig).provide(:lnx) do + defaultfor :osfamily => :linux + commands :iproute => 'ip', + :ifup => 'ifup', + :ifdown => 'ifdown' + + + def self.prefetch(resources) + interfaces = instances + resources.keys.each do |name| + if provider = interfaces.find{ |ii| ii.name == name } + resources[name].provider = provider + end + end + end + + def self.instances + insts = [] + rou_list = self.get_if_defroutes_mappings() + # parse all system interfaces + self.get_if_addr_mappings().each_pair do |if_name, pro| + props = { + :ensure => :present, + :name => if_name, + :ipaddr => pro[:ipaddr], + } + if !rou_list[if_name].nil? + props.merge! rou_list[if_name] + else + props.merge!({ + :gateway => :absent, + :gateway_metric => :absent + }) + end + debug("PREFETCHED properties for '#{if_name}': #{props}") + insts << new(props) + end + return insts + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + debug("CREATE resource: #{@resource}") # with hash: '#{m}'") + @old_property_hash = {} + @property_flush = {}.merge! @resource + #p @property_flush + #p @property_hash + #p @resource.inspect + end + + def destroy + debug("DESTROY resource: #{@resource}") + # todo: Destroing of L3 resource -- is a removing any IP addresses. + # DO NOT!!! put intedafce to Down state. + iproute('--force', 'addr', 'flush', 'dev', @resource[:interface]) + @property_hash.clear + end + + def initialize(value={}) + super(value) + @property_flush = {} + @old_property_hash = {} + @old_property_hash.merge! @property_hash + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + # + # FLUSH changed properties + if ! @property_flush[:ipaddr].nil? + if @property_flush[:ipaddr].include?(:absent) + # flush all ip addresses from interface + iproute('--force', 'addr', 'flush', 'dev', @resource[:interface]) + #todo(sv): check for existing dhclient for this interface and kill it + elsif (@property_flush[:ipaddr] & [:dhcp, 'dhcp', 'DHCP']).any? + # start dhclient on interface the same way as at boot time + ifdown(@resource[:interface]) + sleep(5) + ifup(@resource[:interface]) + else + # add-remove static IP addresses + if !@old_property_hash.nil? and !@old_property_hash[:ipaddr].nil? + (@old_property_hash[:ipaddr] - @property_flush[:ipaddr]).each do |ipaddr| + iproute('--force', 'addr', 'del', ipaddr, 'dev', @resource[:interface]) + end + adding_addresses = @property_flush[:ipaddr] - @old_property_hash[:ipaddr] + else + adding_addresses = @property_flush[:ipaddr] + end + if adding_addresses.include? :none + iproute('--force', 'link', 'set', 'dev', @resource[:interface], 'up') + elsif adding_addresses.include? :dhcp + debug("!!! DHCP runtime configuration not implemented now !!!") + else + # add IP addresses + adding_addresses.each do |ipaddr| + iproute('addr', 'add', ipaddr, 'dev', @resource[:interface]) + end + end + end + end + + if !@property_flush[:gateway].nil? or !@property_flush[:gateway_metric].nil? + # clean all default gateways for this interface with any metrics + cmdline = ['route', 'del', 'default', 'dev', @resource[:interface]] + rc = 0 + while rc == 0 + # we should remove route repeatedly for prevent situation + # when has multiple default routes through the same router, + # but with different metrics + begin + iproute(cmdline) + rescue + rc = 1 + end + end + # add new route + if @resource[:gateway] != :absent + cmdline = ['route', 'add', 'default', 'via', @resource[:gateway], 'dev', @resource[:interface]] + if ![nil, :absent].include?(@property_flush[:gateway_metric]) and @property_flush[:gateway_metric].to_i > 0 + cmdline << ['metric', @property_flush[:gateway_metric]] + end + begin + rv = iproute(cmdline) + rescue + warn("!!! Iproute can't setup new gateway.\n!!! May be you already have default gateway with same metric:") + rv = iproute('-f', 'inet', 'route', 'show') + warn("#{rv}\n\n") + end + end + end + + # if ! @property_flush[:onboot].nil? + # iproute('link', 'set', 'dev', @resource[:interface], 'up') + # end + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + # def bridge + # @property_hash[:bridge] || :absent + # end + # def bridge=(val) + # @property_flush[:bridge] = val + # end + + # def name + # @property_hash[:name] + # end + + def port_type + @property_hash[:port_type] || :absent + end + def port_type=(val) + @property_flush[:port_type] = val + end + + def onboot + @property_hash[:onboot] || :absent + end + def onboot=(val) + @property_flush[:onboot] = val + end + + def ipaddr + @property_hash[:ipaddr] || :absent + end + def ipaddr=(val) + if (@old_property_hash[:ipaddr] - val) != (val - @old_property_hash[:ipaddr]) + @property_flush[:ipaddr] = val + end + end + + def gateway + @property_hash[:gateway] || :absent + end + def gateway=(val) + @property_flush[:gateway] = val + end + + def gateway_metric + @property_hash[:gateway_metric] || :absent + end + def gateway_metric=(val) + @property_flush[:gateway_metric] = val + end + + def dhcp_hostname + @property_hash[:dhcp_hostname] || :absent + end + def dhcp_hostname=(val) + @property_flush[:dhcp_hostname] = val + end + + def vendor_specific + @property_hash[:vendor_specific] || :absent + end + def vendor_specific=(val) + nil + end + + #----------------------------------------------------------------- + + def self.get_if_addr_mappings + if_list = {} + ip_a = iproute('-f', 'inet', 'addr', 'show').split(/\n+/) + if_name = nil + ip_a.each do |line| + line.rstrip! + case line + when /^\s*\d+\:\s+([\w\-\.]+)[\:\@]/i + if_name = $1 + if_list[if_name] = { :ipaddr => [] } + when /^\s+inet\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/ + next if if_name.nil? + if_list[if_name][:ipaddr] << $1 + else + next + end + end + return if_list + end + + def self.get_if_defroutes_mappings + rou_list = {} + ip_a = iproute('-f', 'inet', 'route', 'show').split(/\n+/) + ip_a.each do |line| + line.rstrip! + next if !line.match(/^\s*default\s+via\s+([\d\.]+)\s+dev\s+([\w\-\.]+)(\s+metric\s+(\d+))?/) + metric = $4.nil? ? :absent : $4.to_i + rou_list[$2] = { :gateway => $1, :gateway_metric => metric } if rou_list[$2].nil? # do not replace to gateway with highest metric + end + return rou_list + end + + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/l3_route/lnx.rb b/lib/puppet/provider/l3_route/lnx.rb new file mode 100644 index 0000000..f262904 --- /dev/null +++ b/lib/puppet/provider/l3_route/lnx.rb @@ -0,0 +1,172 @@ +require 'ipaddr' +require 'yaml' +require 'puppetx/l23_utils' + +Puppet::Type.type(:l3_route).provide(:lnx) do + defaultfor :osfamily => :linux + commands :iproute => 'ip' + + + def self.prefetch(resources) + interfaces = instances + resources.keys.each do |name| + if provider = interfaces.find{ |ii| ii.name == name } + resources[name].provider = provider + end + end + end + + def self.get_routes + # return array of hashes -- all defined routes. + rv = [] + # cat /proc/net/route returns information about routing table in format: + # Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT + # eth0 00000000 0101010A 0003 0 0 0 00000000 0 0 0 + # eth0 0001010A 00000000 0001 0 0 0 00FFFFFF 0 0 0 + File.open('/proc/net/route').readlines.reject{|l| l.match(/^[Ii]face.+/) or l.match(/^(\r\n|\n|\s*)$|^$/)}.map{|l| l.split(/\s+/)}.each do |line| + #https://github.com/kwilczynski/facter-facts/blob/master/default_gateway.rb + iface = line[0] + metric = line[6] + # whether gateway is default + if line[1] == '00000000' + dest = 'default' + dest_addr = nil + mask = nil + route_type = 'default' + else + dest_addr = [line[1]].pack('H*').unpack('C4').reverse.join('.') + mask = [line[7]].pack('H*').unpack('B*')[0].count('1') + dest = "#{dest_addr}/#{mask}" + end + # whether route is local + if line[2] == '00000000' + gateway = nil + route_type = 'local' + else + gateway = [line[2]].pack('H*').unpack('C4').reverse.join('.') + route_type = nil + end + rv << { + :destination => dest, + :gateway => gateway, + :metric => metric.to_i, + :type => route_type, + :interface => iface, + } + end + # this sort need for prioritize routes by metrics + return rv.sort_by{|r| r[:metric]||0} + end + + def self.instances + rv = [] + routes = get_routes() + routes.each do |route| + name = L23network.get_route_resource_name(route[:destination], route[:metric]) + props = { + :ensure => :present, + :name => name, + } + props.merge! route + props.delete(:metric) if props[:metric] == 0 + debug("PREFETCHED properties for '#{name}': #{props}") + rv << new(props) + end + return rv + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + debug("CREATE resource: #{@resource}") + @property_flush = {}.merge! @resource + #todo(sv): check accessability of gateway. + cmd = ['route', 'add', @resource[:destination], 'via', @resource[:gateway]] + cmd << ['metric', @resource[:metric]] if @resource[:metric] != :absent && @resource[:metric].to_i > 0 + iproute(cmd) + @old_property_hash = {} + @old_property_hash.merge! @resource + end + + def destroy + debug("DESTROY resource: #{@resource}") + cmd = ['--force', 'route', 'del', @resource[:destination], 'via', @resource[:gateway]] + cmd << ['metric', @resource[:metric]] if @resource[:metric] != :absent && @resource[:metric].to_i > 0 + iproute(cmd) + @property_hash.clear + end + + def initialize(value={}) + super(value) + @property_flush = {} + @old_property_hash = {} + @old_property_hash.merge! @property_hash + end + + def flush + if ! @property_flush.empty? + debug("FLUSH properties: #{@property_flush}") + # + # FLUSH changed properties + if @property_flush.has_key? :gateway + # gateway can't be "absent" by design + #debug("RES: '#{@resource[:gateway]}', OLD:'#{@old_property_hash[:gateway]}', FLU:'#{@property_flush[:gateway]}'") + if @old_property_hash[:gateway] != @property_flush[:gateway] + cmd = ['route', 'change', @resource[:destination], 'via', @property_flush[:gateway]] + cmd << ['metric', @resource[:metric]] if @resource[:metric] != :absent && @resource[:metric].to_i > 0 + iproute(cmd) + end + end + + @property_hash = resource.to_hash + end + end + + #----------------------------------------------------------------- + def destination + @property_hash[:destination] || :absent + end + def destination=(val) + @property_flush[:destination] = val + end + + def gateway + @property_hash[:gateway] || :absent + end + def gateway=(val) + @property_flush[:gateway] = val + end + + def metric + @property_hash[:metric] || :absent + end + def metric=(val) + @property_flush[:metric] = val + end + + def interface + @property_hash[:interface] || :absent + end + def interface=(val) + @property_flush[:interface] = val + end + + def type + @property_hash[:type] || :absent + end + def type=(val) + @property_flush[:type] = val + end + + def vendor_specific + @property_hash[:vendor_specific] || :absent + end + def vendor_specific=(val) + nil + end + #----------------------------------------------------------------- + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/lnx_base.rb b/lib/puppet/provider/lnx_base.rb new file mode 100644 index 0000000..fb02a28 --- /dev/null +++ b/lib/puppet/provider/lnx_base.rb @@ -0,0 +1,94 @@ +require File.join(File.dirname(__FILE__), 'l2_base') + +class Puppet::Provider::Lnx_base < Puppet::Provider::L2_base + + #todo(sv): adapt this to LNX resources + # def self.instances + # rv = [] + # get_instances(ovs_vsctl_show()).each_pair do |p_name, p_props| + # props = { + # :ensure => :present, + # :name => p_name, + # :vendor_specific => {} + # } + # debug("prefetching '#{p_name}'") + # props.merge! p_props + # next if skip_port_for? props + # add_unremovable_flag(props) + # ##add PROVIDER prefix to port type flags and create puppet resource + # if props[:provider] == 'ovs' + # props[:port_type] = props[:port_type].insert(0, 'ovs').join(':') + # rv << new(props) + # debug("PREFETCH properties for '#{p_name}': #{props}") + # else + # debug("SKIP properties for '#{p_name}': #{props}") + # end + # end + # return rv + # end + + + def initialize(value={}) + super(value) + @property_flush = {} + @old_property_hash = {} + @old_property_hash.merge! @property_hash + end + + def exists? + @property_hash[:ensure] == :present + end + + #----------------------------------------------------------------- + def vendor_specific + @property_hash[:vendor_specific] || :absent + end + def vendor_specific=(val) + nil + end + + def mtu + @property_hash[:mtu] || :absent + end + def mtu=(val) + # for MTU :absent is sinonym of 'do not change' + @property_flush[:mtu] = val.to_i if !['', 'absent'].include? val.to_s + end + + def onboot + @property_hash[:onboot] || :absent + end + def onboot=(val) + @property_flush[:onboot] = val + end + + def bridge + @property_hash[:bridge] || :absent + end + def bridge=(val) + @property_flush[:bridge] = val + end + + def ethtool + @property_hash[:ethtool] || nil + end + def ethtool=(val) + @property_flush[:ethtool] = val + end + + def port_type + @property_hash[:port_type] || :absent + end + def port_type=(val) + @property_flush[:port_type] = val + end + + def type + :absent + end + def type=(value) + debug("Resource '#{@resource[:name]}': Doesn't support interface type change.") + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/provider/ovs_base.rb b/lib/puppet/provider/ovs_base.rb new file mode 100644 index 0000000..a412198 --- /dev/null +++ b/lib/puppet/provider/ovs_base.rb @@ -0,0 +1,141 @@ +require File.join(File.dirname(__FILE__), 'l2_base') + +class Puppet::Provider::Ovs_base < Puppet::Provider::L2_base + + def self.skip_port_for?(port_props) + # calculate whether this port should be skipped. + # Should be re-defined in chield providers + false + end + + def self.add_unremovable_flag(port_props) + # calculate 'unremovable' flag. Should be re-defined in chield providers + true + end + + def self.get_instances(big_hash) + # calculate hash of hashes from given big hash + # Should be re-defined in chield providers + {} + end + + def self.instances + rv = [] + get_instances(ovs_vsctl_show()).each_pair do |p_name, p_props| + props = { + :ensure => :present, + :name => p_name, + :vendor_specific => {} + } + debug("prefetching '#{p_name}'") + props.merge! p_props + next if skip_port_for? props + add_unremovable_flag(props) + ##add PROVIDER prefix to port type flags and create puppet resource + if props[:provider] == 'ovs' + props[:port_type] = props[:port_type].insert(0, 'ovs').join(':') + rv << new(props) + debug("PREFETCH properties for '#{p_name}': #{props}") + else + debug("SKIP properties for '#{p_name}': #{props}") + end + end + return rv + end + + + def initialize(value={}) + super(value) + @property_flush = {} + @old_property_hash = {} + @old_property_hash.merge! @property_hash + end + + def exists? + @property_hash[:ensure] == :present + end + + #----------------------------------------------------------------- + #----------------------------------------------------------------- + def bridge + @property_hash[:bridge] || :absent + end + def bridge=(val) + @property_flush[:bridge] = val + end + + def vlan_dev + :absent + end + def vlan_dev=(val) + nil + end + + def vlan_id + @property_hash[:vlan_id] || :absent + end + def vlan_id=(val) + @property_flush[:vlan_id] = val + end + + def port_type + @property_hash[:port_type] || :absent + end + def port_type=(val) + @property_flush[:port_type] = val + end + + def vlan_mode + 'vlan' + end + def vlan_mode=(val) + nil + end + + def bond_master + :absent + end + def bond_master=(val) + nil + end + + def slaves + @property_hash[:slaves] || :absent + end + def slaves=(val) + nil + end + + def mtu + @property_hash[:mtu] || :absent + end + def mtu=(val) + # for MTU :absent is sinonym of 'do not change' + @property_flush[:mtu] = val.to_i if !['', 'absent'].include? val.to_s + end + + def onboot + @property_hash[:onboot] || :absent + end + def onboot=(val) + @property_flush[:onboot] = val + end + + def vendor_specific + @property_hash[:vendor_specific] || :absent + end + def vendor_specific=(val) + @property_flush[:vendor_specific] = val + end + + def type + @property_hash[:type] || :absent + end + def type=(value) + @property_flush[:type] = val + end + + #----------------------------------------------------------------- + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/cfg.rb b/lib/puppet/type/cfg.rb index e2374c2..e956a33 100644 --- a/lib/puppet/type/cfg.rb +++ b/lib/puppet/type/cfg.rb @@ -39,3 +39,4 @@ end end end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/k_mod.rb b/lib/puppet/type/k_mod.rb new file mode 100644 index 0000000..50efa4b --- /dev/null +++ b/lib/puppet/type/k_mod.rb @@ -0,0 +1,14 @@ +Puppet::Type.newtype(:k_mod) do + @doc = "Check and load kernel module, if need" + desc @doc + + ensurable + + MAX_BR_NAME_LENGTH = 15 + + newparam(:module) do + isnamevar + desc "Module name" + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l23_stored_config.rb b/lib/puppet/type/l23_stored_config.rb new file mode 100644 index 0000000..416f9c9 --- /dev/null +++ b/lib/puppet/type/l23_stored_config.rb @@ -0,0 +1,332 @@ +# type for managing persistent interface config options +# Inspired by puppet-network module. Adrien, thanks. + +require 'ipaddr' + +Puppet::Type.newtype(:l23_stored_config) do + @doc = "Manage lines in interface config file" + desc @doc + + feature :provider_options, <<-EOD + The provider can accept a hash of arbitrary options. The semantics of + these options will depend on the provider. + EOD + + ensurable + + newparam(:name) do + isnamevar + desc "The name of the physical or logical network device" + end + + newproperty(:method) do + desc "The method for determining an IP address for the interface" + # static -- assign IP address in config + # manual -- UP interface without IP address + newvalues(:static, :absent, :manual, :dhcp, :loopback, :none, :undef, :nil) + aliasvalue(:none, :manual) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :manual + end + + # newproperty(:port_type) do + # desc "port_type fake RO property" + # end + + newproperty(:if_type) do + desc "Device type. Service property, shouldn't be setting by puppet" + newvalues(:ethernet, :bridge, :bond) + end + + newproperty(:bridge, :array_matching => :all) do + # Array_matching for this property required for very complicated cases + # ex. patchcord for connectind two bridges or bridge and network namesspace + desc "Name of bridge, including this port" + newvalues(/^[\w+\-]+$/, :none, :undef, :nil, :absent) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newproperty(:jacks, :array_matching => :all) do + desc "Name of jacks for patchcord" + newvalues(/^[\w+\-]+$/) + end + + newproperty(:bridge_ports, :array_matching => :all) do + desc "Ports, member of bridge, service property, do not use directly." + end + + newproperty(:bridge_stp) do + desc "Whether stp enable" + newvalues(:true, :absent, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :absent + end + + newproperty(:onboot) do + desc "Whether to bring the interface up on boot" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + + def insync?(value) + value.to_s.downcase == should.to_s.downcase + end + end + + newproperty(:mtu) do + desc "The Maximum Transmission Unit size to use for the interface" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent # MTU value should be undefined by default, because some network resources (bridges, subinterfaces) + validate do |value| # inherits it from a parent interface + # Intel 82598 & 82599 chips support MTUs up to 16110; is there any + # hardware in the wild that supports larger frames? + # + # It appears loopback devices routinely have large MTU values; Eg. 65536 + # + # Frames small than 64bytes are discarded as runts. Smallest valid MTU + # is 42 with a 802.1q header and 46 without. + min_mtu = 42 + max_mtu = 65536 + if ! (value.to_s == 'absent' or (min_mtu .. max_mtu).include?(value.to_i)) + raise ArgumentError, "'#{value}' is not a valid mtu (must be a positive integer in range (#{min_mtu} .. #{max_mtu})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + end + + newproperty(:vlan_dev) do + desc "802.1q vlan base device" + end + + newproperty(:vlan_id) do + desc "802.1q vlan ID" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent + validate do |val| + min_vid = 1 + max_vid = 4094 + if ! (val.to_s == 'absent' or (min_vid .. max_vid).include?(val.to_i)) + raise ArgumentError, "'#{val}' is not a valid 802.1q NALN_ID (must be a integer value in range (#{min_vid} .. #{max_vid})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + end + + newproperty(:vlan_mode) do + desc "802.1q vlan interface naming model" + #newvalues(:ethernet, :bridge, :bond) + #defaultto :ethernet + end + + + newproperty(:ipaddr) do + desc "Primary IP address for interface" + newvalues(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/, :absent, :none, :undef, :nil, :dhcp) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newproperty(:gateway) do + desc "Default gateway" + newvalues(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newproperty(:gateway_metric) do + desc "Default gateway metric" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent + validate do |val| + min_metric = 0 + max_metric = 65535 + if ! (val.to_s == 'absent' or (min_metric .. max_metric).include?(val.to_i)) + raise ArgumentError, "'#{val}' is not a valid metric (must be a integer value in range (#{min_metric} .. #{max_metric})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + end + + newproperty(:bond_master) do + desc "bond name for bonded interface" + newvalues(/^[\w+\-]+$/, :none, :undef, :nil, :absent) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newproperty(:bond_slaves, :array_matching => :all) do + desc "slave ports for bond interface" + newvalues(/^[\w+\-]+$/, :false, :none, :undef, :nil, :absent) + #aliasvalue(:absent, :none) # none is a valid config value + aliasvalue(:false, :none) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newproperty(:bond_mode) + newproperty(:bond_miimon) + newproperty(:bond_lacp_rate) + newproperty(:bond_xmit_hash_policy) + + # # `:options` provides an arbitrary passthrough for provider properties, so + # # that provider specific behavior doesn't clutter up the main type but still + # # allows for more powerful actions to be taken. + # newproperty(:options, :required_features => :provider_options) do + # desc "Provider specific options to be passed to the provider" + + # def is_to_s(hash = @is) + # hash.keys.sort.map {|key| "#{key} => #{hash[key]}"}.join(", ") + # end + + # def should_to_s(hash = @should) + # hash.keys.sort.map {|key| "#{key} => #{hash[key]}"}.join(", ") + # end + + # defaultto {} + + # validate do |value| + # raise ArgumentError, "#{self.class} requires a hash for the options property" unless value.is_a? Hash + # #provider.validate + # end + # end + + newproperty(:routes) do + desc "routes, corresponded to this interface. This-a R/O property, that autofill from L3_route resource" + #defaultto {} # no default value should be!!! + validate do |val| + if ! val.is_a? Hash + fail("routes should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml.gsub('!ruby/sym','')}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml.gsub('!ruby/sym','')}\n" + end + end + + newproperty(:ethtool) do + desc "ethtool addition configuration for this interface" + #defaultto {} # no default value should be!!! + validate do |val| + if ! val.is_a? Hash + fail("ethtool commands should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml.gsub('!ruby/sym','')}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml.gsub('!ruby/sym','')}\n" + end + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml.gsub('!ruby/sym ','')}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml.gsub('!ruby/sym ','')}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + + def generate + if (!([:absent, :none, :nil, :undef] & self[:bridge]).any? and [:ethernet, :bond].include? self[:if_type]) + self[:bridge].each do |bridge| + br = self.catalog.resource('L23_stored_config', bridge) + fail("Stored_config resource for bridge '#{bridge}' not found for port '#{self[:name]}'!") if ! br + br[:bridge_ports] ||= [] + ports = br[:bridge_ports] + return if ! ports.is_a? Array + if ! ports.include? self[:name] + ports << self[:name].to_s + br[:bridge_ports] = ports.reject{|a| a=='none'}.sort + end + end + end + # find routes, that should be applied while this interface UP + if !['', 'none', 'absent'].include?(self[:ipaddr].to_s.downcase) + l3_routes = self.catalog.resources.reject{|nnn| nnn.ref.split('[')[0]!='L3_route'} + our_network = IPAddr.new(self[:ipaddr].to_s.downcase) + l3_routes.each do |rou| + if our_network.include? rou[:gateway] + self[:routes] ||= {} + self[:routes][rou[:name]] = { + :gateway => rou[:gateway], + :destination => rou[:destination] + } + self[:routes][rou[:name]][:metric] = rou[:metric] if !['', 'absent'].include? rou[:metric].to_s.downcase + end + end + end + nil + end +end +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/type/l2_bond.rb b/lib/puppet/type/l2_bond.rb new file mode 100644 index 0000000..d45f420 --- /dev/null +++ b/lib/puppet/type/l2_bond.rb @@ -0,0 +1,217 @@ +# type for managing runtime bond of NICs states. + +Puppet::Type.newtype(:l2_bond) do + @doc = "Manage a network port abctraction." + desc @doc + + ensurable + + newparam(:bond) do + isnamevar + desc "The bond name" + # + validate do |val| + if not val =~ /^[a-z_][\w\.\-]*[0-9a-z]$/ + fail("Invalid bond name: '#{val}'") + end + end + end + + newparam(:use_ovs) do + desc "Whether using OVS comandline tools" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + end + + newproperty(:port_type) do + desc "Internal read-only property" + validate do |value| + raise ArgumentError, "You shouldn't change port_type -- it's a internal RO property!" + end + end + + + newproperty(:onboot) do + desc "Whether to bring the interface up" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + + def insync?(value) + value.to_s.downcase == should.to_s.downcase + end + end + + newproperty(:bridge) do + desc "What bridge to use" + newvalues(/^[a-z][0-9a-z\-\_]*[0-9a-z]$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newproperty(:slaves, :array_matching => :all) do + desc "What bridge to use" + newvalues(/^[a-z][0-9a-z\-\_]*[0-9a-z]$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + # provider-specific list. may be empty. + def should_to_s(value) + value == :absent ? value : value.sort.join(',') + end + def is_to_s(value) + should_to_s(value) + end + def insync?(value) + should_to_s(value) == should_to_s(should) + end + + end + + newparam(:trunks, :array_matching => :all) do + desc "Array of trunks id, for configure patch's ends as ports in trunk mode" + end + + newproperty(:interface_properties) do + desc "Hash of bonded interfaces properties" + #defaultto {} + # provider-specific hash, validating only by type. + validate do |val| + if ! val.is_a? Hash + fail("Interface_properties should be a hash!") + end + end + + def should_to_s(value) + return :absent if value == :absent + rv = [] + value.keys.sort.each do |key| + rv << "(#{key.to_s}=#{value[key]})" + end + rv.join(', ') + end + + def is_to_s(value) + should_to_s(value) + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + newproperty(:bond_properties) do + desc "Hash of bond properties" + #defaultto {} + # provider-specific hash, validating only by type. + validate do |val| + #puts "l2_bond validate got '#{val.inspect}'" + if ! val.is_a? Hash + fail("Interface_properties should be a hash!") + end + end + + munge do |val| + # it's a workaround, because puppet double some values inside his internal logic + val.keys.each do |k| + if k.is_a? String + if ! val.has_key? k.to_sym + val[k.to_sym] = val[k] + end + val.delete(k) + end + end + val + end + + def should_to_s(value) + return '' if [:absent, 'absent', nil, {}].include? value + value.keys.sort.map{|k| "(#{k.to_s}=#{value[k]})"}.join(', ') + end + + def is_to_s(value) + should_to_s(value) + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + newproperty(:mtu) do + desc "The Maximum Transmission Unit size to use for the interface" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent # MTU value should be undefined by default, because some network resources (bridges, subinterfaces) + validate do |value| # inherits it from a parent interface + # Intel 82598 & 82599 chips support MTUs up to 16110; is there any + # hardware in the wild that supports larger frames? + # + # It appears loopback devices routinely have large MTU values; Eg. 65536 + # + # Frames small than 64bytes are discarded as runts. Smallest valid MTU + # is 42 with a 802.1q header and 46 without. + min_mtu = 42 + max_mtu = 65536 + if ! (value.to_s == 'absent' or (min_mtu .. max_mtu).include?(value.to_i)) + raise ArgumentError, "'#{value}' is not a valid mtu (must be a positive integer in range (#{min_mtu} .. #{max_mtu})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + + autorequire(:l2_bridge) do + [self[:bridge]] + end + + # def validate + # if self[:name].to_s == 'bond23' + # require 'pry' + # binding.pry + # end + # end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l2_bridge.rb b/lib/puppet/type/l2_bridge.rb new file mode 100644 index 0000000..0cf52a2 --- /dev/null +++ b/lib/puppet/type/l2_bridge.rb @@ -0,0 +1,115 @@ +# +Puppet::Type.newtype(:l2_bridge) do + @doc = "Manage a native linux and open vSwitch bridges (virtual switches)" + desc @doc + + ensurable + + MAX_BR_NAME_LENGTH = 15 + + newparam(:bridge) do + isnamevar + desc "The bridge to configure" + # + validate do |val| + if not val =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ + fail("Wrong bridge name: '#{val}'") + end + end + end + + newparam(:use_ovs) do + desc "Whether using OVS comandline tools" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + end + + newproperty(:external_ids) do + desc "External IDs for the bridge" + #defaultto {} # do not use defaultto here!!! + + validate do |val| + if ! val.is_a? Hash + fail("External_ids should be a hash!") + end + end + def should_to_s(value) + return [] if value == :absent + rv = [] + value.keys.sort.each do |key| + rv << "(#{key.to_s}=#{value[key]})" + end + rv.join(', ') + end + + def is_to_s(value) + should_to_s(value) + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + newproperty(:br_type) do + desc "Internal read-only property" + validate do |value| + raise ArgumentError, "You shouldn't change br_type -- it's a internal RO property!" + end + end + + newproperty(:stp) do + desc "Whether stp enable" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :false + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + + # global validator + def validate + # require 'pry' + # binding.pry + if provider.class.name != :ovs and self[:name].length > MAX_BR_NAME_LENGTH + # validate name for differetn providers may only in global validator, because + # 'provider' option don't accessible while validating name + fail("Wrong bridge name '#{self[:name]}'.\n For provider '#{provider.class.name}' bridge name shouldn't has length more, than #{MAX_BR_NAME_LENGTH}.") + end + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l2_ovs_bond.rb b/lib/puppet/type/l2_ovs_bond.rb deleted file mode 100644 index 0d91fdc..0000000 --- a/lib/puppet/type/l2_ovs_bond.rb +++ /dev/null @@ -1,56 +0,0 @@ -Puppet::Type.newtype(:l2_ovs_bond) do - @doc = "Manage a Open vSwitch port" - desc @doc - - ensurable - - newparam(:bond) do - isnamevar - desc "The bond name" - # - validate do |val| - if not val =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ - fail("Invalid bond name: '#{val}'") - end - end - end - - newparam(:ports) do - desc "List of ports that will be added to the bond" - # - validate do |val| - if not val.is_a?(Array) - fail("Ports parameter must be an array (not #{val.class}).") - end - for port in val - if not port =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ - fail("Invalid port name: '#{port}'") - end - end - end - end - - newparam(:skip_existing) do - defaultto(false) - desc "Allow to skip existing bond" - end - - newparam(:properties) do - defaultto([]) - desc "Array of bond properties" - end - - newparam(:bridge) do - desc "What bridge to use" - # - validate do |val| - if not val =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ - fail("Invalid bridge name: '#{val}'") - end - end - end - - autorequire(:l2_ovs_bridge) do - [self[:bridge]] - end -end diff --git a/lib/puppet/type/l2_ovs_bridge.rb b/lib/puppet/type/l2_ovs_bridge.rb deleted file mode 100644 index 26907ec..0000000 --- a/lib/puppet/type/l2_ovs_bridge.rb +++ /dev/null @@ -1,46 +0,0 @@ -Puppet::Type.newtype(:l2_ovs_bridge) do - @doc = "Manage a Open vSwitch bridge (virtual switch)" - desc @doc - - ensurable - - newparam(:bridge) do - isnamevar - desc "The bridge to configure" - # - validate do |val| - if not val =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ - fail("Invalid bridge name: '#{val}'") - end - end - end - - newparam(:port_properties) do - defaultto([]) - desc "Array of port properties" - validate do |val| - if not (val.is_a?(Array) or val.is_a?(String)) # String need for array with one element. it's a puppet's feature - fail("port_properties must be an array (not #{val.class}).") - end - end - end - - newparam(:interface_properties) do - defaultto([]) - desc "Array of port interface properties" - validate do |val| - if not (val.is_a?(Array) or val.is_a?(String)) # String need for array with one element. it's a puppet's feature - fail("interface_properties must be an array (not #{val.class}).") - end - end - end - - newparam(:skip_existing) do - defaultto(false) - desc "Allow to skip existing bridge" - end - - newproperty(:external_ids) do - desc "External IDs for the bridge" - end -end diff --git a/lib/puppet/type/l2_ovs_port.rb b/lib/puppet/type/l2_ovs_port.rb deleted file mode 100644 index 87d1379..0000000 --- a/lib/puppet/type/l2_ovs_port.rb +++ /dev/null @@ -1,62 +0,0 @@ -Puppet::Type.newtype(:l2_ovs_port) do - @doc = "Manage a Open vSwitch port" - desc @doc - - ensurable - - newparam(:interface) do - isnamevar - desc "The interface to attach to the bridge" - # - validate do |val| - if not val =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ - fail("Invalid interface name: '#{val}'") - end - end - end - - newparam(:type) do - newvalues('', :system, :internal, :tap, :gre, :ipsec_gre, :capwap, :patch, :null) - defaultto('') - desc "Ovs port type" - end - - newparam(:skip_existing) do - defaultto(false) - desc "Allow to skip existing port" - end - - newparam(:bridge) do - desc "What bridge to use" - # - validate do |val| - if not val =~ /^[a-z][0-9a-z\.\-\_]*[0-9a-z]$/ - fail("Invalid bridge name: '#{val}'") - end - end - end - - newparam(:port_properties) do - defaultto([]) - desc "Array of port properties" - validate do |val| - if not (val.is_a?(Array) or val.is_a?(String)) # String need for array with one element. it's a puppet's feature - fail("port_properties must be an array (not #{val.class}).") - end - end - end - - newparam(:interface_properties) do - defaultto([]) - desc "Array of port interface properties" - validate do |val| - if not (val.is_a?(Array) or val.is_a?(String)) # String need for array with one element. it's a puppet's feature - fail("interface_properties must be an array (not #{val.class}).") - end - end - end - - autorequire(:l2_ovs_bridge) do - [self[:bridge]] - end -end diff --git a/lib/puppet/type/l2_patch.rb b/lib/puppet/type/l2_patch.rb new file mode 100644 index 0000000..b133bbb --- /dev/null +++ b/lib/puppet/type/l2_patch.rb @@ -0,0 +1,92 @@ +Puppet::Type.newtype(:l2_patch) do + @doc = "Manage a patchcords between two bridges" + desc @doc + + ensurable + + newparam(:name) # workarround for following error: + # Error 400 on SERVER: Could not render to pson: undefined method `merge' for []:Array + # http://projects.puppetlabs.com/issues/5220 + + newparam(:use_ovs) do + desc "Whether using OVS comandline tools" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + end + + newproperty(:bridges, :array_matching => :all) do + desc "Array of bridges that will be connected" + newvalues(/^[a-z][0-9a-z\-\_]*[0-9a-z]$/) + end + + newproperty(:jacks, :array_matching => :all) do + desc "Patchcord jacks. Read-only. for debug purpose." + end + + newproperty(:cross) do + desc "Cross-system patch. Read-only. for debug purpose." + end + + newproperty(:mtu) do + desc "The Maximum Transmission Unit size to use for the interface" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent # MTU value should be undefined by default, because some network resources (bridges, subinterfaces) + validate do |value| # inherits it from a parent interface + # Intel 82598 & 82599 chips support MTUs up to 16110; is there any + # hardware in the wild that supports larger frames? + # + # It appears loopback devices routinely have large MTU values; Eg. 65536 + # + # Frames small than 64bytes are discarded as runts. Smallest valid MTU + # is 42 with a 802.1q header and 46 without. + min_mtu = 42 + max_mtu = 65536 + if ! (value.to_s == 'absent' or (min_mtu .. max_mtu).include?(value.to_i)) + raise ArgumentError, "'#{value}' is not a valid mtu (must be a positive integer in range (#{min_mtu} .. #{max_mtu})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + autorequire(:l2_bridge) do + self[:bridges] + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l2_port.rb b/lib/puppet/type/l2_port.rb new file mode 100644 index 0000000..58f1aed --- /dev/null +++ b/lib/puppet/type/l2_port.rb @@ -0,0 +1,226 @@ +# type for managing runtime NIC states. + +require 'yaml' +require 'puppetx/l23_utils' + +Puppet::Type.newtype(:l2_port) do + @doc = "Manage a network port abctraction." + desc @doc + + ensurable + + newparam(:interface) do + isnamevar + desc "The interface name" + # + validate do |val| + if not val =~ /^[a-z_][\w\.\-]*[0-9a-z]$/ + fail("Invalid interface name: '#{val}'") + end + end + end + + newparam(:use_ovs) do + desc "Whether using OVS comandline tools" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + end + + #todo(sv): move to provider_specific hash + newproperty(:type) do + newvalues(:system, :internal, :tap, :gre, :ipsec_gre, :capwap, :patch, :null, :undef, :nil, :none) + aliasvalue(:none, :internal) + aliasvalue(:undef, :internal) + aliasvalue(:nil, :internal) + aliasvalue(:null, :internal) + #defaultto :internal + desc "Port type (for openvswitch only)" + end + + newproperty(:port_type) do + desc "Internal read-only property" + validate do |value| + raise ArgumentError, "You shouldn't change port_type -- it's a internal RO property!" + end + end + + newproperty(:onboot) do + desc "Whether to bring the interface up" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + + def insync?(value) + value.to_s.downcase == should.to_s.downcase + end + end + + newproperty(:bridge) do + desc "What bridge to use" + # + validate do |val| + if not val =~ /^[a-z][0-9a-z\-\_]*[0-9a-z]$/ + fail("Invalid bridge name: '#{val}'") + end + end + munge do |val| + if ['nil', 'undef', 'none', 'absent', ''].include?(val.to_s) + :absent + else + val + end + end + end + + newparam(:port_properties, :array_matching => :all) do + desc "Array of port properties" + defaultto [] + end + + newparam(:interface_properties, :array_matching => :all) do + desc "Array of port interface properties" + defaultto [] + end + + newproperty(:vlan_dev) do + desc "802.1q vlan base device" + end + + newproperty(:vlan_id) do + desc "802.1q vlan ID" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent + validate do |value| + min_vid = 1 + max_vid = 4094 + if ! (value.to_s == 'absent' or (min_vid .. max_vid).include?(value.to_i)) + raise ArgumentError, "'#{value}' is not a valid 802.1q NALN_ID (must be a integer value in range (#{min_vid} .. #{max_vid})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + + end + + newproperty(:vlan_mode) do + desc "802.1q vlan interface naming model" + end + + newproperty(:bond_master) do + desc "Bond name, if interface is a part of bond" + newvalues(/^[a-z][\w\-]*$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + end + + newparam(:trunks, :array_matching => :all) do + desc "Array of trunks id, for configure patch's ends as ports in trunk mode" + end + + newproperty(:mtu) do + desc "The Maximum Transmission Unit size to use for the interface" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent # MTU value should be undefined by default, because some network resources (bridges, subinterfaces) + validate do |value| # inherits it from a parent interface + # Intel 82598 & 82599 chips support MTUs up to 16110; is there any + # hardware in the wild that supports larger frames? + # + # It appears loopback devices routinely have large MTU values; Eg. 65536 + # + # Frames small than 64bytes are discarded as runts. Smallest valid MTU + # is 42 with a 802.1q header and 46 without. + min_mtu = 42 + max_mtu = 65536 + if ! (value.to_s == 'absent' or (min_mtu .. max_mtu).include?(value.to_i)) + raise ArgumentError, "'#{value}' is not a valid mtu (must be a positive integer in range (#{min_mtu} .. #{max_mtu})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + + end + + newproperty(:ethtool) do + desc "Hash of ethtool properties" + #defaultto {} + # provider-specific hash, validating only by type. + validate do |val| + if ! val.is_a? Hash + fail("Ethtool should be a hash!") + end + end + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + new_should = {} + (value.keys + should.keys).uniq.map{|k| new_should[k] = {}} + # debug("\nV: #{value.to_yaml}\n") + # debug("\nS: #{should.to_yaml}\n") + # debug("\nN: #{new_should.to_yaml}\n") + new_should.keys.map{|key| new_should[key] = value[key].merge should[key] } + #debug("\nZ: #{new_should.to_yaml}\n") + (L23network.reccursive_sanitize_hash(value) == L23network.reccursive_sanitize_hash(new_should)) + end + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + autorequire(:l2_bridge) do + [self[:bridge]] + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l3_if_downup.rb b/lib/puppet/type/l3_if_downup.rb index c0b4b6a..749d224 100644 --- a/lib/puppet/type/l3_if_downup.rb +++ b/lib/puppet/type/l3_if_downup.rb @@ -54,14 +54,36 @@ end newparam(:check_by_ping_timeout) do + defaultto(30) + end + + newparam(:wait_carrier_after_ifup) do + desc "Enable carrier waiting after interface up. Affect only phys.interfaces." + newvalues(true, false) + defaultto(true) + end + + newparam(:wait_carrier_after_ifup_timeout) do + desc "Timeout for carrier waiting after interface up." defaultto(120) + validate do |val| + if val.to_i() >= 0 + true + else + fail("Timeout must be a positive integer, not '#{val}'.") + end + end + munge do |val| + val.to_i() + end end def refresh - provider.restart + provider.restart() end - # autorequire(:l2_ovs_bridge) do + # autorequire(:l2_bridge) do # [self[:bridge]] # end end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l3_ifconfig.rb b/lib/puppet/type/l3_ifconfig.rb new file mode 100644 index 0000000..edf3330 --- /dev/null +++ b/lib/puppet/type/l3_ifconfig.rb @@ -0,0 +1,145 @@ +# type for managing runtime IP addresses and another L3 stuff. + +require 'yaml' + +Puppet::Type.newtype(:l3_ifconfig) do + @doc = "Manage a network port abctraction." + desc @doc + + ensurable + + newparam(:interface) do + isnamevar + desc "The interface name" + # + validate do |val| + if not val =~ /^[a-z_][0-9a-z\.\-\_]*[0-9a-z]$/ + fail("Invalid interface name: '#{val}'") + end + end + end + + newparam(:use_ovs) do + desc "Whether using OVS comandline tools" + newvalues(:true, :yes, :on, :false, :no, :off) + aliasvalue(:yes, :true) + aliasvalue(:on, :true) + aliasvalue(:no, :false) + aliasvalue(:off, :false) + defaultto :true + end + + newproperty(:port_type) do + desc "Internal read-only property" + validate do |value| + raise ArgumentError, "You shouldn't change port_type -- it's a internal RO property!" + end + end + + newproperty(:ipaddr, :array_matching => :all) do + desc "List of IP address for this interface" + newvalues(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\d{1,2}))?$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + validate do |val| + return true if [:dhcp, :none, :undef, :nil, :absent].include?(val.downcase.to_sym) + val.strip! + raise ArgumentError, "Invalid IP address in list: '#{val}'" if \ + not val.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\d{1,2}))?$/) \ + or not ($1.to_i >= 0 and $1.to_i <= 255) \ + or not ($2.to_i >= 0 and $2.to_i <= 255) \ + or not ($3.to_i >= 0 and $3.to_i <= 255) \ + or not ($4.to_i >= 0 and $4.to_i <= 255) \ + or not ($6.to_i >= 0 and $6.to_i <= 32) + end + def should_to_s(value) + value.inspect + end + def is_to_s(value) + value.inspect + end + end + + newproperty(:gateway) do + desc "Default gateway" + newvalues(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + validate do |val| + if val != :absent + val.strip! + raise ArgumentError, "Invalid gateway: '#{val}'" if \ + not val.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) \ + or not ($1.to_i >= 0 and $1.to_i <= 255) \ + or not ($2.to_i >= 0 and $2.to_i <= 255) \ + or not ($3.to_i >= 0 and $3.to_i <= 255) \ + or not ($4.to_i >= 0 and $4.to_i <= 255) + end + end + end + newproperty(:gateway_metric) do + desc "Default gateway metric" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + defaultto :absent + validate do |val| + min_metric = 0 + max_metric = 65535 + if ! (val.to_s == 'absent' or (min_metric .. max_metric).include?(val.to_i)) + raise ArgumentError, "'#{val}' is not a valid metric (must be a integer value in range (#{min_metric} .. #{max_metric})" + end + end + munge do |val| + if val == :absent + :absent + else + begin + val.to_i + rescue + :absent + end + end + end + end + + newproperty(:dhcp_hostname) do + desc "DHCP hostname" + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + autorequire(:l2_port) do + [self[:interface]] + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppet/type/l3_route.rb b/lib/puppet/type/l3_route.rb new file mode 100644 index 0000000..6138928 --- /dev/null +++ b/lib/puppet/type/l3_route.rb @@ -0,0 +1,108 @@ +# type for managing routes in runtime. + +require 'yaml' + +Puppet::Type.newtype(:l3_route) do + @doc = "Manage a network routings." + desc @doc + + ensurable + + newparam(:name) # workarround for following error: + # Error 400 on SERVER: Could not render to pson: undefined method `merge' for []:Array + # http://projects.puppetlabs.com/issues/5220 + + + newproperty(:destination) do + desc "Destination network" + validate do |val| + val.strip! + if val.to_s.downcase != 'default' + raise ArgumentError, "Invalid IP address: '#{val}'" if \ + not val.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\d{1,2}))?$/) \ + or not ($1.to_i >= 0 and $1.to_i <= 255) \ + or not ($2.to_i >= 0 and $2.to_i <= 255) \ + or not ($3.to_i >= 0 and $3.to_i <= 255) \ + or not ($4.to_i >= 0 and $4.to_i <= 255) \ + or not ($6.to_i >= 0 and $6.to_i <= 32) + end + end + + end + + newproperty(:gateway) do + desc "Gateway" + newvalues(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) + validate do |val| + # gateway can't be "absent" by design + val.strip! + raise ArgumentError, "Invalid gateway: '#{val}'" if \ + not val.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) \ + or not ($1.to_i >= 0 and $1.to_i <= 255) \ + or not ($2.to_i >= 0 and $2.to_i <= 255) \ + or not ($3.to_i >= 0 and $3.to_i <= 255) \ + or not ($4.to_i >= 0 and $4.to_i <= 255) + end + end + + newproperty(:metric) do + desc "Route metric" + newvalues(/^\d+$/, :absent, :none, :undef, :nil) + aliasvalue(:none, :absent) + aliasvalue(:undef, :absent) + aliasvalue(:nil, :absent) + aliasvalue(0, :absent) + defaultto :absent + validate do |val| + min_metric = 0 + max_metric = 65535 + if ! (val.to_s == 'absent' or (min_metric .. max_metric).include?(val.to_i)) + raise ArgumentError, "'#{val}' is not a valid metric (must be a integer value in range (#{min_metric} .. #{max_metric})" + end + end + munge do |val| + ((val == :absent) ? :absent : val.to_i) + end + end + + newproperty(:interface) do + newvalues(/^[a-z_][0-9a-z\.\-\_]*[0-9a-z]$/) + desc "The interface name" + end + + newproperty(:vendor_specific) do + desc "Hash of vendor specific properties" + #defaultto {} # no default value should be!!! + # provider-specific properties, can be validating only by provider. + validate do |val| + if ! val.is_a? Hash + fail("Vendor_specific should be a hash!") + end + end + + munge do |value| + (value.empty? ? nil : L23network.reccursive_sanitize_hash(value)) + end + + def should_to_s(value) + "\n#{value.to_yaml}\n" + end + + def is_to_s(value) + "\n#{value.to_yaml}\n" + end + + def insync?(value) + should_to_s(value) == should_to_s(should) + end + end + + newproperty(:type) do + desc "RO field, type of route" + end + + autorequire(:l2_port) do + [self[:interface]] + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppetx/l23_ethtool_name_commands_mapping.rb b/lib/puppetx/l23_ethtool_name_commands_mapping.rb new file mode 100644 index 0000000..1376823 --- /dev/null +++ b/lib/puppetx/l23_ethtool_name_commands_mapping.rb @@ -0,0 +1,42 @@ +module L23network + + def self.ethtool_name_commands_mapping() + { + 'offload' => { + '__section_key_set__' => '-K', + '__section_key_get__' => '-k', + 'rx-checksumming' => 'rx', + 'tx-checksumming' => 'tx', + 'scatter-gather' => 'sg', + 'tcp-segmentation-offload' => 'tso', + 'udp-fragmentation-offload' => 'ufo', + 'generic-segmentation-offload' => 'gso', + 'generic-receive-offload' => 'gro', + 'large-receive-offload' => 'lro', + 'rx-vlan-offload' => 'rxvlan', + 'tx-vlan-offload' => 'txvlan', + 'ntuple-filters' => 'ntuple', + 'receive-hashing' => 'rxhash', + 'rx-fcs' => 'rx-fcs', + 'rx-all' => 'rx-all', + 'highdma' => 'highdma', + 'rx-vlan-filter' => 'rx-vlan-filter', + 'fcoe-mtu' => 'fcoe-mtu', + 'l2-fwd-offload' => 'l2-fwd-offload', + 'loopback' => 'loopback', + 'tx-nocache-copy' => 'tx-nocache-copy', + 'tx-gso-robust' => 'tx-gso-robust', + 'tx-fcoe-segmentation' => 'tx-fcoe-segmentation', + 'tx-gre-segmentation' => 'tx-gre-segmentation', + 'tx-ipip-segmentation' => 'tx-ipip-segmentation', + 'tx-sit-segmentation' => 'tx-sit-segmentation', + 'tx-udp_tnl-segmentation' => 'tx-udp_tnl-segmentation', + 'tx-mpls-segmentation' => 'tx-mpls-segmentation', + 'tx-vlan-stag-hw-insert' => 'tx-vlan-stag-hw-insert', + 'rx-vlan-stag-hw-parse' => 'rx-vlan-stag-hw-parse', + 'rx-vlan-stag-filter' => 'rx-vlan-stag-filter', + } + } + end + +end \ No newline at end of file diff --git a/lib/puppetx/l23_hash_tools.rb b/lib/puppetx/l23_hash_tools.rb new file mode 100644 index 0000000..886117a --- /dev/null +++ b/lib/puppetx/l23_hash_tools.rb @@ -0,0 +1,66 @@ +module L23network + + def self.process_array4keys(aa) + rv = [] + aa.each do |v| + if v.is_a? Hash + rv.insert(-1, self.sanitize_keys_in_hash(v)) + elsif v.is_a? Array + rv.insert(-1, self.process_array4keys(v)) + else + rv.insert(-1, v) + end + end + return rv + end + def self.sanitize_keys_in_hash(hh) + rv = {} + hh.each do |k, v| + if v.is_a? Hash + rv[k.to_sym] = self.sanitize_keys_in_hash(v) + elsif v.is_a? Array + rv[k.to_sym] = self.process_array4keys(v) + else + rv[k.to_sym] = v + end + end + return rv + end + + + def self.process_array4bool(aa) + rv = [] + aa.each do |v| + if v.is_a? Hash + rv.insert(-1, self.sanitize_bool_in_hash(v)) + elsif v.is_a? Array + rv.insert(-1, self.process_array4bool(v)) + else + rv.insert(-1, v) + end + end + return rv + end + def self.sanitize_bool_in_hash(hh) + rv = {} + hh.each do |k, v| + if (v.is_a? String or v.is_a? Symbol) + rv[k] = case v.upcase() + when 'TRUE', :TRUE then true + when 'FALSE', :FALSE then false + when 'NONE', :NONE, 'NULL', :NULL, 'NIL', :NIL, 'NILL', :NILL then nil + else v + end + elsif v.is_a? Hash + rv[k] = self.sanitize_bool_in_hash(v) + elsif v.is_a? Array + rv[k] = self.process_array4bool(v) + else + rv[k] = v + end + end + return rv + end + +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppetx/l23_network_scheme.rb b/lib/puppetx/l23_network_scheme.rb new file mode 100644 index 0000000..37b8e69 --- /dev/null +++ b/lib/puppetx/l23_network_scheme.rb @@ -0,0 +1,12 @@ +module L23network + class Scheme + def self.set_config(h, v) + @network_scheme_hash ||= {} + @network_scheme_hash[h.to_sym] = v + end + def self.get_config(h) + @network_scheme_hash[h.to_sym] + end + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/lib/puppetx/l23_utils.rb b/lib/puppetx/l23_utils.rb new file mode 100644 index 0000000..c8785e0 --- /dev/null +++ b/lib/puppetx/l23_utils.rb @@ -0,0 +1,105 @@ +module L23network + def self.reccursive_sanitize_hash(data) + if data.is_a? Hash + new_data = {} + data.each do |key, value| + new_data.store(reccursive_sanitize_hash(key), reccursive_sanitize_hash(value)) + end + new_data + elsif data.is_a? Array + data.map do |element| + reccursive_sanitize_hash(element) + end + elsif ['true', 'on', 'yes'].include? data.to_s.downcase + true + elsif ['false', 'off', 'no'].include? data.to_s.downcase + false + elsif data.nil? + nil + else + data.to_s + end + end + + def self.get_patch_name(bridges) + # bridges should be an array of two string + "patch__#{bridges.map{|s| s.to_s}.sort.join('--')}" + end + + def self.ovs_jack_name_len + 13 + end + + def self.get_ovs_jack_name(bridge) + # bridges should be an array of two string + tail = bridge[0..ovs_jack_name_len-1] + "p_#{tail}" + end + + def self.lnx_jack_name_len + 11 + end + + def self.get_lnx_jack_name(bridge, num=0) + # bridges should be an array of two string + tail = bridge[0..lnx_jack_name_len-1] + "p_#{tail}-#{num}" + end + + def self.get_pair_of_jack_names(bridges) + if bridges.is_a? String + j1 = get_lnx_jack_name(bridges,0) + j2 = get_lnx_jack_name(bridges,1) + elsif bridges.is_a? Array and bridges.length==1 + j1 = get_lnx_jack_name(bridges[0],0) + j2 = get_lnx_jack_name(bridges[0],1) + else + j1 = get_lnx_jack_name(bridges[0],0) + j2 = get_lnx_jack_name(bridges[1],1) + end + return [j1, j2] + end + +# def self.reccursive_merge_hash(a,b) +# rv = {} + +# a.keys.each do |key| +# if data.is_a? Hash +# new_data = {} +# data.each do |key, value| +# new_data.store(reccursive_sanitize_hash(key), reccursive_sanitize_hash(value)) +# end +# new_data +# else +# data.to_s +# end +# end + +# if data.is_a? Hash +# new_data = {} +# data.each do |key, value| +# new_data.store(reccursive_sanitize_hash(key), reccursive_sanitize_hash(value)) +# end +# new_data +# elsif data.is_a? Array +# data.map do |element| +# reccursive_sanitize_hash(element) +# end +# elsif ['true', 'on', 'yes'].include? data.to_s.downcase +# true +# elsif ['false', 'off', 'no'].include? data.to_s.downcase +# false +# elsif data.nil? +# nil +# else +# data.to_s +# end + +# return rv +# end + + def self.get_route_resource_name(dest, metric) + (metric.to_i > 0 ? "#{dest},metric:#{metric}" : rv = "#{dest}") + end +end +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/manifests/examples/run_network_scheme.pp b/manifests/examples/run_network_scheme.pp new file mode 100644 index 0000000..cc27916 --- /dev/null +++ b/manifests/examples/run_network_scheme.pp @@ -0,0 +1,12 @@ +# xxx +class l23network::examples::run_network_scheme ( + $settings_yaml +){ + + class {'l23network': } + + $config = parseyaml($settings_yaml) + prepare_network_config($config['network_scheme']) + $sdn = generate_network_config() +} +### \ No newline at end of file diff --git a/manifests/hosts_file.pp b/manifests/hosts_file.pp new file mode 100644 index 0000000..fc8a9ad --- /dev/null +++ b/manifests/hosts_file.pp @@ -0,0 +1,18 @@ +# manage /etc/hosts +# +class l23network::hosts_file ( + $nodes, + $hosts_file = '/etc/hosts' +) { + + #Move original hosts file + + $host_resources = nodes_to_hosts($nodes) + + Host { + ensure => present, + target => $hosts_file + } + + create_resources(host, $host_resources) +} diff --git a/manifests/init.pp b/manifests/init.pp index 95b60ca..6a206e0 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -4,13 +4,64 @@ # Requirements, packages and services. # class l23network ( - $use_ovs = true, - $use_lnxbr = true, + $use_lnx = true, + $use_ovs = false, + $install_ovs = $use_ovs, + $install_brtool = $use_lnx, + $install_ethtool = $use_lnx, + $install_bondtool = $use_lnx, + $install_vlantool = $use_lnx, + $ovs_modname = undef, + $ovs_datapath_package_name = undef, + $ovs_common_package_name = undef, ){ - class {'l23network::l2': - use_ovs => $use_ovs, - use_lnxbr => $use_lnxbr, + + include stdlib + include ::l23network::params + + class { 'l23network::l2': + use_ovs => $use_ovs, + use_lnx => $use_lnx, + install_ovs => $install_ovs, + install_brtool => $install_brtool, + install_ethtool => $install_ethtool, + install_bondtool => $install_bondtool, + install_vlantool => $install_vlantool, + ovs_modname => $ovs_modname, + ovs_datapath_package_name => $ovs_datapath_package_name, + ovs_common_package_name => $ovs_common_package_name, } + + if $::l23network::params::interfaces_file { + if ! defined(File["${::l23network::params::interfaces_file}"]) { + file {"${::l23network::params::interfaces_file}": + ensure => present, + content => template('l23network/interfaces.erb'), + } + } + File<| title == "${::l23network::params::interfaces_file}" |> -> File<| title == "${::l23network::params::interfaces_dir}" |> + } + + if ! defined(File["${::l23network::params::interfaces_dir}"]) { + file {"${::l23network::params::interfaces_dir}": + ensure => directory, + owner => 'root', + mode => '0755', + } -> Anchor['l23network::init'] + } + Class['l23network::l2'] -> File<| title == "${::l23network::params::interfaces_dir}" |> + Class['l23network::l2'] -> File<| title == "${::l23network::params::interfaces_file}" |> + + # Centos interface up-n-down scripts + if $::osfamily =~ /(?i)redhat/ { + class{'::l23network::l2::centos_upndown_scripts': } -> Anchor['l23network::init'] + Anchor <| title == 'l23network::l2::centos_upndown_scripts' |> -> Anchor['l23network::init'] + } + + + Anchor['l23network::l2::init'] -> Anchor['l23network::init'] + anchor { 'l23network::init': } + } # -### \ No newline at end of file +### diff --git a/manifests/l2.pp b/manifests/l2.pp index 86c0c7d..702daf2 100644 --- a/manifests/l2.pp +++ b/manifests/l2.pp @@ -4,51 +4,94 @@ # Requirements, packages and services. # class l23network::l2 ( - $use_ovs = true, - $use_lnxbr = true, + $use_lnx = true, + $use_ovs = false, + $install_ovs = $use_ovs, + $install_brtool = $use_lnx, + $install_ethtool = $use_lnx, + $install_bondtool = $use_lnx, + $install_vlantool = $use_lnx, + $ovs_modname = $::l23network::params::ovs_kern_module_name, + $ovs_datapath_package_name = $::l23network::params::ovs_datapath_package_name, + $ovs_common_package_name = $::l23network::params::ovs_common_package_name, ){ + include stdlib include ::l23network::params if $use_ovs { - #include ::l23network::l2::use_ovs - package {$::l23network::params::ovs_packages: - ensure => present, - } ~> + $ovs_mod_ensure = present + if $install_ovs { + if $ovs_datapath_package_name { + package { 'openvswitch-datapath': + name => $ovs_datapath_package_name + } + Package['openvswitch-datapath'] -> Service['openvswitch-service'] + } + if $ovs_common_package_name { + package { 'openvswitch-common': + name => $ovs_common_package_name + } + Package['openvswitch-common'] ~> Service['openvswitch-service'] + } + + Package<| title=='openvswitch-datapath' |> -> Package<| title=='openvswitch-common' |> + } + service {'openvswitch-service': - ensure => running, + ensure => 'running', name => $::l23network::params::ovs_service_name, enable => true, hasstatus => true, - status => $::l23network::params::ovs_status_cmd, } + Service['openvswitch-service'] -> Anchor['l23network::l2::init'] + + } else { + $ovs_mod_ensure = absent } - if $::osfamily =~ /(?i)debian/ { - if !defined(Package["$l23network::params::lnx_bond_tools"]) { - package {"$l23network::params::lnx_bond_tools": - ensure => installed - } - } + @k_mod{$ovs_modname: + ensure => $ovs_mod_ensure } - if !defined(Package["$l23network::params::lnx_vlan_tools"]) { - package {"$l23network::params::lnx_vlan_tools": - ensure => installed - } + if $use_lnx { + $mod_8021q_ensure = present + $mod_bonding_ensure = present + $mod_bridge_ensure = present + } else { + $mod_8021q_ensure = absent + $mod_bonding_ensure = absent + $mod_bridge_ensure = absent } - if !defined(Package["$l23network::params::lnx_ethernet_tools"]) { - package {"$l23network::params::lnx_ethernet_tools": - ensure => installed - } + if $install_vlantool and $::l23network::params::lnx_vlan_tools { + ensure_packages($::l23network::params::lnx_vlan_tools) + Package[$::l23network::params::lnx_vlan_tools] -> Anchor['l23network::l2::init'] + } + @k_mod{'8021q': + ensure => $mod_8021q_ensure } - if $use_ovs { - if $::osfamily =~ /(?i)debian/ { - Package["$l23network::params::lnx_bond_tools"] -> Service['openvswitch-service'] - } - Package["$l23network::params::lnx_vlan_tools"] -> Service['openvswitch-service'] - Package["$l23network::params::lnx_ethernet_tools"] -> Service['openvswitch-service'] + if $install_bondtool and $::l23network::params::lnx_bond_tools { + ensure_packages($::l23network::params::lnx_bond_tools) + Package[$::l23network::params::lnx_bond_tools] -> Anchor['l23network::l2::init'] + } + @k_mod{'bonding': + ensure => $mod_bonding_ensure + } + + if $install_brtool and $::l23network::params::lnx_bridge_tools { + ensure_packages($::l23network::params::lnx_bridge_tools) + #Package[$::l23network::params::lnx_bridge_tools] -> Anchor['l23network::l2::init'] + } + @k_mod{'bridge': + ensure => $mod_bridge_ensure } + if $install_ethtool and $::l23network::params::lnx_ethernet_tools { + ensure_packages($::l23network::params::lnx_ethernet_tools) + Package[$::l23network::params::lnx_ethernet_tools] -> Anchor['l23network::l2::init'] + } + + anchor { 'l23network::l2::init': } + } diff --git a/manifests/l2/bond.pp b/manifests/l2/bond.pp index fe2f9a7..6bac65b 100644 --- a/manifests/l2/bond.pp +++ b/manifests/l2/bond.pp @@ -10,33 +10,172 @@ # [*bridge*] # Bridge that will contain this bond. # -# [*ports*] -# List of ports in this bond. -# -# [*skip_existing*] -# If this bond already exists it will be ignored without any errors. -# Must be true or false. -# +# [*interfaces*] +# List of interfaces in this bond. + define l23network::l2::bond ( - $bridge, - $ports, - $bond = $name, - $properties = [], - $ensure = present, - $skip_existing = false, + $ensure = present, + $bond = $name, + $use_ovs = $::l23network::use_ovs, + $interfaces = undef, + $bridge = undef, + $mtu = undef, + $onboot = undef, +# $ethtool = undef, + $bond_properties = undef, # bond configuration options + $interface_properties = undef, # configuration options for included interfaces (mtu, ethtool, etc...) + $vendor_specific = undef, + $monolith_bond_providers = undef, + $provider = undef, + # deprecated parameters, in the future ones will be moved to the vendor_specific hash +# $skip_existing = undef, ) { - if ! $::l23network::l2::use_ovs { - fail('You must enable Open vSwitch by setting the l23network::l2::use_ovs to true.') - } - - if ! defined (L2_ovs_bond["$bond"]) { - l2_ovs_bond { "$bond" : - ports => $ports, - ensure => $ensure, - bridge => $bridge, - properties => $properties, - skip_existing => $skip_existing, + include ::stdlib + include ::l23network::params + + $actual_monolith_bond_providers = $monolith_bond_providers ? { + undef => $l23network::params::monolith_bond_providers, + default => $monolith_bond_providers, + } + + $bond_modes = [ + 'balance-rr', + 'active-backup', + 'balance-xor', + 'broadcast', + '802.3ad', + 'balance-tlb', + 'balance-alb' + ] + + $lacp_rates = [ + 'slow', + 'fast' + ] + + $xmit_hash_policies = [ + 'layer2', + 'layer2+3', + 'layer3+4', + 'encap2+3', + 'encap3+4' + ] + + # calculate string representation for bond_mode + if ! $bond_properties[mode] { + # default value by design https://www.kernel.org/doc/Documentation/networking/bonding.txt + $bond_mode = $bond_modes[0] + } elsif is_integer($bond_properties[mode]) and $bond_properties[mode] < size($bond_modes) { + $bond_mode = $bond_modes[$bond_properties[mode]] + } else { + $bond_mode = $bond_properties[mode] + } + + # calculate string representation for lacp_rate + if $bond_mode == '802.3ad' { + if ! $bond_properties[lacp_rate] { + # default value by design https://www.kernel.org/doc/Documentation/networking/bonding.txt + $lacp_rate = $lacp_rates[0] + } elsif is_integer($bond_properties[lacp_rate]) and $bond_properties[lacp_rate] < size($lacp_rates) { + $lacp_rate = $lacp_rates[$bond_properties[lacp_rate]] + } else { + $lacp_rate = $bond_properties[lacp_rate] + } + } + + # calculate default miimon + if is_integer($bond_properties[miimon]) and $bond_properties[miimon] >= 0 { + $miimon = $bond_properties[miimon] + } else { + # recommended default value https://www.kernel.org/doc/Documentation/networking/bonding.txt + $miimon = 100 + } + + # calculate string representation for xmit_hash_policy + if ( $bond_mode == '802.3ad' or $bond_mode == 'balance-xor' or $bond_mode == 'balance-tlb') { + if ! $bond_properties[xmit_hash_policy] { + # default value by design https://www.kernel.org/doc/Documentation/networking/bonding.txt + $xmit_hash_policy = $xmit_hash_policies[0] + } else { + $xmit_hash_policy = $bond_properties[xmit_hash_policy] + } + } + + # default bond properties + $default_bond_properties = { + mode => $bond_mode, + miimon => $miimon, + lacp_rate => $lacp_rate, + xmit_hash_policy => $xmit_hash_policy + } + + $real_bond_properties = merge($bond_properties, $default_bond_properties) + + if $interfaces { + validate_array($interfaces) + } + + # Use $monolith_bond_providers list for prevent creating ports for monolith bonds + $actual_provider_for_bond_interface = $provider ? { + undef => default_provider_for('L2_port'), + default => $provider + } + $eee = default_provider_for('L2_port') + + if ! member($actual_monolith_bond_providers, $actual_provider_for_bond_interface) { + l23network::l2::bond_interface{ $interfaces: + bond => $bond, + mtu => $mtu, + interface_properties => $interface_properties, + ensure => $ensure, + provider => $actual_provider_for_bond_interface + } + } + + if ! defined(L2_bond[$bond]) { + if $provider { + $config_provider = "${provider}_${::l23_os}" + } else { + $config_provider = undef + } + + if ! defined(L23_stored_config[$bond]) { + l23_stored_config { $bond: } + } + L23_stored_config <| title == $bond |> { + ensure => $ensure, + if_type => 'bond', + bridge => $bridge, + mtu => $mtu, + onboot => $onboot, + bond_mode => $real_bond_properties[mode], + bond_master => undef, + bond_slaves => $interfaces, + bond_miimon => $real_bond_properties[miimon], + bond_lacp_rate => $real_bond_properties[lacp_rate], + bond_xmit_hash_policy => $real_bond_properties[xmit_hash_policy], + vendor_specific => $vendor_specific, + provider => $config_provider + } + + l2_bond { $bond : + ensure => $ensure, + bridge => $bridge, + use_ovs => $use_ovs, + onboot => $onboot, + slaves => $interfaces, + mtu => $mtu, + interface_properties => $interface_properties, + bond_properties => $real_bond_properties, + vendor_specific => $vendor_specific, + provider => $provider } - Service<| title == 'openvswitch-service' |> -> L2_ovs_bond["$bond"] + + # this need for creating L2_port resource by ifup, if it allowed by OS + L23_stored_config[$bond] -> L2_bond[$bond] + + K_mod<||> -> L2_bond<||> + } + } diff --git a/manifests/l2/bond_interface.pp b/manifests/l2/bond_interface.pp new file mode 100644 index 0000000..c135eba --- /dev/null +++ b/manifests/l2/bond_interface.pp @@ -0,0 +1,40 @@ +# This technological resource should be used for configure bond slaves only from +# l23network::l2::bond resource. No self-contained purposes given. +define l23network::l2::bond_interface ( + $bond, + $use_ovs = $::l23network::use_ovs, + $ensure = present, + $mtu = undef, + $interface_properties = {}, + $provider = undef, +) { + include ::l23network::params + include ::stdlib + + if ! defined(L23network::L2::Port[$name]) { + $additional_properties = { + use_ovs => $use_ovs, + mtu => is_integer($interface_properties[mtu]) ? {false=>$mtu, default=>$interface_properties[mtu]}, + master => $bond, + slave => true, + provider => $provider + } + + create_resources(l23network::l2::port, { + "${name}" => merge($interface_properties, $additional_properties) + }) + } else { + L23network::L2::Port<| title == $name |> { + use_ovs => $use_ovs, + master => $bond, + slave => true + } + } + if $provider == 'ovs' { + # OVS can't create bond if slave port don't exists. + L2_port[$name] -> L2_bond[$bond] + } else { + L2_bond[$bond] -> L2_port[$name] + } +} +### \ No newline at end of file diff --git a/manifests/l2/bridge.pp b/manifests/l2/bridge.pp index 52fecf9..a007f98 100644 --- a/manifests/l2/bridge.pp +++ b/manifests/l2/bridge.pp @@ -7,29 +7,59 @@ # [*name*] # Bridge name. # -# [*skip_existing*] -# If this bridge already exists it will be ignored without any errors. -# Must be true or false. -# # [*external_ids*] # See open vSwitch documentation. # http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-vsctl.8 # define l23network::l2::bridge ( - $external_ids = '', - $ensure = present, - $skip_existing = false, + $ensure = present, + $use_ovs = $::l23network::use_ovs, + $mtu = undef, + $stp = undef, + $bpdu_forward = true, +# $bridge_id = undef, # will be implemented later + $external_ids = { 'bridge-id' => "${name}" }, + $vendor_specific = undef, + $provider = undef, ) { - if ! $::l23network::l2::use_ovs { - fail('You must enable Open vSwitch by setting the l23network::l2::use_ovs to true.') - } - if ! defined (L2_ovs_bridge[$name]) { - l2_ovs_bridge {$name: + include ::stdlib + include ::l23network::params + + if ! defined (L2_bridge[$name]) { + if $provider { + $config_provider = "${provider}_${::l23_os}" + } else { + $config_provider = undef + } + + if ! defined (L23_stored_config[$name]) { + l23_stored_config { $name: } + } + + L23_stored_config <| title == $name |> { ensure => $ensure, - external_ids => $external_ids, - skip_existing=> $skip_existing, + #bpdu_forward => $bpdu_forward, + if_type => 'bridge', + bridge_stp => $stp, + bridge_ports => ['none'], # this property will be fulled by l2_port + vendor_specific => $vendor_specific, + provider => $config_provider + } + + l2_bridge {$name: + ensure => $ensure, + use_ovs => $use_ovs, + external_ids => $external_ids, + stp => $stp, + #bpdu_forward => $bpdu_forward, + vendor_specific => $vendor_specific, + provider => $provider } - Service<| title == 'openvswitch-service' |> -> L2_ovs_bridge[$name] + + # this need for creating L2_bridge resource by ifup, if it allowed by OS + L23_stored_config[$name] -> L2_bridge[$name] + + K_mod<||> -> L2_bridge<||> } } diff --git a/manifests/l2/centos_upndown_scripts.pp b/manifests/l2/centos_upndown_scripts.pp index 376bcb6..9babb2f 100644 --- a/manifests/l2/centos_upndown_scripts.pp +++ b/manifests/l2/centos_upndown_scripts.pp @@ -4,15 +4,13 @@ ensure => present, owner => 'root', mode => '0755', - recurse => true, - content => template("l23network/centos_ifup-local.erb"), + content => template('l23network/centos_ifup-local.erb'), } -> file {'/sbin/ifdown-local': ensure => present, owner => 'root', mode => '0755', - recurse => true, - content => template("l23network/centos_ifdown-local.erb"), + content => template('l23network/centos_ifdown-local.erb'), } -> - anchor { 'l23network::l2::centos_upndown_scripts': } -} \ No newline at end of file + anchor { 'l23network::l2::centos_upndown_scripts': } +} diff --git a/manifests/l2/patch.pp b/manifests/l2/patch.pp new file mode 100644 index 0000000..cc21dc7 --- /dev/null +++ b/manifests/l2/patch.pp @@ -0,0 +1,69 @@ +# == Define: l23network::l2::patch +# +# Connect two open vSwitch bridges by virtual patch-cord. +# +# === Parameters +# [*bridges*] +# Bridges that will be connected. +# +# [*peers*] +# Patch port names for both bridges. must be array of two strings. +# +define l23network::l2::patch ( + $bridges, + $use_ovs = $::l23network::use_ovs, + $ensure = present, + $mtu = undef, + $vendor_specific = undef, + $provider = undef, +) { + + include ::stdlib + include ::l23network::params + + #$provider_1 = get_provider_for('L2_bridge', bridges[0]) # this didn't work, because parser functions + #$provider_2 = get_provider_for('L2_bridge', bridges[1]) # executed before resources prefetch + + # Architecture limitation. + # We can't create more one patch between same bridges. + $patch_name = get_patch_name($bridges) + $patch_jacks_names = get_pair_of_jack_names($bridges) + + if ! defined(L2_patch[$patch_name]) { + if $provider { + $config_provider = "${provider}_${::l23_os}" + } else { + $config_provider = undef + } + + if ! defined(L23_stored_config[$patch_jacks_names[0]]) { + # we use only one (last) patch jack name here and later, + # because a both jacks for patch + # creates by one command. This command stores in one config file. + l23_stored_config { $patch_jacks_names[0]: } + } + L23_stored_config <| title == $patch_jacks_names[0] |> { + ensure => $ensure, + if_type => 'ethernet', + bridge => $bridges, + jacks => $patch_jacks_names, + mtu => $mtu, + onboot => true, + vendor_specific => $vendor_specific, + provider => $config_provider + } + L23_stored_config[$patch_jacks_names[0]] -> L2_patch[$patch_name] + + l2_patch{ $patch_name : + ensure => $ensure, + bridges => $bridges, + use_ovs => $use_ovs, + mtu => $mtu, + vendor_specific => $vendor_specific, + provider => $provider + } + + K_mod<||> -> L2_patch<||> + } +} +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/manifests/l2/port.pp b/manifests/l2/port.pp index aa90c7d..085510a 100644 --- a/manifests/l2/port.pp +++ b/manifests/l2/port.pp @@ -17,32 +17,129 @@ # the port with default behavior. # (see http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-vsctl.8) # +# [*vlan_id*] +# Specify 802.1q tag for result bond. If need. +# +# [*trunks*] +# Specify array of 802.1q tags if need configure bond in trunk mode. +# Define trunks => [0] if you need pass only untagged traffic. +# # [*skip_existing*] # If this port already exists it will be ignored without any errors. # Must be true or false. # define l23network::l2::port ( - $bridge, - $port = $name, - $type = '', - $port_properties = [], - $interface_properties = [], - $ensure = present, - $skip_existing = false, + $ensure = present, + $use_ovs = $::l23network::use_ovs, + $port = $name, + $bridge = undef, + $onboot = undef, + $vlan_id = undef, # actually only for OVS workflow + $vlan_dev = undef, + $mtu = undef, + $ethtool = undef, + $master = undef, # used for bonds automatically + $slave = undef, # used for bonds automatically +# $type = undef, # was '', + $vendor_specific = undef, + $provider = undef, + # deprecated parameters, in the future ones will be moved to the vendor_specific hash +# $skip_existing = undef, +# $port_properties = [], +# $interface_properties = [], +# $trunks = [], ) { - if ! $::l23network::l2::use_ovs { - fail('You must enable Open vSwitch by setting the l23network::l2::use_ovs to true.') + + include ::stdlib + include ::l23network::params + + # Detect VLAN mode configuration + case $port { + /^vlan(\d+)/: { + $port_name = $port + $port_vlan_mode = 'vlan' + if $vlan_id { + $port_vlan_id = $vlan_id + } else { + $port_vlan_id = $1 + } + if $vlan_dev { + $port_vlan_dev = $vlan_dev + } else { + if $provider != 'ovs' { + fail("Can't configure vlan interface ${port} without definition vlandev=>ethXX.") + } + } + } + /^([\w\-]+\d+)\.(\d+)/: { + $port_vlan_mode = 'eth' + $port_vlan_id = $2 + $port_vlan_dev = $1 + $port_name = "${1}.${2}" + } + default: { + $port_vlan_mode = undef + $port_vlan_id = undef + $port_vlan_dev = undef + $port_name = $port + } } - - if ! defined (L2_ovs_port[$port]) { - l2_ovs_port { $port : - ensure => $ensure, - bridge => $bridge, - type => $type, - port_properties => $port_properties, - interface_properties => $interface_properties, - skip_existing => $skip_existing, + + # # implicitly create bridge, if it given and not exists + # if $bridge { + # if !defined(L2_bridge[$bridge]) { + # l2_bridge { $bridge: } + # } + # # or do this from autorequire ?????? + # L2_bridge[$bridge] -> L2_port[$port_name] + # } + + if ! defined(L2_port[$port_name]) { + if $provider { + $config_provider = "${provider}_${::l23_os}" + } else { + $config_provider = undef + } + + if ! defined(L23_stored_config[$port_name]) { + l23_stored_config { $port_name: } + } + L23_stored_config <| title == $port_name |> { + ensure => $ensure, + if_type => 'ethernet', + bridge => $bridge, + vlan_id => $port_vlan_id, + vlan_dev => $port_vlan_dev, + vlan_mode => $port_vlan_mode, + bond_master => $master, + mtu => $mtu, + onboot => $onboot, + ethtool => $ethtool, + vendor_specific => $vendor_specific, + provider => $config_provider + } + + l2_port { $port_name : + ensure => $ensure, + use_ovs => $use_ovs, + bridge => $bridge, + vlan_id => $port_vlan_id, + vlan_dev => $port_vlan_dev, + vlan_mode => $port_vlan_mode, + bond_master => $master, + mtu => $mtu, + onboot => $onboot, + #type => $type, + #trunks => $trunks, + ethtool => $ethtool, + vendor_specific => $vendor_specific, + provider => $provider } - Service<| title == 'openvswitch-service' |> -> L2_ovs_port[$port] + + # this need for creating L2_port resource by ifup, if it allowed by OS + L23_stored_config[$port_name] -> L2_port[$port_name] + + K_mod<||> -> L2_port<||> } } +# vim: set ts=2 sw=2 et : \ No newline at end of file diff --git a/manifests/l3/create_br_iface.pp b/manifests/l3/create_br_iface.pp deleted file mode 100644 index 8b2d77e..0000000 --- a/manifests/l3/create_br_iface.pp +++ /dev/null @@ -1,123 +0,0 @@ -# == Define: l23network::l3::create_br_iface -# -# Create L2 ovs bridge, clean IPs from interface and add IP address to this -# bridge -# -# === Parameters -# -# [*bridge*] -# Bridge name -# -# [*interface*] -# Interface that will be added to the bridge. -# If you set the interface parameter as an array of interface names -# then Open vSwitch will create bond with given interfaces. -# In this case you must set ovs_bond_name and ovs_bond_properties parameters. -# -# [*ipaddr*] -# IP address for port in bridge. -# -# [*netmask*] -# Network mask. -# -# [*gateway*] -# You can specify default gateway IP address, or 'save' for save default route -# if it lies through this interface now. -# -# [*dns_nameservers*] -# Dns nameservers to use -# -# [*dns_domain*] -# Describe DNS domain -# -# [*dns_search*] -# DNS domain to search for -# -# [*save_default_gateway*] -# If current network configuration contains a gateway parameter -# this option will try to save it. -# DEPRECATED!!! use gateway=>'save' -# -define l23network::l3::create_br_iface ( - $interface, - $ipaddr, - $bridge = $name, - $netmask = '255.255.255.0', - $gateway = undef, - $se = true, - $external_ids = '', - $dns_nameservers = undef, - $dns_domain = undef, - $dns_search = undef, - $save_default_gateway = false, - $lnx_interface_vlandev = undef, - $lnx_interface_bond_mode = undef, - $lnx_interface_bond_miimon = 100, - $lnx_interface_bond_lacp_rate = 1, - $ovs_bond_name = 'bond0', - $ovs_bond_properties = [], - $interface_order_prefix = false, -){ - if ! $::l23network::l2::use_ovs { - fail('You must enable Open vSwitch by setting the l23network::l2::use_ovs to true.') - } - - if ! $external_ids { - $ext_ids = "bridge-id=${bridge}" - } - # - if $gateway { - $gateway_ip_address_for_newly_created_interface = $gateway - } elsif ($save_default_gateway or $gateway == 'save') and $::l3_default_route_interface == $interface { - $gateway_ip_address_for_newly_created_interface = 'save' - } else { - $gateway_ip_address_for_newly_created_interface = undef - } - # Build ovs bridge - l23network::l2::bridge {"$bridge": - skip_existing => $se, - external_ids => $ext_ids, - } - if is_array($interface) { - # Build an ovs bridge containing ovs bond with given interfaces - l23network::l2::bond {"$ovs_bond_name": - bridge => $bridge, - ports => $interface, - properties => $ovs_bond_properties, - skip_existing => $se, - require => L23network::L2::Bridge["$bridge"] - } -> - l23network::l3::ifconfig {$interface: # no quotes here, $interface _may_be_ array!!! - ipaddr => 'none', - ifname_order_prefix => '0', - require => L23network::L2::Bond["$ovs_bond_name"], - before => L23network::L3::Ifconfig["$bridge"] - } - } else { - # Build an ovs bridge containing one interface - l23network::l2::port {$interface: - bridge => $bridge, - skip_existing => $se, - require => L23network::L2::Bridge["$bridge"] - } -> - l23network::l3::ifconfig {"$interface": # USE quotes!!!!! - ipaddr => 'none', - vlandev => $lnx_interface_vlandev, - bond_mode => $lnx_interface_bond_mode, - bond_miimon => $lnx_interface_bond_miimon, - bond_lacp_rate => $lnx_interface_bond_lacp_rate, - ifname_order_prefix => $interface_order_prefix, - require => L23network::L2::Port["$interface"], - before => L23network::L3::Ifconfig["$bridge"] - } - } - l23network::l3::ifconfig {"$bridge": - ipaddr => $ipaddr, - netmask => $netmask, - gateway => $gateway_ip_address_for_newly_created_interface, - dns_nameservers => $dns_nameservers, - dns_domain => $dns_domain, - dns_search => $dns_search, - ifname_order_prefix => 'ovs', - } -} diff --git a/manifests/l3/defaultroute.pp b/manifests/l3/defaultroute.pp index 6aaa1da..37782da 100644 --- a/manifests/l3/defaultroute.pp +++ b/manifests/l3/defaultroute.pp @@ -7,16 +7,31 @@ $gateway = $name, $metric = undef, ){ + + $exec_name = "Default route of ${name} metric ${metric}" + case $::osfamily { /(?i)debian/: { - fail("Unsupported for ${::osfamily}/${::operatingsystem}!!! Specify gateway directly for network interface.") + exec { $exec_name : + path => '/bin:/usr/bin:/sbin:/usr/sbin', + command => "ip route replace default via ${gateway}", + unless => "netstat -r | grep -q 'default.*${gateway}'", + } } /(?i)redhat/: { + Cfg <| name == $gateway |> if ! defined(Cfg[$gateway]) { cfg { $gateway: file => '/etc/sysconfig/network', key => 'GATEWAY', value => $gateway, + } -> + # FIXME: we should not nuke the system with 'service network restart'... + # FIXME: but we should ensure default route will be created somehow + exec { $exec_name : + path => '/bin:/usr/bin:/sbin:/usr/sbin', + command => "ip route replace default via ${gateway} || true", + unless => "netstat -r | grep -q 'default.*${gateway}'", } } } @@ -27,4 +42,4 @@ } # -### \ No newline at end of file +### diff --git a/manifests/l3/ifconfig.pp b/manifests/l3/ifconfig.pp index ee4a043..43acf43 100644 --- a/manifests/l3/ifconfig.pp +++ b/manifests/l3/ifconfig.pp @@ -16,32 +16,21 @@ # [*netmask*] # Specify network mask. Default is '255.255.255.0'. # -# [*vlandev*] -# If you configure 802.1q vlan interface with name like 'vlanXXX' -# you must specify a parent interface in this option -# -# [*bond_master*] -# This parameter sets the bond_master interface and says that this interface -# is a slave for bondX interface. -# -# [*bond_mode*] -# This parameter specifies a bond mode for interfaces like bondNN. -# All bond_* properties are ignored for non-bond-master interfaces. -# -# [*bond_miimon*] -# lacp MII monitor period. -# -# [*bond_lacp_rate*] -# lacp MII rate -# -# [*ifname_order_prefix*] -# Sets the interface startup order -# # [*gateway*] # Specify default gateway if need. # You can specify IP address, or 'save' for save default route # if it lies through this interface now. # +## [*default_gateway*] +## Specify if this nic and gateway should become the default route. +## requires that gateway is also set. +## +## [*other_nets*] +## Optional. Defines additional networks that this inteface can reach in CIDR +## format. +## It will be used to add additional routes to this interface. +## other_nets => ['10.10.2.0/24', '10.10.4.0/24'] +## # [*dns_nameservers*] # Specify a pair of nameservers if need. Must be an array, for example: # nameservers => ['8.8.8.8', '8.8.4.4'] @@ -69,51 +58,35 @@ # [*check_by_ping_timeout*] # Timeout for check_by_ping # -# -# If you configure 802.1q vlan interfaces then you must declare relationships -# between them in site.pp. -# Ex: L23network:L3:Ifconfig['eth2'] -> L23network:L3:Ifconfig['eth2.128'] -# + define l23network::l3::ifconfig ( - $ipaddr, + $ensure = present, $interface = $name, - $netmask = '255.255.255.0', + $ipaddr = undef, $gateway = undef, - $vlandev = undef, - $bond_master = undef, - $bond_mode = undef, - $bond_miimon = 100, - $bond_lacp_rate = 1, - $mtu = undef, + $gateway_metric = undef, +# $default_gateway = false, +# $other_nets = undef, $dns_nameservers = undef, $dns_search = undef, $dns_domain = undef, $dhcp_hostname = undef, - $dhcp_nowait = false, - $ifname_order_prefix = false, +# $dhcp_nowait = false, $check_by_ping = 'gateway', - $check_by_ping_timeout = 120, + $check_by_ping_timeout = 30, #todo: label => "XXX", # -- "ip addr add..... label XXX" -){ + $vendor_specific = undef, + $provider = undef +) { + include ::stdlib include ::l23network::params - $bond_modes = [ - 'balance-rr', - 'active-backup', - 'balance-xor', - 'broadcast', - '802.3ad', - 'balance-tlb', - 'balance-alb' - ] - # setup configure method for inteface - if $bond_master { - $method = 'bondslave' - } elsif is_array($ipaddr) { + if is_array($ipaddr) { # getting array of IP addresses for one interface $method = 'static' check_cidrs($ipaddr) + $ipaddr_list = $ipaddr $effective_ipaddr = cidr_to_ipaddr($ipaddr[0]) $effective_netmask = cidr_to_netmask($ipaddr[0]) $ipaddr_aliases = array_part($ipaddr,1,0) @@ -123,13 +96,15 @@ case $ipaddr { 'dhcp': { $method = 'dhcp' - $effective_ipaddr = $ipaddr + $effective_ipaddr = 'dhcp' $effective_netmask = undef + $ipaddr_list = ['dhcp'] } 'none': { $method = 'manual' - $effective_ipaddr = $ipaddr + $effective_ipaddr = 'none' $effective_netmask = undef + $ipaddr_list = ['none'] } default: { $method = 'static' @@ -137,44 +112,23 @@ # ipaddr can be cidr-notated $effective_ipaddr = cidr_to_ipaddr($ipaddr) $effective_netmask = cidr_to_netmask($ipaddr) + $ipaddr_list = [$ipaddr] } else { # or classic pair of ipaddr+netmask $effective_ipaddr = $ipaddr $effective_netmask = $netmask + $cidr_notated_effective_netmask = netmask_to_cidr($netmask) + $ipaddr_list = ["${ipaddr}/${cidr_notated_effective_netmask}"] } } } } else { - fail("Ipaddr must be a string or array of strings") - } - - # OS dependent constants and packages - case $::osfamily { - /(?i)debian/: { - $if_files_dir = '/etc/network/interfaces.d' - $interfaces = '/etc/network/interfaces' - } - /(?i)redhat/: { - $if_files_dir = '/etc/sysconfig/network-scripts' - $interfaces = false - if ! defined(Class[L23network::L2::Centos_upndown_scripts]) { - if defined(Stage[netconfig]) { - class{'l23network::l2::centos_upndown_scripts': stage=>'netconfig' } - } else { - class{'l23network::l2::centos_upndown_scripts': } - } - } - Anchor <| title == 'l23network::l2::centos_upndown_scripts' |> - -> L23network::L3::Ifconfig <| interface == "$interface" |> - } - default: { - fail("Unsupported OS: ${::osfamily}/${::operatingsystem}") - } + fail('Ipaddr must be a single IPaddr or list of IPaddrs in CIDR notation.') } # DNS nameservers, search and domain options if $dns_nameservers { - $dns_nameservers_list = merge_arrays( array_or_string_to_array($dns_nameservers), [false, false]) + $dns_nameservers_list = concat(array_or_string_to_array($dns_nameservers), [false, false]) $dns_nameservers_1 = $dns_nameservers_list[0] $dns_nameservers_2 = $dns_nameservers_list[1] } @@ -183,7 +137,7 @@ if $dns_search_list { $dns_search_string = join($dns_search_list, ' ') } else { - fail("dns_search option must be array or string") + fail('dns_search option must be array or string') } } if $dns_domain { @@ -191,107 +145,90 @@ if $dns_domain_list { $dns_domain_string = $dns_domain_list[0] } else { - fail("dns_domain option must be array or string") + fail('dns_domain option must be array or string') } } - # Detect VLAN and bond mode configuration - case $interface { - /^vlan(\d+)/: { - $vlan_mode = 'vlan' - $vlan_id = $1 - if $vlandev { - $vlan_dev = $vlandev - } else { - fail("Can't configure vlan interface ${interface} without definition (ex: vlandev=>ethXX).") - } - } - /^(eth\d+|bond\d+)\.(\d+)/: { - $vlan_mode = 'eth' - $vlan_id = $2 - $vlan_dev = $1 - } - /^(bond\d+)$/: { - if ! $bond_mode { - fail("To configure the interface bonding you must the bond_mode parameter is required and must be between 0..6.") - } - if $bond_mode <0 or $bond_mode>6 { - fail("For interface bonding the bond_mode must be between 0..6, not '${bond_mode}'.") - } - $vlan_mode = undef - } - default: { - $vlan_mode = undef - } - } - - # Specify interface file name prefix - if $ifname_order_prefix { - $interface_file= "${if_files_dir}/ifcfg-${ifname_order_prefix}-${interface}" + if $method == 'static' { + $def_gateway = $gateway + # # todo: move routing to separated resource with his own provider + # if ($def_gateway and !defined(L23network::L3::Defaultroute[$def_gateway])) { + # Anchor['l23network::init'] -> + # L3_ifconfig[$interface] + # -> + # l23network::l3::defaultroute { $def_gateway: } + # } } else { - $interface_file= "${if_files_dir}/ifcfg-${interface}" + $def_gateway = undef } - if $method == 'static' { - if $gateway and $gateway != 'save' { - $def_gateway = $gateway + # todo: re-implement later + # if $::osfamily =~ /(?i)redhat/ and ($ipaddr_aliases or $ethtool_lines) { + # Anchor['l23network::init'] -> + # file {"${::l23network::params::interfaces_dir}/interface-up-script-${interface}": + # ensure => present, + # owner => 'root', + # mode => '0755', + # recurse => true, + # content => template("l23network/ipconfig_${::osfamily}_ifup-script.erb"), + # } -> + # file {"${::l23network::params::interfaces_dir}/interface-dn-script-${interface}": + # ensure => present, + # owner => 'root', + # mode => '0755', + # recurse => true, + # content => template("l23network/ipconfig_${::osfamily}_ifdn-script.erb"), + # } -> + # File <| title == $interface_file |> + # } + + if ! defined (L3_ifconfig[$interface]) { + if $provider { + $config_provider = "${provider}_${::l23_os}" } else { - # recognizing default gateway - if $gateway == 'save' and $::l3_default_route and $::l3_default_route_interface == $interface { - $def_gateway = $::l3_default_route - } else { - $def_gateway = undef - } + $config_provider = undef } - if $::osfamily == 'RedHat' and $def_gateway and !defined(L23network::L3::Defaultroute[$def_gateway]) { - l23network::l3::defaultroute { $def_gateway: } + + + if !defined(L23network::L2::Port[$interface]) and !defined(L23network::L2::Bond[$interface]) and !defined(L23network::L2::Bridge[$interface]) { + l23network::l2::port { $interface: } + L23network::L2::Port[$interface] -> L3_ifconfig[$interface] } - } else { - $def_gateway = undef - } - if $interfaces { - if ! defined(File["$interfaces"]) { - file {"$interfaces": - ensure => present, - content => template('l23network/interfaces.erb'), + + if ! defined (L23_stored_config[$interface]) { + l23_stored_config { $interface: + provider => $config_provider } } - File<| title == "$interfaces" |> -> File<| title == "$if_files_dir" |> - } + L23_stored_config <| title == $interface |> { + method => $method, + ipaddr => $ipaddr_list[0], + gateway => $def_gateway, + gateway_metric => $gateway_metric, + vendor_specific => $vendor_specific, + #provider => $config_provider # do not enable, provider should be set while port define + } - if ! defined(File["$if_files_dir"]) { - file {"$if_files_dir": - ensure => directory, - owner => 'root', - mode => '0755', - recurse => true, + # configure runtime + l3_ifconfig { $interface : + ensure => $ensure, + ipaddr => $ipaddr_list, + gateway => $def_gateway, + gateway_metric => $gateway_metric, +## $other_nets = undef, +# dns_nameservers => $dns_nameservers, +# dns_search => $dns_search_string, +# dns_domain => $dns_domain_string, +# dhcp_hostname => $dhcp_hostname, +# check_by_ping => $check_by_ping, +# check_by_ping_timeout => $check_by_ping_timeout, + vendor_specific => $vendor_specific, + provider => $provider # For L3 features provider independed from OVS } - } - File<| title == "$if_files_dir" |> -> File<| title == "$interface_file" |> - file {"$interface_file": - ensure => present, - owner => 'root', - mode => '0644', - content => template("l23network/ipconfig_${::osfamily}_${method}.erb"), - } - if $::osfamily =~ /(?i)redhat/ and $ipaddr_aliases { - file {"${if_files_dir}/interface-up-script-${interface}": - ensure => present, - owner => 'root', - mode => '0755', - recurse => true, - content => template("l23network/ipconfig_${::osfamily}_${method}_up-script.erb"), - } -> - File <| title == $interface_file |> + L23_stored_config <| title == $interface |> -> + L3_ifconfig <| title == $interface |> } - notify {"ifconfig_${interface}": message=>"Interface:${interface} IP:${effective_ipaddr}/${effective_netmask}", withpath=>false} -> - l3_if_downup {"$interface": - check_by_ping => $check_by_ping, - check_by_ping_timeout => $check_by_ping_timeout, - subscribe => File["$interface_file"], - refreshonly => true, - } } diff --git a/manifests/l3/route.pp b/manifests/l3/route.pp new file mode 100644 index 0000000..34ee125 --- /dev/null +++ b/manifests/l3/route.pp @@ -0,0 +1,34 @@ +# == Define: l23network::l3::route + +define l23network::l3::route ( + $destination, # should be CIDR or 'default' + $gateway, # should be IP address + $metric = undef, + $vendor_specific = undef, + $provider = undef, + $ensure = present, +) { + include ::l23network::params + + $r_name = get_route_resource_name($destination, $metric) + + if ! defined (L3_route[$r_name]) { + if $provider { + $config_provider = "${provider}_${::l23_os}" + } else { + $config_provider = undef + } + + # There are no stored_config for this resource. Configure runtime only. + l3_route { $r_name : + ensure => $ensure, + destination => $destination, + gateway => $gateway, + metric => $metric, + vendor_specific => $vendor_specific, + provider => $provider # For L3 features provider independed from OVS + } + L3_ifconfig<||> -> L3_route<||> + } + +} diff --git a/manifests/params.pp b/manifests/params.pp index b3a985b..a320c00 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -1,48 +1,46 @@ +# L23network OS-aware constants +# class l23network::params { - if is_float($::kernelmajversion) { - $kernelmajversion_f = 0 + $::kernelmajversion # just a hack for convert string to float - } else { - $kernelmajversion_f = -1 - } + $monolith_bond_providers = ['ovs'] + case $::osfamily { /(?i)debian/: { - $ovs_service_name = 'openvswitch-switch' - $ovs_status_cmd = '/etc/init.d/openvswitch-switch status' - $lnx_vlan_tools = 'vlan' - $lnx_bond_tools = 'ifenslave' - $lnx_ethernet_tools = 'ethtool' - if $kernelmajversion_f > 0 and $kernelmajversion_f < 3.13 { - $ovs_packages = ['openvswitch-datapath-dkms', 'openvswitch-switch'] - } else { - $ovs_packages = ['openvswitch-switch'] - } + $interfaces_dir = '/etc/network/interfaces.d' + $interfaces_file = '/etc/network/interfaces' + $ovs_service_name = 'openvswitch-switch' + $ovs_status_cmd = '/etc/init.d/openvswitch-switch status' + $lnx_vlan_tools = 'vlan' + $lnx_bond_tools = 'ifenslave' + $lnx_ethernet_tools = 'ethtool' + $lnx_bridge_tools = 'bridge-utils' + $ovs_datapath_package_name = undef + $ovs_common_package_name = 'openvswitch-switch' + $ovs_kern_module_name = 'openvswitch' } /(?i)redhat/: { - $ovs_service_name = 'openvswitch' #'ovs-vswitchd' - $ovs_status_cmd = '/etc/init.d/openvswitch status' - $lnx_vlan_tools = 'vconfig' - $lnx_bond_tools = undef - $lnx_ethernet_tools = 'ethtool' - if $kernelmajversion_f > 0 and $kernelmajversion_f < 3.10 { - $ovs_packages = ['kmod-openvswitch', 'openvswitch'] - } else { - $ovs_packages = ['openvswitch'] - } + $interfaces_dir = '/etc/sysconfig/network-scripts' + $interfaces_file = undef + $ovs_service_name = 'openvswitch' + $ovs_status_cmd = '/etc/init.d/openvswitch status' + $lnx_vlan_tools = 'vconfig' + $lnx_bond_tools = undef + $lnx_ethernet_tools = 'ethtool' + $lnx_bridge_tools = 'bridge-utils' + $ovs_datapath_package_name = 'kmod-openvswitch' + $ovs_common_package_name = 'openvswitch' + $ovs_kern_module_name = 'openvswitch' } - /(?i)linux/: { - case $::operatingsystem { - /(?i)archlinux/: { - $ovs_service_name = 'openvswitch.service' - $ovs_status_cmd = 'systemctl status openvswitch' - $ovs_packages = ['aur/openvswitch'] - $lnx_vlan_tools = 'aur/vconfig' - $lnx_bond_tools = 'core/ifenslave' - $lnx_ethernet_tools = 'extra/ethtool' - } - default: { - fail("Unsupported OS: ${::osfamily}/${::operatingsystem}") - } - } + /(?i)darwin/: { + $interfaces_dir = '/tmp/1' + $interfaces_file = undef + $ovs_service_name = undef + $lnx_vlan_tools = undef + $lnx_bond_tools = undef + $lnx_ethernet_tools = undef + $lnx_bridge_tools = undef + $ovs_datapath_package_name = undef + $ovs_common_package_name = undef + $ovs_kern_module_name = unedf } default: { fail("Unsupported OS: ${::osfamily}/${::operatingsystem}") diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..f7812cf --- /dev/null +++ b/metadata.json @@ -0,0 +1,30 @@ +{ + "name": "xenolog/l23network", + "version": "1.1.0", + "author": "Sergey Vasilenko, Stas Makar, Mirantis FUEL team, L23network community team", + "summary": "Puppet module for configure network features", + "license": "Apache-2.0", + "source": "git://github.com/xenolog/l23network.git", + "project_page": "https://github.com/xenolog/l23network", + "issues_url": "https://github.com/xenolog/l23network/issues", + "requirements": [ + { "name": "pe","version_requirement": "3.x" }, + { "name": "puppet","version_requirement": "3.x" } + ], + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": ["6.5"] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": ["14.04"] + } + ], + "description": "Configure network features for 2nd and 3d network level.", + "dependencies": [ + { "name": "puppetlabs/stdlib", "version_requirement": ">=4.0.0 <5.0.0" }, + { "name": "duritong/sysctl", "version_requirement": ">=0.0.1 <1.0.0" }, + { "name": "adrien/filemapper", "version_requirement": ">=1.1.3" }, + ] +} diff --git a/spec/classes/bond__spec.rb b/spec/classes/bond__spec.rb new file mode 100644 index 0000000..a165a2a --- /dev/null +++ b/spec/classes/bond__spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe 'l23network::examples::run_network_scheme', :type => :class do +let(:network_scheme) do +< 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } + } + + let(:params) do { + :settings_yaml => network_scheme, + } end + + it do + should compile + end + + it do + should contain_l2_bond('bond23').with({ + 'ensure' => 'present', + #'slaves' => ['eth2', 'eth3'], + 'mtu' => 4000, + }) + end + + ['eth2', 'eth3'].each do |iface| + it do + should contain_l2_port(iface).with({ + 'ensure' => 'present', + 'mtu' => 9000, + 'ethtool' => { + 'offload' => { + 'generic-receive-offload' => false, + 'generic-segmentation-offload' => false + } + } + }) + end + end + + end + +end + +### diff --git a/spec/classes/empty_network_scheme__spec.rb b/spec/classes/empty_network_scheme__spec.rb new file mode 100644 index 0000000..c49d008 --- /dev/null +++ b/spec/classes/empty_network_scheme__spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'l23network::examples::run_network_scheme', :type => :class do +let(:network_scheme) { +< 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + let(:params) { { + :settings_yaml => network_scheme + } } + + it do + should compile + end + + end + +end + +### \ No newline at end of file diff --git a/spec/classes/l23network_init__spec.rb b/spec/classes/l23network_init__spec.rb new file mode 100644 index 0000000..4068865 --- /dev/null +++ b/spec/classes/l23network_init__spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe 'l23network', :type => :class do + + context 'default init of l23network module' do +# let(:title) { 'empty network scheme' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + it do + should compile.with_all_deps + end + + it do + should contain_package('bridge-utils').with_ensure('present') + should contain_package('ethtool').with_ensure('present') + should contain_package('ifenslave').with_ensure('present') + should contain_package('vlan').with_ensure('present') + end + end + + context 'init l23network module with enabled OVS' do + #let(:title) { 'empty network scheme' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + let(:params) { { + :use_ovs => true + } } + + it do + should compile.with_all_deps + end + + it do + should contain_package('openvswitch-common').with({ + 'name' => 'openvswitch-switch' + }) + should contain_package('bridge-utils').with_ensure('present') + should contain_package('ethtool').with_ensure('present') + should contain_package('ifenslave').with_ensure('present') + should contain_package('vlan').with_ensure('present') + end + + it do + should contain_service('openvswitch-service').with({ + 'ensure' => 'running', + 'name' => 'openvswitch-switch', + 'enable' => true + }) + end + + end + +end + +### \ No newline at end of file diff --git a/spec/defines/ifconfig__dhcp__spec.rb b/spec/defines/ifconfig__dhcp__spec.rb new file mode 100644 index 0000000..e851656 --- /dev/null +++ b/spec/defines/ifconfig__dhcp__spec.rb @@ -0,0 +1,144 @@ +require 'spec_helper' + +describe 'l23network::l3::ifconfig', :type => :define do + context 'ifconfig with dhcp' do + let(:title) { 'ifconfig simple test' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux' + } } + + let(:params) { { + :interface => 'eth4', + :ipaddr => 'dhcp' + } } + + it do + should compile + end + + it do + should contain_l23_stored_config('eth4').only_with({ + 'ensure' => 'present', + 'name' => 'eth4', + 'method' => 'dhcp', + 'ipaddr' => 'dhcp', + 'gateway' => nil, + 'use_ovs' => nil, + }) + end + + it do + should contain_l3_ifconfig('eth4').with({ + 'ensure' => 'present', + 'ipaddr' => 'dhcp', + 'gateway' => nil, + }).that_requires('L23_stored_config[eth4]') + end + + + end + +end + +# # Ubintu, dhcp, ordered iface +# describe 'l23network::l3::ifconfig', :type => :define do +# let(:module_path) { '../' } +# let(:title) { 'ifconfig simple test' } +# let(:params) { { +# :interface => 'eth4', +# :ipaddr => 'dhcp', +# :ifname_order_prefix => 'zzz' +# } } +# let(:facts) { { +# :osfamily => 'Debian', +# :operatingsystem => 'Ubuntu', +# :kernel => 'Linux' +# } } +# let(:interface_file_start) { '/etc/network/interfaces.d/ifcfg-' } + +# it "Ubintu/dhcp: ordered.ifaces: Should contain interface_file" do +# should contain_file('/etc/network/interfaces').with_content(/\*/) +# end + +# it "Ubintu/dhcp: ordered.ifaces: interface file shouldn't contain ipaddr and netmask" do +# rv = contain_file("#{interface_file_start}#{params[:ifname_order_prefix]}-#{params[:interface]}") +# should rv.without_content(/address/) +# should rv.without_content(/netmask/) +# end + +# it "Ubintu/dhcp: ordered.ifaces: interface file should contain ifup/ifdn commands" do +# rv = contain_file("#{interface_file_start}#{params[:ifname_order_prefix]}-#{params[:interface]}") +# should rv.without_content(/address/) +# should rv.without_content(/netmask/) +# end + +# it "Ubintu/dhcp: ordered.ifaces: interface file shouldn't contains bond-master options" do +# rv = contain_file("#{interface_file_start}#{params[:ifname_order_prefix]}-#{params[:interface]}") +# should rv.without_content(/bond-mode/) +# should rv.without_content(/slaves/) +# end +# end + +# # Centos, dhcp +# describe 'l23network::l3::ifconfig', :type => :define do +# let(:module_path) { '../' } +# let(:title) { 'ifconfig simple test' } +# let(:params) { { +# :interface => 'eth4', +# :ipaddr => 'dhcp' +# } } +# let(:facts) { { +# :osfamily => 'RedHat', +# :operatingsystem => 'Centos', +# :kernel => 'Linux' +# } } +# let(:interface_file_start) { '/etc/sysconfig/network-scripts/ifcfg-' } +# let(:interface_up_file_start) { '/etc/sysconfig/network-scripts/interface-up-script-' } + +# it 'Centos/dhcp: interface file should contains true header' do +# rv = contain_file("#{interface_file_start}#{params[:interface]}") +# should rv.with_content(/DEVICE=#{params[:interface]}/) +# should rv.with_content(/BOOTPROTO=dhcp/) +# should rv.with_content(/ONBOOT=yes/) +# end + +# it "Centos/dhcp: Shouldn't contains interface_file with IP-addr" do +# rv = contain_file("#{interface_file_start}#{params[:interface]}") +# should rv.without_content(/IPADDR=/) +# should rv.without_content(/NETMASK=/) +# end +# end + +# # Centos, dhcp, ordered iface +# describe 'l23network::l3::ifconfig', :type => :define do +# let(:module_path) { '../' } +# let(:title) { 'ifconfig simple test' } +# let(:params) { { +# :interface => 'eth4', +# :ipaddr => 'dhcp', +# :ifname_order_prefix => 'zzz' +# } } +# let(:facts) { { +# :osfamily => 'RedHat', +# :operatingsystem => 'Centos', +# :kernel => 'Linux' +# } } +# let(:interface_file_start) { '/etc/sysconfig/network-scripts/ifcfg-' } +# let(:interface_up_file_start) { '/etc/sysconfig/network-scripts/interface-up-script-' } + +# it 'Centos/dhcp: ordered.ifaces: interface file should contains true header' do +# rv = contain_file("#{interface_file_start}#{params[:ifname_order_prefix]}-#{params[:interface]}") +# should rv.with_content(/DEVICE=#{params[:interface]}/) +# should rv.with_content(/BOOTPROTO=dhcp/) +# should rv.with_content(/ONBOOT=yes/) +# end + +# it 'Centos/dhcp: ordered.ifaces: Should contains interface_file with IP-addr' do +# rv = contain_file("#{interface_file_start}#{params[:ifname_order_prefix]}-#{params[:interface]}") +# should rv.without_content(/IPADDR=/) +# should rv.without_content(/NETMASK=/) +# end +# end +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/defines/ifconfig__spec.rb b/spec/defines/ifconfig__spec.rb new file mode 100644 index 0000000..fbf1bc0 --- /dev/null +++ b/spec/defines/ifconfig__spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe 'l23network::l3::ifconfig', :type => :define do + context 'simple ifconfig usage' do + let(:title) { 'ifconfig simple test' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux' + } } + + let(:params) { { + :interface => 'eth4', + :ipaddr => 'none' + } } + + it do + should compile + end + + it do + should contain_l23_stored_config('eth4').only_with({ + 'ensure' => 'present', + 'name' => 'eth4', + 'method' => 'manual', + 'ipaddr' => 'none', + 'gateway' => nil, + 'use_ovs' => nil, + }) + end + + it do + should contain_l3_ifconfig('eth4').with({ + 'ensure' => 'present', + 'ipaddr' => 'none', + 'gateway' => nil, + }) + end + + it do + should contain_l3_ifconfig('eth4').that_requires('L23_stored_config[eth4]') + should contain_l3_ifconfig('eth4').that_requires('L23network::L2::Port[eth4]') + end + + end + +end + +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/defines/l2_bond__spec.rb b/spec/defines/l2_bond__spec.rb new file mode 100644 index 0000000..4058464 --- /dev/null +++ b/spec/defines/l2_bond__spec.rb @@ -0,0 +1,213 @@ +require 'spec_helper' + +describe 'l23network::l2::bond', :type => :define do + let(:title) { 'Spec for l23network::l2::bond' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + context 'Just create a lnx-bond with two slave interfaces' do + let(:params) do + { + :name => 'bond0', + :interfaces => ['eth3', 'eth4'], + :bond_properties => {}, + :provider => 'lnx' + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('bond0').with({ + 'ensure' => 'present', + 'if_type' => 'bond', + 'bond_mode' => 'balance-rr', + 'bond_slaves' => ['eth3', 'eth4'], + 'bond_miimon' => '100', + }) + end + + # it do + # should contain_l2_bond('bond0').with_ensure('present') + # should contain_l2_bond('bond0').with_bond_properties({ + # :mode => "balance-rr", + # :miimon => "100", + # :lacp_rate => "slow", + # :xmit_hash_policy => "layer2" + # }) + # end + + ['eth3', 'eth4'].each do |slave| + it do + should contain_l23_stored_config(slave).with({ + 'ensure' => 'present', + 'if_type' => 'ethernet', + 'bond_master' => 'bond0', + }) + end + + it do + should contain_l2_port(slave).with({ + 'ensure' => 'present', + 'provider' => 'lnx', + }).that_requires("L23_stored_config[#{slave}]") + end + + it do + should contain_l2_port(slave).that_requires('L2_bond[bond0]') + end + end + end + + context 'Just create a lnx-bond with specific MTU' do + let(:params) do + { + :name => 'bond0', + :interfaces => ['eth3', 'eth4'], + :mtu => 9000, + :bond_properties => {}, + :provider => 'lnx' + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('bond0').with({ + 'mtu' => 9000, + }) + end + + it do + should contain_l2_bond('bond0').with({ + 'mtu' => 9000, + }).that_requires('L23_stored_config[bond0]') + end + + ['eth3', 'eth4'].each do |slave| + it do + should contain_l23_stored_config(slave).with({ + 'mtu' => 9000, + }) + end + + it do + should contain_l2_port(slave).with({ + 'mtu' => 9000, + }).that_requires("L23_stored_config[#{slave}]") + end + + end + end + + context 'Create a lnx-bond with LACP' do + let(:params) do + { + :name => 'bond0', + :interfaces => ['eth3', 'eth4'], + :bond_properties => { + 'mode' => '802.3ad', + }, + :provider => 'lnx' + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('bond0').with({ + 'ensure' => 'present', + 'if_type' => 'bond', + 'bond_mode' => '802.3ad', + 'bond_lacp_rate' => 'slow', + 'bond_xmit_hash_policy' => 'layer2', + }) + end + # it do + # should contain_l2_bond('bond0').with_ensure('present') + # should contain_l2_bond('bond0').with_bond_properties({ + # :mode => '802.3ad', + # :lacp_rate => 'slow', + # :miimon => '100', + # :xmit_hash_policy => 'layer2' + # }) + # end + + # we shouldn't test bond slaves here, because it equalent to previous tests + end + + context 'Create a lnx-bond with mode = active-backup, lacp_rate = fast xmit_hash_policy = layer2' do + let(:params) do + { + :name => 'bond0', + :interfaces => ['eth3', 'eth4'], + :bond_properties => { + 'mode' => 'active-backup', + 'lacp_rate' => 'fast', + 'xmit_hash_policy' => 'layer2', + }, + :provider => 'lnx' + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('bond0').with({ + 'ensure' => 'present', + 'if_type' => 'bond', + 'bond_mode' => 'active-backup', + 'bond_lacp_rate' => nil, + 'bond_xmit_hash_policy' => nil, + 'bond_miimon' => '100', + }) + end + end + + context 'Create a lnx-bond with mode = balance-tlb, lacp_rate = fast xmit_hash_policy = layer2+3' do + let(:params) do + { + :name => 'bond0', + :interfaces => ['eth3', 'eth4'], + :bond_properties => { + 'mode' => 'balance-tlb', + 'lacp_rate' => 'fast', + 'xmit_hash_policy' => 'layer2+3', + 'miimon' => '300', + }, + :provider => 'lnx' + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('bond0').with({ + 'ensure' => 'present', + 'if_type' => 'bond', + 'bond_mode' => 'balance-tlb', + 'bond_lacp_rate' => nil, + 'bond_xmit_hash_policy' => 'layer2+3', + 'bond_miimon' => '300', + }) + end + end + + +end +# vim: set ts=2 sw=2 et diff --git a/spec/defines/l2_bridge__spec.rb b/spec/defines/l2_bridge__spec.rb new file mode 100644 index 0000000..a887981 --- /dev/null +++ b/spec/defines/l2_bridge__spec.rb @@ -0,0 +1,171 @@ +require 'spec_helper' + +describe 'l23network::l2::bridge', :type => :define do + let(:title) { 'Spec for l23network::l2::bridge' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + context 'Just a bridge, created by name' do + let(:params) do + { + :name => 'br-mgmt', + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('br-mgmt').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'bridge_stp' => nil, + }) + end + + it do + should contain_l2_bridge('br-mgmt').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + 'external_ids' => {'bridge-id'=>'br-mgmt'}, + }).that_requires('L23_stored_config[br-mgmt]') + end + end + + # This feature will be implemented later + # context 'Bridge, created with specigic MTU value' do + # let(:params) do + # { + # :name => 'br-mgmt', + # :mtu => 9000, + # } + # end + + # it do + # should compile + # end + + # it do + # should contain_l23_stored_config('br-mgmt').with({ + # 'mtu' => 9000, + # }) + # end + + # it do + # should contain_l2_bridge('br-mgmt').with({ + # 'ensure' => 'present', + # 'mtu' => 9000, + # }).that_requires('L23_stored_config[br-mgmt]') + # end + # end + + + context 'Bridge, created with enabled stp' do + let(:params) do + { + :name => 'br-mgmt', + :stp => true, + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('br-mgmt').with({ + 'bridge_stp' => true, + }) + end + + it do + should contain_l2_bridge('br-mgmt').with({ + 'ensure' => 'present', + 'stp' => true, + }).that_requires('L23_stored_config[br-mgmt]') + end + end + + context 'Pass vendor-specific property to bridge resource' do + let(:params) do + { + :name => 'br-mgmt', + :vendor_specific => { + 'aaa' => '1111', + 'bbb' => { + 'bbb1' => 11111, + 'bbb2' => ['b11','b12','b13'] + }, + }, + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('br-mgmt').with({ + 'vendor_specific' => { + 'aaa' => '1111', + 'bbb' => { + 'bbb1' => 11111, + 'bbb2' => ['b11','b12','b13'] + }, + }, + }) + end + + it do + should contain_l2_bridge('br-mgmt').with({ + 'ensure' => 'present', + 'vendor_specific' => { + 'aaa' => '1111', + 'bbb' => { + 'bbb1' => 11111, + 'bbb2' => ['b11','b12','b13'] + }, + }, + }).that_requires('L23_stored_config[br-mgmt]') + end + end + + context 'Pass non-default property to bridge resource' do + # Warning!! in the latest releases external_ids property will + # be moved to vendor_specific hash + let(:params) do + { + :name => 'br-mgmt', + :external_ids => { 'bridge-id' => 'qwe', 'aaa' => 'bbb'}, + } + end + + it do + should compile + end + + # In this case no stored_config, because only OVS has this functionality, + # bun one store this values in ovsdb automatically + it do + should contain_l2_bridge('br-mgmt').with({ + 'ensure' => 'present', + 'external_ids' => { + 'bridge-id' => 'qwe', + 'aaa' => 'bbb' + }, + }) + end + end + + + +end +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/defines/l2_patch__spec.rb b/spec/defines/l2_patch__spec.rb new file mode 100644 index 0000000..e01e0fc --- /dev/null +++ b/spec/defines/l2_patch__spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe 'l23network::l2::patch', :type => :define do + let(:title) { 'Spec for l23network::l2::port' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + context 'Just a patch between two bridges' do + let(:params) do + { + :bridges => ['br1', 'br2'], + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('p_br1-0').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'onboot' => true, + 'bridge' => ['br1', 'br2'], + 'jacks' => ['p_br1-0', 'p_br2-1'] + }) + end + + it do + should contain_l2_patch('patch__br1--br2').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + 'bridges' => ['br1', 'br2'], + }).that_requires('L23_stored_config[p_br1-0]') + end + end + + context 'Patch, which has jumbo frames' do + let(:params) do + { + :bridges => ['br1', 'br2'], + :mtu => 9000, + } + end + + it do + should compile + should contain_l23_stored_config('p_br1-0').with({ + 'bridge' => ['br1', 'br2'], + 'jacks' => ['p_br1-0', 'p_br2-1'], + 'mtu' => 9000, + }) + should contain_l2_patch('patch__br1--br2').with({ + 'ensure' => 'present', + 'mtu' => 9000, + 'bridges' => ['br1', 'br2'], + }).that_requires('L23_stored_config[p_br1-0]') + end + end + + context 'Patch, which has vendor-specific properties' do + let(:params) do + { + :bridges => ['br1', 'br2'], + :vendor_specific => { + 'aaa' => '111', + 'bbb' => { + 'bbb1' => 1111, + 'bbb2' => ['b1','b2','b3'] + }, + }, + } + end + + it do + should compile + should contain_l23_stored_config('p_br1-0').with({ + 'bridge' => ['br1', 'br2'], + 'jacks' => ['p_br1-0', 'p_br2-1'], + 'vendor_specific' => { + 'aaa' => '111', + 'bbb' => { + 'bbb1' => 1111, + 'bbb2' => ['b1','b2','b3'] + }, + }, + }) + should contain_l2_patch('patch__br1--br2').with({ + 'ensure' => 'present', + 'bridges' => ['br1', 'br2'], + 'vendor_specific' => { + 'aaa' => '111', + 'bbb' => { + 'bbb1' => 1111, + 'bbb2' => ['b1','b2','b3'] + }, + }, + }).that_requires('L23_stored_config[p_br1-0]') + end + end + + +end +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/defines/l2_port__spec.rb b/spec/defines/l2_port__spec.rb new file mode 100644 index 0000000..d4c2bd7 --- /dev/null +++ b/spec/defines/l2_port__spec.rb @@ -0,0 +1,224 @@ +require 'spec_helper' + +describe 'l23network::l2::port', :type => :define do + let(:title) { 'Spec for l23network::l2::port' } + let(:facts) { { + :osfamily => 'Debian', + :operatingsystem => 'Ubuntu', + :kernel => 'Linux', + :l23_os => 'ubuntu', + :l3_fqdn_hostname => 'stupid_hostname', + } } + + context 'Port without anythyng' do + let(:params) do + { + :name => 'eth4', + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('eth4').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + }) + end + + it do + should contain_l2_port('eth4').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + }).that_requires('L23_stored_config[eth4]') + end + end + + context 'Native linux subinterface' do + let(:params) do + { + :name => 'eth4.102', + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('eth4.102').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'vlan_id' => '102', + 'vlan_dev' => 'eth4', + 'vlan_mode' => 'eth' + }) + end + + it do + should contain_l2_port('eth4.102').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + 'vlan_id' => '102', + 'vlan_dev' => 'eth4', + 'vlan_mode' => 'eth' + }).that_requires('L23_stored_config[eth4.102]') + end + end + + context 'Alternative VLAN definition' do + let(:params) do + { + :name => 'vlan102', + :vlan_dev => 'eth4', + :vlan_id => '102', + } + end + + it do + should compile + end + + it do + should contain_l23_stored_config('vlan102').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'vlan_id' => '102', + 'vlan_dev' => 'eth4', + 'vlan_mode' => 'vlan' + }) + end + + it do + should contain_l2_port('vlan102').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + 'vlan_id' => '102', + 'vlan_dev' => 'eth4', + 'vlan_mode' => 'vlan' + }).that_requires('L23_stored_config[vlan102]') + end + end + + context 'Port, which not ensured' do + let(:params) do + { + :name => 'eth2', + :ensure => 'absent', + } + end + + it do + should compile + should contain_l23_stored_config('eth2').with({ + 'ensure' => 'absent', + }) + should contain_l2_port('eth2').with({ + 'ensure' => 'absent', + }).that_requires('L23_stored_config[eth2]') + end + end + + context 'Port, which has jumbo frames' do + let(:params) do + { + :name => 'eth2', + :mtu => 9000, + } + end + + it do + should compile + should contain_l23_stored_config('eth2').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'mtu' => 9000, + }) + should contain_l2_port('eth2').only_with({ + 'ensure' => 'present', + 'mtu' => 9000, + 'use_ovs' => nil, + }).that_requires('L23_stored_config[eth2]') + end + end + + context 'Port, which an a member of bridge' do + let(:params) do + { + :name => 'eth2', + :bridge => 'br-floating', + } + end + + it do + should compile + should contain_l23_stored_config('eth2').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'bridge' => 'br-floating', + }) + should contain_l2_port('eth2').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + 'bridge' => 'br-floating', + }).that_requires('L23_stored_config[eth2]') + end + end + + context 'Port, which has vendor-specific field' do + let(:params) do + { + :name => 'eth2', + :vendor_specific => { + 'aaa' => '1111', + 'bbb' => { + 'bbb1' => 11111, + 'bbb2' => ['b11','b12','b13'] + }, + }, + } + end + + it do + should compile + should contain_l23_stored_config('eth2').only_with({ + 'use_ovs' => nil, + 'method' => nil, + 'ipaddr' => nil, + 'gateway' => nil, + 'vendor_specific' => { + 'aaa' => '1111', + 'bbb' => { + 'bbb1' => 11111, + 'bbb2' => ['b11','b12','b13'] + }, + }, + }) + should contain_l2_port('eth2').only_with({ + 'ensure' => 'present', + 'use_ovs' => nil, + 'vendor_specific' => { + 'aaa' => '1111', + 'bbb' => { + 'bbb1' => 11111, + 'bbb2' => ['b11','b12','b13'] + }, + }, + }).that_requires('L23_stored_config[eth2]') + end + end + +end +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/fixtures/manifests/site.pp b/spec/fixtures/manifests/site.pp index e69de29..8b13789 100644 --- a/spec/fixtures/manifests/site.pp +++ b/spec/fixtures/manifests/site.pp @@ -0,0 +1 @@ + diff --git a/spec/functions/get_network_role_property__spec.rb b/spec/functions/get_network_role_property__spec.rb new file mode 100644 index 0000000..6ae2088 --- /dev/null +++ b/spec/functions/get_network_role_property__spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:get_network_role_property) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + subject do + function_name = Puppet::Parser::Functions.function(:get_network_role_property) + scope.method(function_name) + end + + context "mtk" do + before(:each) do + #Puppet::Parser::Scope.any_instance.stubs(:lookupvar).with('l3_fqdn_hostname').returns('node1.tld') + scope.stubs(:lookupvar).with('l3_fqdn_hostname').returns('node1.tld') + L23network::Scheme.set_config(scope.lookupvar('l3_fqdn_hostname'), { + :endpoints => { + :eth0 => {:IP => 'dhcp'}, + :"br-ex" => { + :gateway => '10.1.3.1', + :IP => ['10.1.3.11/24'], + }, + :"br-mgmt" => { :IP => ['10.20.1.11/25'] }, + :"br-storage" => { :IP => ['192.168.1.2/24'] }, + :"br-prv" => { :IP => 'none' }, + }, + :roles => { + :management => 'br-mgmt', + :private => 'br-prv', + :ex => 'br-ex', + :storage => 'br-storage', + :admin => 'eth0', + }, + }) + end + + it 'should exist' do + subject == Puppet::Parser::Functions.function(:get_network_role_property) + end + + it 'should return interface name for "private" network role' do + should run.with_params('private', 'interface').and_return('br-prv') + end + + it 'should raise for non-existing role name' do + subject.call(['not_exist', 'interface']).should == nil + end + + it 'should return ip address for "management" network role' do + should run.with_params('management', 'ipaddr').and_return('10.20.1.11') + end + + it 'should return cidr-notated ip address for "management" network role' do + should run.with_params('management', 'cidr').and_return('10.20.1.11/25') + end + + it 'should return netmask for "management" network role' do + should run.with_params('management', 'netmask').and_return('255.255.255.128') + end + + it 'should return ip address and netmask for "management" network role' do + should run.with_params('management', 'ipaddr_netmask_pair').and_return(['10.20.1.11','255.255.255.128']) + end + + it 'should return NIL for "admin" network role' do + should run.with_params('admin', 'netmask').and_return(nil) + end + it 'should return NIL for "admin" network role' do + should run.with_params('admin', 'ipaddr').and_return(nil) + end + it 'should return NIL for "admin" network role' do + should run.with_params('admin', 'cidr').and_return(nil) + end + it 'should return NIL for "admin" network role' do + should run.with_params('admin', 'ipaddr_netmask_pair').and_return(nil) + end + end + +end \ No newline at end of file diff --git a/spec/functions/get_pair_of_jack_names__spec.rb b/spec/functions/get_pair_of_jack_names__spec.rb new file mode 100644 index 0000000..bd0f626 --- /dev/null +++ b/spec/functions/get_pair_of_jack_names__spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'get_pair_of_jack_names' do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + subject do + function_name = Puppet::Parser::Functions.function(:get_pair_of_jack_names) + scope.method(function_name) + end + + context "t1" do + + it 'should exist' do + subject == Puppet::Parser::Functions.function(:get_pair_of_jack_names) + end + + it 'should throw an error on invalid types' do + should run.with_params({:foo => :bar}).and_raise_error(Puppet::ParseError) + end + + it 'should throw an error on invalid arguments number' do + should run.with_params().and_raise_error(Puppet::ParseError) + should run.with_params([1,2],[3,4]).and_raise_error(Puppet::ParseError) + end + + it 'should return numbered interface names' do + should run.with_params(['br-mgmt', 'br-ex']).and_return(["p_br-mgmt-0", "p_br-ex-1"]) + end + + it 'should cut interface names for long interfaces' do + should run.with_params(['br-mmmmmmmmmmmmmmmmmmmmmmmmgmt', 'br-ex']).and_return(["p_br-mmmmmmmm-0", "p_br-ex-1"]) + end + + end +end +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/functions/get_patch_name__spec.rb b/spec/functions/get_patch_name__spec.rb new file mode 100644 index 0000000..2e0f3e8 --- /dev/null +++ b/spec/functions/get_patch_name__spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'get_patch_name' do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + subject do + function_name = Puppet::Parser::Functions.function(:get_patch_name) + scope.method(function_name) + end + + context "t1" do + + it 'should exist' do + subject == Puppet::Parser::Functions.function(:get_patch_name) + end + + it 'should throw an error on invalid types' do + should run.with_params({:foo => :bar}).and_raise_error(Puppet::ParseError) + end + + it 'should throw an error on invalid arguments number' do + should run.with_params().and_raise_error(Puppet::ParseError) + should run.with_params([1,2],[3,4]).and_raise_error(Puppet::ParseError) + end + + it 'should return numbered interface names' do + should run.with_params(['br-mgmt', 'br-ex']).and_return("patch__br-ex--br-mgmt") + end + + #todo(sv): should be refactoded reo returns more shot name + # it 'should cut interface names for long interfaces' do + # should run.with_params(['br-mmmmmmmmmmmmmmmmmmmmmmmmgmt', 'br-ex']).and_return(["p_br-mmmmmmmm-0", "p_br-ex-1"]) + # end + + end +end +# vim: set ts=2 sw=2 et \ No newline at end of file diff --git a/spec/functions/merge_arrays__spec.rb b/spec/functions/merge_arrays__spec.rb deleted file mode 100644 index af8175f..0000000 --- a/spec/functions/merge_arrays__spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'spec_helper' - -describe 'merge_arrays' do - let(:scope) { PuppetlabsSpec::PuppetInternals.scope } - - it 'should exist' do - Puppet::Parser::Functions.function('merge_arrays').should == 'function_merge_arrays' - end - - it 'should throw an error on invalid types' do - lambda { - scope.function_merge_arrays([{:foo => :bar}]) - }.should(raise_error(Puppet::ParseError)) - end - - it 'should throw an error on invalid arguments number' do - lambda { - scope.function_merge_arrays([]) - }.should(raise_error(Puppet::ParseError)) - end - - it 'should return array' do - scope.function_merge_arrays([[1,2,3],[4,5,6],[7,8,9]]).should == [1,2,3,4,5,6,7,8,9] - end -end \ No newline at end of file diff --git a/spec/functions/sanitize_bool_in_hash__spec.rb b/spec/functions/sanitize_bool_in_hash__spec.rb new file mode 100644 index 0000000..eda6735 --- /dev/null +++ b/spec/functions/sanitize_bool_in_hash__spec.rb @@ -0,0 +1,154 @@ +require 'spec_helper' + +describe 'sanitize_bool_in_hash' do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it 'should exist' do + Puppet::Parser::Functions.function('sanitize_bool_in_hash').should == 'function_sanitize_bool_in_hash' + end + + it 'should convert string-boolean values to boolean' do + expect(scope.function_sanitize_bool_in_hash([{ + :s_true => 'true', + :s_false => 'false', + :s_none => 'none', + :s_null => 'null', + :s_nil => 'nil', + :s_nill => 'nill', + }])).to eq({ + :s_true => true, + :s_false => false, + :s_none => nil, + :s_null => nil, + :s_nil => nil, + :s_nill => nil, + }) + end + + it 'should convert UP-sace string-boolean values to boolean' do + expect(scope.function_sanitize_bool_in_hash([{ + :s_true => 'TRUE', + :s_false => 'FALSE', + :s_none => 'NONE', + :s_null => 'NULL', + :s_nil => 'NIL', + :s_nill => 'NILL', + }])).to eq({ + :s_true => true, + :s_false => false, + :s_none => nil, + :s_null => nil, + :s_nil => nil, + :s_nill => nil, + }) + end + + it 'should convert reccursive hashes' do + expect(scope.function_sanitize_bool_in_hash([{ + :bool_hash => { + :str => 'aaa', + :int => 123, + :array => [111,222,333], + :hash => { + :str => 'aaa', + :int => 123, + :array => [111,222,333], + :a_sbool => ['true', 'nil', 'false'], + :a_bool => [true, nil, false], + :hash => { + :str => 'aaa', + :int => 123, + :array => [111,222,333], + :a_sbool => ['true', 'nil', 'false'], + :a_bool => [true, nil, false], + }, + }, + :a_sbool => ['true', 'nil', 'false'], + :a_bool => [true, nil, false], + }, + :bool_hash => { + :t => true, + :f => false, + :n => nil + }, + }])).to eq({ + :bool_hash => { + :str => 'aaa', + :int => 123, + :array => [111,222,333], + :hash => { + :str => 'aaa', + :int => 123, + :array => [111,222,333], + :a_sbool => [true, nil, false], + :a_bool => [true, nil, false], + :hash => { + :str => 'aaa', + :int => 123, + :array => [111,222,333], + :a_sbool => [true, nil, false], + :a_bool => [true, nil, false], + }, + }, + :a_sbool => [true, nil, false], + :a_bool => [true, nil, false], + }, + :bool_hash => { + :t => true, + :f => false, + :n => nil + }, + }) + end + + it 'should convert array of hashes' do + expect(scope.function_sanitize_bool_in_hash([{ :array => [ + {:aaa=>1,"aaa"=>11, :bbb=>2,'bbb'=>12, :ccc=>3,'ccc'=>3}, + {:t=>'true','tt'=>'true', :f=>'false','ff'=>'false', :n=>'nil','nn'=>'nil'}, + { + :s_true => 'true', + :s_false => 'false', + :s_none => 'none', + :s_null => 'null', + :s_nil => 'nil', + :s_nill => 'nill', + }, + { + :s_true => 'TRUE', + :s_false => 'FALSE', + :s_none => 'NONE', + :s_null => 'NULL', + :s_nil => 'NIL', + :s_nill => 'NILL', + }, + ]}])).to eq({ :array => [ + {:aaa=>1,"aaa"=>11, :bbb=>2,'bbb'=>12, :ccc=>3,'ccc'=>3}, + {:t=>true,'tt'=>true, :f=>false,'ff'=>false, :n=>nil,'nn'=>nil}, + { + :s_true => true, + :s_false => false, + :s_none => nil, + :s_null => nil, + :s_nil => nil, + :s_nill => nil, + }, + { + :s_true => true, + :s_false => false, + :s_none => nil, + :s_null => nil, + :s_nil => nil, + :s_nill => nil, + }, + ]}) + end + + it 'should throw an error' do + lambda { + scope.function_sanitize_bool_in_hash(['xxx']) + }.should(raise_error(Puppet::ParseError)) + end + +end + +# vim: set ts=2 sw=2 et : diff --git a/spec/spec.opts b/spec/spec.opts deleted file mode 100644 index c5f6ccf..0000000 --- a/spec/spec.opts +++ /dev/null @@ -1 +0,0 @@ - --colour --backtrace diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d3923f8..7c816e2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,15 @@ -require 'rspec-puppet' +require 'rubygems' +require 'puppetlabs_spec_helper/module_spec_helper' fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) +# Add fixture lib dirs to LOAD_PATH. Work-around for PUP-3336 +if Puppet.version < '4.0.0' + Dir["#{fixture_path}/modules/*/lib"].entries.each do |lib_dir| + $LOAD_PATH << lib_dir + end +end + RSpec.configure do |c| c.module_path = File.join(fixture_path, 'modules') c.manifest_dir = File.join(fixture_path, 'manifests') diff --git a/templates/ethtool_Debian.erb b/templates/ethtool_Debian.erb new file mode 100644 index 0000000..efd8459 --- /dev/null +++ b/templates/ethtool_Debian.erb @@ -0,0 +1,5 @@ +<%- if @ethtool_lines -%> +<%- @ethtool_lines.each do |key,line| -%> +post-up ethtool <%= key %> <%= @interface %> <%= line %> || true +<%- end -%> +<%- end -%> diff --git a/templates/ip_route_Debian.erb b/templates/ip_route_Debian.erb new file mode 100644 index 0000000..b992c47 --- /dev/null +++ b/templates/ip_route_Debian.erb @@ -0,0 +1,4 @@ +<%- @other_nets.each do |n| -%> +post-up ip route add <%= n %> via <%= @gateway %> +post-down ip route del <%= n %> via <%= @gateway %> +<%- end -%> diff --git a/templates/ipconfig_Debian_bondslave.erb b/templates/ipconfig_Debian_bondslave.erb deleted file mode 100644 index 080ac72..0000000 --- a/templates/ipconfig_Debian_bondslave.erb +++ /dev/null @@ -1,3 +0,0 @@ -auto <%= @interface %> -iface <%= @interface %> inet manual -bond-master <%= @bond_master %> diff --git a/templates/ipconfig_Debian_dhcp.erb b/templates/ipconfig_Debian_dhcp.erb deleted file mode 100644 index d5a297d..0000000 --- a/templates/ipconfig_Debian_dhcp.erb +++ /dev/null @@ -1,10 +0,0 @@ -auto <%= @interface %> -iface <%= @interface %> inet dhcp -<% if @dhcp_hostname %>hostname <%= @dhcp_hostname %><% end %> -<% if @vlan_mode %>vlan_raw_device <%= @vlan_dev %><% end %> -<% if @mtu %>mtu <%= @mtu %><% end %> -<% if @bond_mode %>slaves none -bond-mode <%= @bond_mode %><% if @bond_miimon %> -bond-miimon <%= @bond_miimon %><% end %><% if @bond_lacp_rate %> -bond-lacp-rate <%= @bond_lacp_rate %><% end %> -<% end %> diff --git a/templates/ipconfig_Debian_manual.erb b/templates/ipconfig_Debian_manual.erb deleted file mode 100644 index ae4ceeb..0000000 --- a/templates/ipconfig_Debian_manual.erb +++ /dev/null @@ -1,11 +0,0 @@ -auto <%= @interface %> -iface <%= @interface %> inet manual -<% if @vlan_mode %>vlan_raw_device <%= @vlan_dev %><% end %> -up ip l set <%= @interface %> up -down ip l set <%= @interface %> down -<% if @mtu %>mtu <%= @mtu %><% end %> -<% if @bond_mode %>slaves none -bond-mode <%= @bond_mode %><% if @bond_miimon %> -bond-miimon <%= @bond_miimon %><% end %><% if @bond_lacp_rate %> -bond-lacp-rate <%= @bond_lacp_rate %><% end %> -<% end %> diff --git a/templates/ipconfig_Debian_static.erb b/templates/ipconfig_Debian_static.erb deleted file mode 100644 index 2a7e5ce..0000000 --- a/templates/ipconfig_Debian_static.erb +++ /dev/null @@ -1,19 +0,0 @@ -auto <%= @interface %> -iface <%= @interface %> inet static -<% if @vlan_mode %>vlan_raw_device <%= @vlan_dev %><% end %> -address <%= @effective_ipaddr %> -netmask <%= @effective_netmask %> -<% if @def_gateway %>gateway <%= @def_gateway %><% end %> -<% if @dns_nameservers_1 or @dns_nameservers_2 %>dns-nameservers <% if @dns_nameservers_1 %><%= @dns_nameservers_1 %><% end %> <% if @dns_nameservers_2 %><%= @dns_nameservers_2 %><% end %><% end %> -<% if @dns_search_string %>dns-search <%= @dns_search_string %><% end %> -<% if @dns_domain_string %>dns-domain <%= @dns_domain_string %><% end %> -<% if @mtu %>mtu <%= @mtu %><% end %> -<% if @bond_mode %>slaves none -bond-mode <%= @bond_mode %><% if @bond_miimon %> -bond-miimon <%= @bond_miimon %><% end %><% if @bond_lacp_rate %> -bond-lacp-rate <%= @bond_lacp_rate %><% end %> -<% end %> -<%- if @ipaddr_aliases -%><%- @ipaddr_aliases.each do |addr| -%> -post-up ip addr add <%= addr %> dev <%= @interface %> -pre-down ip addr del <%= addr %> dev <%= @interface %> -<%- end -%><%- end -%> diff --git a/templates/ipconfig_RedHat_bondslave.erb b/templates/ipconfig_RedHat_bondslave.erb deleted file mode 100644 index e68f800..0000000 --- a/templates/ipconfig_RedHat_bondslave.erb +++ /dev/null @@ -1,6 +0,0 @@ -DEVICE=<%= @interface %> -BOOTPROTO=none -ONBOOT=yes -USERCTL=no -MASTER=<%= @bond_master %> -SLAVE=yes diff --git a/templates/ipconfig_RedHat_dhcp.erb b/templates/ipconfig_RedHat_dhcp.erb deleted file mode 100644 index cad35f5..0000000 --- a/templates/ipconfig_RedHat_dhcp.erb +++ /dev/null @@ -1,10 +0,0 @@ -DEVICE=<%= @interface %> -BOOTPROTO=dhcp -ONBOOT=yes -USERCTL=no -<% if @dhcp_hostname %>DHCP_HOSTNAME=<%= @dhcp_hostname %><% end %> -<% if @vlan_mode %>VLAN=yes<% end %> -<% if @vlan_mode == 'vlan' %>VLAN_NAME_TYPE=VLAN_PLUS_VID_NO_PAD -PHYSDEV=<%= @vlan_dev %><% end %> -<% if @mtu %>MTU=<%= @mtu %><% end %> -<% if @bond_mode %>BONDING_OPTS="mode=<%= @bond_mode %><% if @bond_miimon %> miimon=<%= @bond_miimon %><% end %><% if @bond_lacp_rate %> bond-lacp-rate=<%= @bond_lacp_rate %><% end %>"<% end %> diff --git a/templates/ipconfig_RedHat_static_down-script.erb b/templates/ipconfig_RedHat_ifdn-script.erb similarity index 100% rename from templates/ipconfig_RedHat_static_down-script.erb rename to templates/ipconfig_RedHat_ifdn-script.erb diff --git a/templates/ipconfig_RedHat_static_up-script.erb b/templates/ipconfig_RedHat_ifup-script.erb similarity index 51% rename from templates/ipconfig_RedHat_static_up-script.erb rename to templates/ipconfig_RedHat_ifup-script.erb index bf623ca..fed537f 100644 --- a/templates/ipconfig_RedHat_static_up-script.erb +++ b/templates/ipconfig_RedHat_ifup-script.erb @@ -4,4 +4,9 @@ ip addr add <%= addr %> dev <%= @interface %> <%- end -%> <%- end -%> +<%- if @ethtool_lines -%> +<%- @ethtool_lines.each do |key,line| -%> +ethtool <%= key %> <%= @interface %> <%= line %> +<%- end -%> +<%- end -%> true diff --git a/templates/ipconfig_RedHat_manual.erb b/templates/ipconfig_RedHat_manual.erb deleted file mode 100644 index 2e06d28..0000000 --- a/templates/ipconfig_RedHat_manual.erb +++ /dev/null @@ -1,9 +0,0 @@ -DEVICE=<%= @interface %> -BOOTPROTO=none -ONBOOT=yes -USERCTL=no -<% if @vlan_mode %>VLAN=yes<% end %> -<% if @vlan_mode == 'vlan' %>VLAN_NAME_TYPE=VLAN_PLUS_VID_NO_PAD -PHYSDEV=<%= @vlan_dev %><% end %> -<% if @mtu %>MTU=<%= @mtu %><% end %> -<% if @bond_mode %>BONDING_OPTS="mode=<%= @bond_mode %><% if @bond_miimon %> miimon=<%= @bond_miimon %><% end %><% if @bond_lacp_rate %> bond-lacp-rate=<%= @bond_lacp_rate %><% end %>"<% end %> diff --git a/templates/ipconfig_RedHat_static.erb b/templates/ipconfig_RedHat_static.erb deleted file mode 100644 index b7d5bc4..0000000 --- a/templates/ipconfig_RedHat_static.erb +++ /dev/null @@ -1,15 +0,0 @@ -DEVICE=<%= @interface %> -IPADDR=<%= @effective_ipaddr %> -NETMASK=<%= @effective_netmask %> -BOOTPROTO=none -ONBOOT=yes -USERCTL=no -<% if @vlan_mode %>VLAN=yes<% end %> -<% if @vlan_mode == 'vlan' %>VLAN_NAME_TYPE=VLAN_PLUS_VID_NO_PAD -PHYSDEV=<%= @vlan_dev %><% end %> -<% if @def_gateway %>GATEWAY=<%= @def_gateway %><% end %> -<% if @dns_nameservers_1 %>DNS1=<%= @dns_nameservers_1 %><% end %> -<% if @dns_nameservers_2 %>DNS2=<%= @dns_nameservers_2 %><% end %> -<% if @dns_search_string %>SEARCH=<%= @dns_search_string %><% end %> -<% if @mtu %>MTU=<%= @mtu %><% end %> -<% if @bond_mode %>BONDING_OPTS="mode=<%= @bond_mode %><% if @bond_miimon %> miimon=<%= @bond_miimon %><% end %><% if @bond_lacp_rate %> bond-lacp-rate=<%= @bond_lacp_rate %><% end %>"<% end %> diff --git a/templates/route_RedHat.erb b/templates/route_RedHat.erb new file mode 100644 index 0000000..b025a1e --- /dev/null +++ b/templates/route_RedHat.erb @@ -0,0 +1,3 @@ +<%- @other_nets.each do |n| -%> +<%= n %> via <%= @gateway %> +<%- end -%>