puppet_x_eos_eapi 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +24 -0
- data/LICENSE.txt +202 -0
- data/README.md +87 -0
- data/Rakefile +1 -0
- data/lib/puppet_x/eos/autoload.rb +57 -0
- data/lib/puppet_x/eos/eapi.rb +259 -0
- data/lib/puppet_x/eos/module_base.rb +37 -0
- data/lib/puppet_x/eos/modules/daemon.rb +109 -0
- data/lib/puppet_x/eos/modules/extension.rb +167 -0
- data/lib/puppet_x/eos/modules/interface.rb +180 -0
- data/lib/puppet_x/eos/modules/ipinterface.rb +133 -0
- data/lib/puppet_x/eos/modules/mlag.rb +268 -0
- data/lib/puppet_x/eos/modules/ntp.rb +129 -0
- data/lib/puppet_x/eos/modules/ospf.rb +129 -0
- data/lib/puppet_x/eos/modules/portchannel.rb +277 -0
- data/lib/puppet_x/eos/modules/radius.rb +367 -0
- data/lib/puppet_x/eos/modules/snmp.rb +177 -0
- data/lib/puppet_x/eos/modules/switchport.rb +255 -0
- data/lib/puppet_x/eos/modules/system.rb +138 -0
- data/lib/puppet_x/eos/modules/tacacs.rb +302 -0
- data/lib/puppet_x/eos/modules/vlan.rb +179 -0
- data/lib/puppet_x/eos/modules/vxlan.rb +132 -0
- data/lib/puppet_x/eos/provider.rb +71 -0
- data/lib/puppet_x/eos/version.rb +41 -0
- data/lib/puppet_x/net_dev/eos_api.rb +1011 -0
- data/lib/puppet_x/net_dev/eos_api/common_methods.rb +27 -0
- data/lib/puppet_x/net_dev/eos_api/snmp_methods.rb +647 -0
- data/lib/puppet_x/net_dev/eos_api/version.rb +8 -0
- data/lib/puppet_x_eos_eapi.rb +4 -0
- data/puppet_x_eos_eapi.gemspec +31 -0
- data/spec/fixtures/fixture_all_portchannel_modes.json +8 -0
- data/spec/fixtures/fixture_all_portchannels_detailed.json +15 -0
- data/spec/fixtures/fixture_create_vlan_error.json +17 -0
- data/spec/fixtures/fixture_create_vlan_success.json +12 -0
- data/spec/fixtures/fixture_eapi_conf.yaml +4 -0
- data/spec/fixtures/fixture_enable_configure_vlan_3111_name_foo.json +14 -0
- data/spec/fixtures/fixture_enable_configure_vlan_foo_name_bar.json +19 -0
- data/spec/fixtures/fixture_get_snmp_communities_non_existent_acl.yaml +2 -0
- data/spec/fixtures/fixture_get_snmp_location_westeros.json +5 -0
- data/spec/fixtures/fixture_portchannel_min_links_1.json +8 -0
- data/spec/fixtures/fixture_portchannel_min_links_2.json +8 -0
- data/spec/fixtures/fixture_running_config.yaml +1 -0
- data/spec/fixtures/fixture_running_configuration_radius_configured.yaml +30 -0
- data/spec/fixtures/fixture_running_configuration_radius_default.yaml +29 -0
- data/spec/fixtures/fixture_running_configuration_radius_server_groups.yaml +38 -0
- data/spec/fixtures/fixture_running_configuration_radius_servers.yaml +34 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_configured.yaml +38 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_default.yaml +38 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_groups.yaml +1 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_groups_3.yaml +43 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_servers.yaml +41 -0
- data/spec/fixtures/fixture_s4_show_etherchannel_detailed.json +9 -0
- data/spec/fixtures/fixture_show_flowcontrol_et1.json +5 -0
- data/spec/fixtures/fixture_show_interfaces.json +297 -0
- data/spec/fixtures/fixture_show_interfaces_switchport_format_text.json +9 -0
- data/spec/fixtures/fixture_show_port_channel_summary_2_lags.json +9 -0
- data/spec/fixtures/fixture_show_port_channel_summary_static.json +9 -0
- data/spec/fixtures/fixture_show_snmp_community.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_contact_empty.json +5 -0
- data/spec/fixtures/fixture_show_snmp_contact_name.json +5 -0
- data/spec/fixtures/fixture_show_snmp_disabled.json +5 -0
- data/spec/fixtures/fixture_show_snmp_enabled.json +5 -0
- data/spec/fixtures/fixture_show_snmp_host.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_host_duplicates.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_host_more_duplicates.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_location_empty.json +5 -0
- data/spec/fixtures/fixture_show_snmp_trap.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_user.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_user_raw_text.yaml +1 -0
- data/spec/fixtures/fixture_show_vlan.json +37 -0
- data/spec/fixtures/fixture_show_vlan_3110.json +18 -0
- data/spec/fixtures/fixture_show_vlan_4000.json +18 -0
- data/spec/fixtures/fixture_snmp_host_opts.yaml +11 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/fixtures.rb +104 -0
- data/spec/unit/puppet_x/eos/eapi_spec.rb +182 -0
- data/spec/unit/puppet_x/eos/module_base_spec.rb +26 -0
- data/spec/unit/puppet_x/eos/modules/daemon_spec.rb +110 -0
- data/spec/unit/puppet_x/eos/modules/extension_spec.rb +197 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/daemon_getall.json +3 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/extension_getall.json +28 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/hostname.json +6 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/interface_getall.json +509 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/ipinterface_getall.json +56 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/mlag_get.json +21 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/mlag_get_interfaces.json +18 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/ntp_get.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/ospf_instance_getall.json +58 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_get.json +54 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_getlacpmode.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_getmembers.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_po1.json +7 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/snmp_get.json +14 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/switchport_get.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/switchport_get_et1.json +7 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/switchport_getall_interfaces.json +230 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_domain_list.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_domain_name.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_hostname.json +6 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_name_servers.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/vlan_getall.json +123 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/vxlan_get.json +24 -0
- data/spec/unit/puppet_x/eos/modules/interface_spec.rb +281 -0
- data/spec/unit/puppet_x/eos/modules/ipinterface_spec.rb +143 -0
- data/spec/unit/puppet_x/eos/modules/mlag_spec.rb +349 -0
- data/spec/unit/puppet_x/eos/modules/ntp_spec.rb +136 -0
- data/spec/unit/puppet_x/eos/modules/ospf_spec.rb +143 -0
- data/spec/unit/puppet_x/eos/modules/portchannel_spec.rb +357 -0
- data/spec/unit/puppet_x/eos/modules/radius_spec.rb +509 -0
- data/spec/unit/puppet_x/eos/modules/snmp_spec.rb +202 -0
- data/spec/unit/puppet_x/eos/modules/switchport_get_et1.json +7 -0
- data/spec/unit/puppet_x/eos/modules/switchport_spec.rb +307 -0
- data/spec/unit/puppet_x/eos/modules/system_spec.rb +170 -0
- data/spec/unit/puppet_x/eos/modules/tacacs_spec.rb +448 -0
- data/spec/unit/puppet_x/eos/modules/vlan_spec.rb +244 -0
- data/spec/unit/puppet_x/eos/modules/vxlan_spec.rb +189 -0
- data/spec/unit/puppet_x/eos/provider_spec.rb +35 -0
- data/spec/unit/puppet_x/net_dev/eos_api/common_methods_spec.rb +34 -0
- data/spec/unit/puppet_x/net_dev/eos_api/snmp_methods_spec.rb +842 -0
- data/spec/unit/puppet_x/net_dev/eos_api_spec.rb +1000 -0
- metadata +369 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014, Arista Networks, Inc.
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# Neither the name of Arista Networks nor the names of its
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
18
|
+
# this software without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
|
24
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
27
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
28
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
29
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
30
|
+
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
#
|
32
|
+
|
33
|
+
##
|
34
|
+
# Eos is the toplevel namespace for working with Arista EOS nodes
|
35
|
+
module PuppetX
|
36
|
+
##
|
37
|
+
# Eapi is module namesapce for working with the EOS command API
|
38
|
+
module Eos
|
39
|
+
##
|
40
|
+
# The Vxlan provides an instance for managing vxlan virtual tunnel
|
41
|
+
# interfaces in EOS
|
42
|
+
#
|
43
|
+
class Vxlan
|
44
|
+
def initialize(api)
|
45
|
+
@api = api
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Returns the vlan data for the provided id with the
|
50
|
+
# show vlan <id> command. If the id doesn't exist then
|
51
|
+
# nil is returned
|
52
|
+
#
|
53
|
+
#
|
54
|
+
# @return [nil, Hash<String, String|Hash|Array>] Hash describing the
|
55
|
+
# vlan configuration specified by id. If the id is not
|
56
|
+
# found then nil is returned
|
57
|
+
def get
|
58
|
+
@api.enable('show interfaces vxlan 1')
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Creates a new logical vxlan virtual interface in the running-config
|
63
|
+
#
|
64
|
+
# @return [Boolean] returns true if the command completed successfully
|
65
|
+
def create
|
66
|
+
@api.config('interface vxlan 1') == [{}]
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Deletes an existing vxlan logical interface from the running-config
|
71
|
+
#
|
72
|
+
# @return [Boolean] always returns true
|
73
|
+
def delete
|
74
|
+
@api.config('no interface vxlan 1') == [{}]
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Defaults an existing vxlan logical interface from the running-config)
|
79
|
+
#
|
80
|
+
# @return [Boolean] returns true if the command completed successfully
|
81
|
+
def default
|
82
|
+
@api.config('default interface vxlan 1') == [{}]
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Configures the source-interface parameter for the Vxlan interface
|
87
|
+
#
|
88
|
+
# @param [Hash] opts The configuration parameters for the VLAN
|
89
|
+
# @option opts [string] :value The value to set the name to
|
90
|
+
# @option opts [Boolean] :default The value should be set to default
|
91
|
+
#
|
92
|
+
# @return [Boolean] returns true if the command completed successfully
|
93
|
+
def set_source_interface(opts = {})
|
94
|
+
value = opts[:value]
|
95
|
+
default = opts[:default] || false
|
96
|
+
|
97
|
+
cmds = ['interface vxlan 1']
|
98
|
+
case default
|
99
|
+
when true
|
100
|
+
cmds << 'default vxlan source-interface'
|
101
|
+
when false
|
102
|
+
cmds << (value.nil? ? 'no vxlan source-interface' : \
|
103
|
+
"vxlan source-interface #{value}")
|
104
|
+
end
|
105
|
+
@api.config(cmds) == [{}, {}]
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Configures the multicast-group parameter for the Vxlan interface
|
110
|
+
#
|
111
|
+
# @param [Hash] opts The configuration parameters for the VLAN
|
112
|
+
# @option opts [string] :value The value to set the name to
|
113
|
+
# @option opts [Boolean] :default The value should be set to default
|
114
|
+
#
|
115
|
+
# @return [Boolean] returns true if the command completed successfully
|
116
|
+
def set_multicast_group(opts = {})
|
117
|
+
value = opts[:value]
|
118
|
+
default = opts[:default] || false
|
119
|
+
|
120
|
+
cmds = ['interface vxlan 1']
|
121
|
+
case default
|
122
|
+
when true
|
123
|
+
cmds << 'default vxlan multicast-group'
|
124
|
+
when false
|
125
|
+
cmds << (value.nil? ? 'no vxlan multicast-group' : \
|
126
|
+
"vxlan multicast-group #{value}")
|
127
|
+
end
|
128
|
+
@api.config(cmds) == [{}, {}]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014, Arista Networks, Inc.
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# Neither the name of Arista Networks nor the names of its
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
18
|
+
# this software without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
|
24
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
27
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
28
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
29
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
30
|
+
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
#
|
32
|
+
require 'puppet_x/eos/eapi'
|
33
|
+
require 'pathname'
|
34
|
+
|
35
|
+
##
|
36
|
+
# PuppetX namespace
|
37
|
+
module PuppetX
|
38
|
+
##
|
39
|
+
# Eos namesapece
|
40
|
+
module Eos
|
41
|
+
##
|
42
|
+
# EapiProviderMixin module
|
43
|
+
module EapiProviderMixin
|
44
|
+
def prefetch(resources)
|
45
|
+
provider_hash = instances.each_with_object({}) do |provider, hsh|
|
46
|
+
hsh[provider.name] = provider
|
47
|
+
end
|
48
|
+
|
49
|
+
resources.each_pair do |name, resource|
|
50
|
+
resource.provider = provider_hash[name] if provider_hash[name]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# conf loads a YAML file from '/mnt/flash/eapi.conf' if it exists. If it
|
56
|
+
# does not exist an empty hash is returned.
|
57
|
+
def conf
|
58
|
+
config_file = Pathname.new('/mnt/flash/eapi.conf')
|
59
|
+
if config_file.exist?
|
60
|
+
YAML.load_file(config_file.to_s)
|
61
|
+
else
|
62
|
+
Hash.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def eapi
|
67
|
+
@eapi ||= PuppetX::Eos::Eapi.new(conf)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014, Arista Networks, Inc.
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# Neither the name of Arista Networks nor the names of its
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
18
|
+
# this software without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
|
24
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
27
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
28
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
29
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
30
|
+
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
#
|
32
|
+
|
33
|
+
# #
|
34
|
+
# PuppetX namespace
|
35
|
+
module PuppetX
|
36
|
+
##
|
37
|
+
# Arista EOS namespace
|
38
|
+
module Eos
|
39
|
+
VERSION = '0.2.0'
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,1011 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'net_http_unix'
|
4
|
+
require 'puppet_x/net_dev/eos_api/common_methods'
|
5
|
+
require 'puppet_x/net_dev/eos_api/snmp_methods'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
##
|
9
|
+
# PuppetX is where utility extensions live.
|
10
|
+
module PuppetX
|
11
|
+
##
|
12
|
+
# NetDev is the module namespace for puppet supported
|
13
|
+
module NetDev
|
14
|
+
## ApiError is raised on REST API errors
|
15
|
+
class ApiError < Exception; end
|
16
|
+
|
17
|
+
##
|
18
|
+
# EosApi provides utility methods to interact with the eAPI using JSON-RPC.
|
19
|
+
# The API may be accessed over a normal TCP connection using the address
|
20
|
+
# and port settings, or a Unix domain socket using a `unix://...` address.
|
21
|
+
#
|
22
|
+
# @example Get all VLAN identifiers as strings
|
23
|
+
# >> api = EosApi.new(address: 'unix:///var/lib/eapi.sock')
|
24
|
+
# >> vlans = api.all_vlans
|
25
|
+
# >> vlans.keys
|
26
|
+
# => ['1', '3110']
|
27
|
+
class EosApi # rubocop:disable Metrics/ClassLength
|
28
|
+
# IP address or hostname of the REST api
|
29
|
+
attr_reader :address
|
30
|
+
# TCP port of the REST api
|
31
|
+
attr_reader :port
|
32
|
+
# API username
|
33
|
+
attr_reader :username
|
34
|
+
# API password
|
35
|
+
attr_reader :password
|
36
|
+
|
37
|
+
# Include type specific methods, broken out for clear organization.
|
38
|
+
include CommonMethods
|
39
|
+
include SnmpMethods
|
40
|
+
|
41
|
+
##
|
42
|
+
# initialize an API instance. The API will communicate with the HTTP
|
43
|
+
# server over TCP or a Unix Domain Socket. If a unix domain socket is
|
44
|
+
# being used then the address parameter should be set to the socket path.
|
45
|
+
# The port, username, and password are not necessary.
|
46
|
+
#
|
47
|
+
# @option opts [String] :address The address to connect to the HTTP
|
48
|
+
# server. This can be a hostname, address or the full path to a unix
|
49
|
+
# domain socket in the form of `unix:///path/to/socket`.
|
50
|
+
#
|
51
|
+
# @option opts [Fixnum] :port The TCP port for an IP connection to the
|
52
|
+
# HTTP API server.
|
53
|
+
#
|
54
|
+
# @option opts [String] :username ('admin') The username to log into the
|
55
|
+
# API server when using TCP/IP HTTP API connections. This option is
|
56
|
+
# not necessary when using a unix:// socket connection.
|
57
|
+
#
|
58
|
+
# @option opts [String] :password The password to use to log into the API
|
59
|
+
# server.
|
60
|
+
#
|
61
|
+
# @return [PuppetX::NetDev::EosApi]
|
62
|
+
#
|
63
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
64
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
65
|
+
def initialize(opts = {})
|
66
|
+
@address = opts[:address] || ENV['EOS_HOSTNAME'] || 'unix:///var/run/command-api.sock'
|
67
|
+
@port = opts[:port] || ENV['EOS_PORT'] || 80
|
68
|
+
@username = opts[:username] || ENV['EOS_USERNAME'] || 'admin'
|
69
|
+
@password = opts[:password] || ENV['EOS_PASSWORD'] || 'puppet'
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# vlan returns data about a specific VLAN identified by the VLAN ID
|
74
|
+
# number. This API call maps roughly to the `show vlan <id>` command.
|
75
|
+
# This method returns nil if no VLAN was found matching the ID provided.
|
76
|
+
#
|
77
|
+
# @param [Fixnum] id The VLAN id to obtain information about.
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
#
|
81
|
+
# @return [nil,Hash<String,Hash>] Hash describing the VLAN attributes, or
|
82
|
+
# nil if no vlan was found matching the id provided. The format of
|
83
|
+
# this hash matches the format of {all_vlans}
|
84
|
+
def vlan(id)
|
85
|
+
result = eapi_action("show vlan #{id}", 'list vlans')
|
86
|
+
result.first['vlans'] if result
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# vlan_create creates a VLAN that does not yet exist on the target
|
91
|
+
# device.
|
92
|
+
#
|
93
|
+
# @param [Fixnum] id The VLAN id to create
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
#
|
97
|
+
# @return [Boolean] true if the vlan was created
|
98
|
+
def vlan_create(id)
|
99
|
+
cmds = ['enable', 'configure', "vlan #{id}"]
|
100
|
+
eapi_action(cmds, "create vlan #{id}")
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# vlan_destroy destroys a vlan
|
105
|
+
#
|
106
|
+
# @param [Integer] id The VLAN ID to destroy
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def vlan_destroy(id)
|
110
|
+
cmds = ['enable', 'configure', "no vlan #{id}"]
|
111
|
+
eapi_action(cmds, "destroy vlan #{id}")
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# channel_group_destroy destroys a port channel group.
|
116
|
+
#
|
117
|
+
# @param [String] name The port channel name, e.g 'Port-Channel3'
|
118
|
+
#
|
119
|
+
# @api public
|
120
|
+
def channel_group_destroy(name)
|
121
|
+
# Need to remove all interfaces from the channel group.
|
122
|
+
port_channels = all_portchannels_detailed
|
123
|
+
channel_group = port_channels[name]
|
124
|
+
unless channel_group
|
125
|
+
msg = "#{name} is not in #{port_channels.keys.inspect}"
|
126
|
+
fail ArgumentError, msg
|
127
|
+
end
|
128
|
+
interfaces = channel_group['ports']
|
129
|
+
interfaces.each { |iface| interface_unset_channel_group(iface) }
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# interface_unset_channel_group removes a specific interface from all
|
134
|
+
# channel groups.
|
135
|
+
#
|
136
|
+
# @param [String] interface The interface name to remove from its
|
137
|
+
# associated channel group, e.g. 'Ethernet1'
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def interface_unset_channel_group(interface)
|
141
|
+
cmds = %w(enable configure) << "interface #{interface}"
|
142
|
+
cmds << 'no channel-group'
|
143
|
+
eapi_action(cmds, "remove #{interface} from channel group")
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# interface_set_channel_group configures an interface to be a member of a
|
148
|
+
# specified channel group ID.
|
149
|
+
#
|
150
|
+
# @param [String] interface The interface name to add to the channel
|
151
|
+
# group, e.g. 'Ethernet1'.
|
152
|
+
#
|
153
|
+
# @option opts [Fixnum] :group The group ID the interface will become a
|
154
|
+
# member of, e.g. 3.
|
155
|
+
#
|
156
|
+
# @option opts [Symbol] :mode (:active, :passive, :disabled) The LACP
|
157
|
+
# operating mode of the interface. Note, the only way to change the
|
158
|
+
# LACP mode is to delete the channel group and re-create the channel
|
159
|
+
# group.
|
160
|
+
#
|
161
|
+
# @api public
|
162
|
+
def interface_set_channel_group(interface, opts)
|
163
|
+
channel_group = opts[:group]
|
164
|
+
mode = case opts[:mode]
|
165
|
+
when :active, :passive then opts[:mode]
|
166
|
+
when :disabled then :on
|
167
|
+
else fail ArgumentError, "Unknown LACP mode #{opts[:mode]}"
|
168
|
+
end
|
169
|
+
|
170
|
+
cmd = %w(enable configure) << "interface #{interface}"
|
171
|
+
cmd << "channel-group #{channel_group} mode #{mode}"
|
172
|
+
msg = "join #{interface} to channel group #{channel_group}"
|
173
|
+
eapi_action(cmd, msg)
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
# port_channel_destroy destroys a port channel interface and removes all
|
178
|
+
# interfaces from the channel group.
|
179
|
+
#
|
180
|
+
# @param [String] name The name of the port channel interface, e.g
|
181
|
+
# 'Port-Channel3'
|
182
|
+
#
|
183
|
+
# @api public
|
184
|
+
def port_channel_destroy(name)
|
185
|
+
cmds = %w(enable configure) << "no interface #{name}"
|
186
|
+
eapi_action(cmds, "remove #{name}")
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# channel_group_create creates a channel group and associated port
|
191
|
+
# channel interface if the interface does not already exist.
|
192
|
+
#
|
193
|
+
# @param [String] name The name of the port channel interface, e.g.
|
194
|
+
# 'Port-Channel3'.
|
195
|
+
#
|
196
|
+
# @option opts [Symbol] :mode (:active, :passive, :disabled) The LACP
|
197
|
+
# operating mode of the interface. Note, the only way to change the
|
198
|
+
# LACP mode is to delete the channel group and re-create the channel
|
199
|
+
# group.
|
200
|
+
#
|
201
|
+
# @option opts [Symbol] :interfaces (['Ethernet1', 'Ethernet2']) The
|
202
|
+
# member interfaces of the channel group.
|
203
|
+
#
|
204
|
+
# @api public
|
205
|
+
def channel_group_create(name, opts)
|
206
|
+
channel_group = name.scan(/\d+/).first.to_i
|
207
|
+
interfaces = [*opts[:interfaces]]
|
208
|
+
if interfaces.empty?
|
209
|
+
fail ArgumentError, 'Cannot create a channel group with no interfaces'
|
210
|
+
end
|
211
|
+
interfaces.each do |interface|
|
212
|
+
set_opts = { mode: opts[:mode], group: channel_group }
|
213
|
+
interface_set_channel_group(interface, set_opts)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# set_vlan_name assigns a name to a vlan
|
219
|
+
#
|
220
|
+
# @param [Integer] id The vlan ID
|
221
|
+
#
|
222
|
+
# @param [String] name The vlan name
|
223
|
+
#
|
224
|
+
# @api public
|
225
|
+
def set_vlan_name(id, name)
|
226
|
+
cmds = ['enable', 'configure', "vlan #{id}"] << "name #{name}"
|
227
|
+
eapi_action(cmds, "set vlan #{id} name to #{name}")
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# set_vlan_state set a vlan to the state specified
|
232
|
+
#
|
233
|
+
# @param [Integer] id The vlan ID
|
234
|
+
#
|
235
|
+
# @param [String] state The state of the vlan, e.g. 'active' or
|
236
|
+
# 'suspend'
|
237
|
+
#
|
238
|
+
# @api public
|
239
|
+
def set_vlan_state(id, state)
|
240
|
+
cmds = ['enable', 'configure', "vlan #{id}"] << "state #{state}"
|
241
|
+
eapi_action(cmds, "set vlan #{id} state to #{state}")
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# all_vlans returns a hash of all vlans
|
246
|
+
#
|
247
|
+
# @example List all vlans
|
248
|
+
# api.all_vlans
|
249
|
+
# => {
|
250
|
+
# "1"=>{
|
251
|
+
# "status"=>"active",
|
252
|
+
# "name"=>"default",
|
253
|
+
# "interfaces"=>{
|
254
|
+
# "Ethernet2"=>{"privatePromoted"=>false},
|
255
|
+
# "Ethernet3"=>{"privatePromoted"=>false},
|
256
|
+
# "Ethernet1"=>{"privatePromoted"=>false},
|
257
|
+
# "Ethernet4"=>{"privatePromoted"=>false}},
|
258
|
+
# "dynamic"=>false},
|
259
|
+
# "3110"=>{
|
260
|
+
# "status"=>"active",
|
261
|
+
# "name"=>"VLAN3110",
|
262
|
+
# "interfaces"=>{},
|
263
|
+
# "dynamic"=>false}}
|
264
|
+
#
|
265
|
+
# @api public
|
266
|
+
#
|
267
|
+
# @return [Hash<String,Hash>]
|
268
|
+
def all_vlans
|
269
|
+
result = eapi_action('show vlan', 'list all vlans')
|
270
|
+
result.first['vlans']
|
271
|
+
end
|
272
|
+
|
273
|
+
##
|
274
|
+
# all_portchannels returns a hash of all port channels based on multiple
|
275
|
+
# sources of data from the API.
|
276
|
+
#
|
277
|
+
# @api public
|
278
|
+
#
|
279
|
+
# @return [Hash<String,Hash>] where the key is the port channel name,
|
280
|
+
# e.g. 'Port-Channel10'
|
281
|
+
def all_portchannels
|
282
|
+
detailed = all_portchannels_detailed
|
283
|
+
modes = all_portchannel_modes
|
284
|
+
# Merge the two
|
285
|
+
detailed.each_with_object(Hash.new) do |(name, attr), hsh|
|
286
|
+
hsh[name] = modes[name] ? attr.merge(modes[name]) : attr
|
287
|
+
hsh[name]['minimum_links'] = portchannel_min_links(name)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
##
|
292
|
+
# portchannel_min_links takes the name of a Port Channel interface and
|
293
|
+
# obtains the currently configured min-links value by parsing the text of
|
294
|
+
# the running configuration.
|
295
|
+
#
|
296
|
+
# @api private
|
297
|
+
#
|
298
|
+
# @return [Fixnum] the minimum number of links for the channel group to
|
299
|
+
# become active.
|
300
|
+
def portchannel_min_links(name)
|
301
|
+
api_commands = ['enable', "show running-config interfaces #{name}"]
|
302
|
+
result = eapi_action(api_commands,
|
303
|
+
'obtain port channel min links value',
|
304
|
+
format: 'text')
|
305
|
+
text = result[1]['output'] # skip over the enable command output.
|
306
|
+
parse_min_links(text)
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# set_portchannel_min_links Configures the minimum links value for a
|
311
|
+
# channel group.
|
312
|
+
#
|
313
|
+
# @param [String] name The port channel name, e.g 'Port-Channel4'.
|
314
|
+
#
|
315
|
+
# @param [Fixnum] min_links The minimum number of active links for the
|
316
|
+
# channel group to be active.
|
317
|
+
#
|
318
|
+
# @api public
|
319
|
+
def set_portchannel_min_links(name, min_links)
|
320
|
+
cmd = %w(enable configure)
|
321
|
+
cmd << "interface #{name}"
|
322
|
+
cmd << "port-channel min-links #{min_links}"
|
323
|
+
eapi_action(cmd, 'set port-channel min links')
|
324
|
+
end
|
325
|
+
|
326
|
+
##
|
327
|
+
# parse_min_links takes the text from the `show running-config interfaces
|
328
|
+
# Port-ChannelX` API command and parses out the currently configured
|
329
|
+
# number of minimum links. If there is no min-links value we (safely)
|
330
|
+
# assume it is configured to 0. Example output is:
|
331
|
+
#
|
332
|
+
# interface Port-Channel4
|
333
|
+
# description Office Backbone
|
334
|
+
# port-channel min-links 2
|
335
|
+
#
|
336
|
+
# @param [String] text The raw text output from the API.
|
337
|
+
#
|
338
|
+
# @api private
|
339
|
+
#
|
340
|
+
# @return [Fixnum] the number of minimum links
|
341
|
+
def parse_min_links(text)
|
342
|
+
re = /min-links\s+(\d+)/m
|
343
|
+
mdata = re.match(text)
|
344
|
+
mdata ? mdata[1].to_i : 0
|
345
|
+
end
|
346
|
+
|
347
|
+
##
|
348
|
+
# all_portchannels_detailed returns a hash of all port channels based on
|
349
|
+
# the `show etherchannel detailed` command.
|
350
|
+
#
|
351
|
+
# @api private
|
352
|
+
#
|
353
|
+
# @return [Hash<String,Hash>] where the key is the port channel name,
|
354
|
+
# e.g. 'Port-Channel10'
|
355
|
+
def all_portchannels_detailed
|
356
|
+
# JSON format is not supported in EOS 4.13.7M so use text format
|
357
|
+
result = eapi_action('show etherchannel detailed', 'list port channels',
|
358
|
+
format: 'text')
|
359
|
+
text = result.first['output']
|
360
|
+
parse_portchannels(text)
|
361
|
+
end
|
362
|
+
|
363
|
+
##
|
364
|
+
# all_portchannel_modes returns a hash of each of the port channel LACP
|
365
|
+
# modes. This method could be merged with the data from the
|
366
|
+
# all_portchannels method.
|
367
|
+
#
|
368
|
+
# @api private
|
369
|
+
#
|
370
|
+
# @return [Hash<String,Hash>] where the key is the port channel name,
|
371
|
+
# e.g. 'Port-Channel10'
|
372
|
+
def all_portchannel_modes
|
373
|
+
# JSON format is not supported in EOS 4.13.7M so use text format
|
374
|
+
result = eapi_action('show port-channel summary', 'get lag modes',
|
375
|
+
format: 'text')
|
376
|
+
text = result.first['output']
|
377
|
+
parse_portchannel_modes(text)
|
378
|
+
end
|
379
|
+
|
380
|
+
##
|
381
|
+
# Parse the portchannel modes from the text of the `show port-channel
|
382
|
+
# summary` command. The following is an example of two channel groups,
|
383
|
+
# one static, one active.
|
384
|
+
#
|
385
|
+
# rubocop:disable Metrics/LineLength, Metrics/MethodLength, Style/TrailingWhitespace
|
386
|
+
#
|
387
|
+
# Flags
|
388
|
+
# ------------------------ ---------------------------- -------------------------
|
389
|
+
# a - LACP Active p - LACP Passive * - static fallback
|
390
|
+
# F - Fallback enabled f - Fallback configured ^ - individual fallback
|
391
|
+
# U - In Use D - Down
|
392
|
+
# + - In-Sync - - Out-of-Sync i - incompatible with agg
|
393
|
+
# P - bundled in Po s - suspended G - Aggregable
|
394
|
+
# I - Individual S - ShortTimeout w - wait for agg
|
395
|
+
#
|
396
|
+
# Number of channels in use: 1
|
397
|
+
# Number of aggregators:1
|
398
|
+
#
|
399
|
+
# Port-Channel Protocol Ports
|
400
|
+
# ------------------ -------------- ----------------
|
401
|
+
# Po3(U) Static Et1(D) Et2(P)
|
402
|
+
# Po4(D) LACP(a) Et3(G-) Et4(G-)
|
403
|
+
#
|
404
|
+
# @api private
|
405
|
+
#
|
406
|
+
# @return [Hash<String,Hash>] where the key is the port channel name,
|
407
|
+
# e.g. 'Port-Channel10'
|
408
|
+
def parse_portchannel_modes(text)
|
409
|
+
lines = text.lines.each_with_object(Array.new) do |v, ary|
|
410
|
+
ary << v.chomp if /^\s*Po\d/.match(v)
|
411
|
+
end
|
412
|
+
lines.each_with_object(Hash.new) do |line, hsh|
|
413
|
+
mdata = /^\s+Po(\d+).*?\s+([a-zA-Z()0-9_-]+)/.match(line)
|
414
|
+
idx = mdata[1]
|
415
|
+
protocol = mdata[2]
|
416
|
+
mode = case protocol
|
417
|
+
when 'Static' then :disabled
|
418
|
+
when /LACP/
|
419
|
+
flags = /\((.*?)\)/.match(protocol)[1]
|
420
|
+
if flags.include? 'p' then :passive
|
421
|
+
elsif flags.include? 'a' then :active
|
422
|
+
end
|
423
|
+
end
|
424
|
+
hsh["Port-Channel#{idx}"] = { 'mode' => mode }
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
##
|
429
|
+
# get_flowcontrol obtains the configured flow_control send and receive
|
430
|
+
# values from the target device.
|
431
|
+
#
|
432
|
+
# @param [String] name The interface name, e.g. 'Ethernet1'
|
433
|
+
#
|
434
|
+
# @api public
|
435
|
+
#
|
436
|
+
# @return [Hash<Symbol,String>] e.g. { send: 'on', receive: 'off' }
|
437
|
+
def get_flowcontrol(name)
|
438
|
+
cmd = "show flowcontrol interface #{name}"
|
439
|
+
result = eapi_action(cmd, 'get flowcontrol config', format: 'text')
|
440
|
+
text = result.first['output']
|
441
|
+
parse_flowcontrol_single(text)
|
442
|
+
end
|
443
|
+
|
444
|
+
##
|
445
|
+
# parse_flowcontrol_single parses the text output of the `show
|
446
|
+
# flowcontrol <interface>` command where there is a single entry for the
|
447
|
+
# named interface.
|
448
|
+
#
|
449
|
+
# Port Send FlowControl Receive FlowControl RxPause TxPause
|
450
|
+
# admin oper admin oper
|
451
|
+
# ---------- -------- -------- -------- -------- ------------- -------------
|
452
|
+
# Et1 off unknown off unknown 0 0
|
453
|
+
#
|
454
|
+
# @param [String] text The text to parse
|
455
|
+
#
|
456
|
+
# @api private
|
457
|
+
#
|
458
|
+
# @return [Hash<Symbol,String>] e.g. { send: 'on', receive: 'off' }
|
459
|
+
def parse_flowcontrol_single(text)
|
460
|
+
re = /----\n(.*?)\s+(.*?)\s+.*?\s+(.*?)\s+.*\n/m
|
461
|
+
mdata = re.match(text)
|
462
|
+
if mdata
|
463
|
+
{ send: mdata[2], receive: mdata[3] }
|
464
|
+
else
|
465
|
+
fail ArgumentError, 'could not parse flowcontrol'
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
##
|
470
|
+
# set_flowcontrol_send Configures a specific interface's flow control
|
471
|
+
# send value.
|
472
|
+
#
|
473
|
+
# @param [String] name The name of the interface to configure, e.g.
|
474
|
+
# 'Ethernet1'
|
475
|
+
#
|
476
|
+
# @param [Symbol] value the value to configure, e.g. `:on`, `:off`
|
477
|
+
#
|
478
|
+
# @api public
|
479
|
+
def set_flowcontrol_send(name, value)
|
480
|
+
cmd = %w(enable configure) << "interface #{name}"
|
481
|
+
cmd << "flowcontrol send #{value}"
|
482
|
+
eapi_action(cmd, 'configure flowcontrol send')
|
483
|
+
end
|
484
|
+
|
485
|
+
##
|
486
|
+
# set_flowcontrol_recv Configures a specific interface's flow control
|
487
|
+
# receive value.
|
488
|
+
#
|
489
|
+
# @param [String] name The name of the interface to configure, e.g.
|
490
|
+
# 'Ethernet1'
|
491
|
+
#
|
492
|
+
# @param [Symbol] value the value to configure, e.g. `:on`, `:off`
|
493
|
+
#
|
494
|
+
# @api public
|
495
|
+
def set_flowcontrol_recv(name, value)
|
496
|
+
cmd = %w(enable configure) << "interface #{name}"
|
497
|
+
cmd << "flowcontrol receive #{value}"
|
498
|
+
eapi_action(cmd, 'configure flowcontrol receive')
|
499
|
+
end
|
500
|
+
|
501
|
+
##
|
502
|
+
# all_interfaces returns a hash of all interfaces
|
503
|
+
#
|
504
|
+
# @api public
|
505
|
+
#
|
506
|
+
# @return [Hash<String,Hash>] where the key is the interface name, e.g.
|
507
|
+
# 'Management1'
|
508
|
+
def all_interfaces
|
509
|
+
result = eapi_action('show interfaces', 'list all interfaces')
|
510
|
+
result.first['interfaces']
|
511
|
+
end
|
512
|
+
|
513
|
+
##
|
514
|
+
# set_interface_state enables or disables a network interface
|
515
|
+
#
|
516
|
+
# @param [String] name The interface name, e.g. 'Ethernet1'
|
517
|
+
#
|
518
|
+
# @param [String] state The interface state, e.g. 'no shutdown' or
|
519
|
+
# 'shutdown'
|
520
|
+
#
|
521
|
+
# @api public
|
522
|
+
def set_interface_state(name, state)
|
523
|
+
cmd = %w(enable configure) << "interface #{name}" << state
|
524
|
+
eapi_action(cmd, "set interface #{name} state to #{state}")
|
525
|
+
end
|
526
|
+
|
527
|
+
##
|
528
|
+
# set_interface_description configures the description string for an
|
529
|
+
# interface.
|
530
|
+
#
|
531
|
+
# @param [String] name The interface name, e.g. 'Ethernet1'
|
532
|
+
#
|
533
|
+
# @param [String] description The description to assign the interface.
|
534
|
+
#
|
535
|
+
# @api public
|
536
|
+
def set_interface_description(name, description)
|
537
|
+
cmd = %w(enable configure) << "interface #{name}"
|
538
|
+
cmd << "description #{description}"
|
539
|
+
eapi_action(cmd, "set interface #{name} description to #{description}")
|
540
|
+
end
|
541
|
+
|
542
|
+
##
|
543
|
+
# set_interface_speed enable a network interface
|
544
|
+
#
|
545
|
+
# @param [String] name The interface name, e.g. 'Ethernet1'
|
546
|
+
#
|
547
|
+
# @param [String] speed The interface state, e.g. '1000full' or
|
548
|
+
# '40gfull'
|
549
|
+
#
|
550
|
+
# @api public
|
551
|
+
def set_interface_speed(name, speed)
|
552
|
+
cmd = %w(enable configure) << "interface #{name}"
|
553
|
+
cmd << "speed forced #{speed}"
|
554
|
+
eapi_action(cmd, "set interface #{name} speed to #{speed}")
|
555
|
+
end
|
556
|
+
|
557
|
+
##
|
558
|
+
# set_interface_mtu configures the interface MTU
|
559
|
+
#
|
560
|
+
# @param [String] name The interface name, e.g. 'Ethernet1'
|
561
|
+
#
|
562
|
+
# @param [Fixnum] mtu The interface mtu, e.g. 9000
|
563
|
+
#
|
564
|
+
# @api public
|
565
|
+
def set_interface_mtu(name, mtu)
|
566
|
+
cmd = %w(enable configure) << "interface #{name}"
|
567
|
+
cmd << "mtu #{mtu}"
|
568
|
+
eapi_action(cmd, "set interface #{name} mtu to #{mtu}")
|
569
|
+
end
|
570
|
+
|
571
|
+
##
|
572
|
+
# format_error takes the value of the 'error' key from the EOS API
|
573
|
+
# response and formats the error strings into a string suitable for error
|
574
|
+
# messages.
|
575
|
+
#
|
576
|
+
# @param [Array<Hash>] data Array of data from the API response, this
|
577
|
+
# will be lcoated in the sub-key api_response['error']['data']
|
578
|
+
#
|
579
|
+
# @api private
|
580
|
+
#
|
581
|
+
# @return [String] the human readable error message
|
582
|
+
def format_error(data)
|
583
|
+
if data
|
584
|
+
data.each_with_object([]) do |i, ary|
|
585
|
+
ary.push(*i['errors']) if i['errors']
|
586
|
+
end.join(', ')
|
587
|
+
else
|
588
|
+
'unknown error'
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
##
|
593
|
+
# http returns a memoized HTTP client instance conforming to the
|
594
|
+
# Net::HTTP interface.
|
595
|
+
#
|
596
|
+
# @api public
|
597
|
+
#
|
598
|
+
# @return [NetX::HttpUnix]
|
599
|
+
def http
|
600
|
+
@http ||= NetX::HTTPUnix.new(address, port)
|
601
|
+
end
|
602
|
+
|
603
|
+
##
|
604
|
+
# format_command takes an EOS command as a string and returns the
|
605
|
+
# appropriate data structure for use with the EOS REST API.
|
606
|
+
#
|
607
|
+
# @param [String, Array<String>] command The command to execute on the
|
608
|
+
# switch, e.g. 'show vlan' or ['show vlan 1', 'show vlan 2'].
|
609
|
+
#
|
610
|
+
# @option opts [String] :id The identifier for this request. If omitted,
|
611
|
+
# a unique identifier will be generated.
|
612
|
+
#
|
613
|
+
# @option opts [String] :format ('json') The desired format of the
|
614
|
+
# response, e.g. 'text' or 'json'. Defaults to 'json' if not provided.
|
615
|
+
#
|
616
|
+
# @api private
|
617
|
+
#
|
618
|
+
# @return [String] The JSON string suitable for use with HTTP POST API
|
619
|
+
# calls to the EOS API.
|
620
|
+
def format_command(command, options = {})
|
621
|
+
cmds = [*command]
|
622
|
+
req_id = options[:id].nil? ? SecureRandom.uuid : options[:id]
|
623
|
+
format = options[:format].nil? ? 'json' : options[:format]
|
624
|
+
params = { 'version' => 1, 'cmds' => cmds, 'format' => format }
|
625
|
+
request = {
|
626
|
+
'jsonrpc' => '2.0', 'method' => 'runCmds',
|
627
|
+
'params' => params, 'id' => req_id
|
628
|
+
}
|
629
|
+
JSON.dump(request)
|
630
|
+
end
|
631
|
+
private :format_command
|
632
|
+
|
633
|
+
##
|
634
|
+
# parse_portchannels accepts the text output of the `show etherchannel
|
635
|
+
# detailed` command and parses the text into structured data with the
|
636
|
+
# portchannel names as keys and portchannel attributes as key/values in a
|
637
|
+
# hash.
|
638
|
+
#
|
639
|
+
# @param [String] text The text output to parse.
|
640
|
+
#
|
641
|
+
# @api private
|
642
|
+
#
|
643
|
+
# @return [Hash<String,Hash>] where the key is the port channel name,
|
644
|
+
# e.g. 'Port-Channel10'
|
645
|
+
def parse_portchannels(text) # rubocop:disable Metrics/MethodLength
|
646
|
+
groups = text.split('Port Channel ')
|
647
|
+
groups.each_with_object({}) do |str, group|
|
648
|
+
lines = [*str.lines]
|
649
|
+
name = parse_portchannel_name(lines.shift)
|
650
|
+
next unless name
|
651
|
+
active_ports = parse_portchannel_active_ports(lines)
|
652
|
+
configured_ports = parse_portchannel_configured_ports(lines)
|
653
|
+
group[name] = {
|
654
|
+
'name' => name,
|
655
|
+
'ports' => [*active_ports, *configured_ports].sort
|
656
|
+
}
|
657
|
+
end
|
658
|
+
end
|
659
|
+
private :parse_portchannels
|
660
|
+
|
661
|
+
##
|
662
|
+
# parse_portchannel_active_ports takes a portchannel section from `show
|
663
|
+
# port-channel detailed` and parses all of the active ports from the
|
664
|
+
# section.
|
665
|
+
#
|
666
|
+
# @param [Array<String>] lines Array of string lines for the section,
|
667
|
+
#
|
668
|
+
# @api private
|
669
|
+
#
|
670
|
+
# @return [Array<String>] Array of string port names, e.g. ['Ethernet1',
|
671
|
+
# 'Ethernet2']
|
672
|
+
def parse_portchannel_active_ports(group_lines)
|
673
|
+
lines = group_lines.dup
|
674
|
+
# Check if there are no active ports
|
675
|
+
mdata = /(No)? Active Ports/.match(lines.shift)
|
676
|
+
return [] if mdata[1] # return if there are none
|
677
|
+
lines.shift until /^\s*Port /.match(lines.first) || lines.empty?
|
678
|
+
lines.shift(2) # heading line and ---- line
|
679
|
+
# Read interfaces until the first blank line
|
680
|
+
lines.each_with_object([]) do |l, a|
|
681
|
+
l.chomp!
|
682
|
+
break a if l.empty?
|
683
|
+
a << l.split.first
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
##
|
688
|
+
# parse_portchannel_configured_ports takes a portchannel section from
|
689
|
+
# `show port-channel detailed` and parses all of the active ports from
|
690
|
+
# the section.
|
691
|
+
#
|
692
|
+
# @param [Array<String>] lines Array of string lines for the section,
|
693
|
+
#
|
694
|
+
# @api private
|
695
|
+
#
|
696
|
+
# @return [Array<String>] Array of string port names, e.g. ['Ethernet1',
|
697
|
+
# 'Ethernet2']
|
698
|
+
def parse_portchannel_configured_ports(group_lines)
|
699
|
+
lines = group_lines.dup
|
700
|
+
# Check if there are no active ports
|
701
|
+
lines.shift until /inactive ports/.match(lines.first) || lines.empty?
|
702
|
+
return [] if lines.empty?
|
703
|
+
lines.shift(3)
|
704
|
+
lines.each_with_object([]) do |l, a|
|
705
|
+
l.chomp!
|
706
|
+
break a if l.empty?
|
707
|
+
a << l.split.first
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
##
|
712
|
+
# parse_portchannel_name parses out the portchannel name from the first
|
713
|
+
# line of a group section.
|
714
|
+
#
|
715
|
+
# @param [String] line The first line of a portchannel group detailed
|
716
|
+
# show statement, e.g. 'Port Channel Port-Channel1 (Fallback State:
|
717
|
+
# Unconfigured):'
|
718
|
+
#
|
719
|
+
# @api private
|
720
|
+
#
|
721
|
+
# @return [String,nil] the name of the portchannel group or nil if the
|
722
|
+
# name could not be parsed.
|
723
|
+
def parse_portchannel_name(line)
|
724
|
+
mdata = /^(?:Port Channel )?(Port-Channel\d+)/.match(line)
|
725
|
+
mdata[1] if mdata
|
726
|
+
end
|
727
|
+
private :parse_portchannel_name
|
728
|
+
|
729
|
+
##
|
730
|
+
# eapi_request returns a Net::HTTP::Post instance suitable for use with
|
731
|
+
# the http client to make an API call to EOS. The request will
|
732
|
+
# automatically be initialized with an username and password if the
|
733
|
+
# attributes have been initialized.
|
734
|
+
#
|
735
|
+
# @param [String] request_body The data to post to the API represented as
|
736
|
+
# a string, usually JSON encoded.
|
737
|
+
#
|
738
|
+
# @api private
|
739
|
+
#
|
740
|
+
# @return [Net::HTTP::Post] A request instance suitable for use with
|
741
|
+
# Net::HTTP#request
|
742
|
+
def eapi_request(request_body)
|
743
|
+
# JSON-RPC 2.0 to /command-api/ location
|
744
|
+
req = Net::HTTP::Post.new('/command-api/')
|
745
|
+
req.basic_auth(username, password) if username && password
|
746
|
+
req.body = request_body
|
747
|
+
req
|
748
|
+
end
|
749
|
+
private :eapi_request
|
750
|
+
|
751
|
+
##
|
752
|
+
# eapi_call takes a string as an arista command and executes the command
|
753
|
+
# on the switch using the eAPI. This method decodes the API response and
|
754
|
+
# returns the value. For example:
|
755
|
+
#
|
756
|
+
# [1] pry(#<PuppetX::NetDev::EosApi>)> eapi_call('show version')
|
757
|
+
# => {"jsonrpc"=>"2.0",
|
758
|
+
# "result"=>
|
759
|
+
# [{"modelName"=>"vEOS",
|
760
|
+
# "internalVersion"=>"4.13.7M-1877079.4137M.1",
|
761
|
+
# "systemMacAddress"=>"00:42:00:08:17:78",
|
762
|
+
# "serialNumber"=>"",
|
763
|
+
# "memTotal"=>2033744,
|
764
|
+
# "bootupTimestamp"=>1403732020.05,
|
765
|
+
# "memFree"=>143688,
|
766
|
+
# "version"=>"4.13.7M",
|
767
|
+
# "architecture"=>"i386",
|
768
|
+
# "internalBuildId"=>"54a9c4ce-bbb0-4f6b-9448-9507de824905",
|
769
|
+
# "hardwareRevision"=>""}],
|
770
|
+
# "id"=>"a4e14732-e0f2-430d-823e-1c801273ec60"}
|
771
|
+
#
|
772
|
+
# @param [String,Array<String>] command The command or commands to
|
773
|
+
# execute, e.g. 'show vlan'
|
774
|
+
#
|
775
|
+
# @option opts [String] :id The identifier for this request. If omitted,
|
776
|
+
# a unique identifier will be generated.
|
777
|
+
#
|
778
|
+
# @option opts [String] :format ('json') The desired format of the
|
779
|
+
# response, e.g. 'text' or 'json'. Defaults to 'json' if not provided.
|
780
|
+
#
|
781
|
+
# @api private
|
782
|
+
#
|
783
|
+
# @return [Hash] the response from the API
|
784
|
+
def eapi_call(command, options = {})
|
785
|
+
cmds = [*command]
|
786
|
+
request_body = format_command(cmds, options)
|
787
|
+
req = eapi_request(request_body)
|
788
|
+
resp = http.request(req)
|
789
|
+
decoded_response = JSON.parse(resp.body)
|
790
|
+
decoded_response
|
791
|
+
end
|
792
|
+
private :eapi_call
|
793
|
+
|
794
|
+
##
|
795
|
+
# eapi_action makes an API call and handles any error messages in the
|
796
|
+
# return value.
|
797
|
+
#
|
798
|
+
# @param [String,Array<String>] command The command or commands to
|
799
|
+
# execute, e.g. 'show vlan'
|
800
|
+
#
|
801
|
+
# @param [String] action The action being performed, e.g. 'set interface
|
802
|
+
# description'. Used to format error messages on API errors.
|
803
|
+
#
|
804
|
+
# @option opts [String] :id The identifier for this request. If omitted,
|
805
|
+
# a unique identifier will be generated.
|
806
|
+
#
|
807
|
+
# @option opts [String] :format ('json') The desired format of the
|
808
|
+
# response, e.g. 'text' or 'json'. Defaults to 'json' if not provided.
|
809
|
+
#
|
810
|
+
# @api private
|
811
|
+
#
|
812
|
+
# @return [Array<Hash>] the value of the 'result' key from the API
|
813
|
+
# response.
|
814
|
+
def eapi_action(command, action = 'make api call', options = {})
|
815
|
+
api_response = eapi_call(command, options)
|
816
|
+
|
817
|
+
return api_response['result'] unless api_response['error']
|
818
|
+
err_msg = format_error(api_response['error']['data'])
|
819
|
+
fail ApiError, "could not #{action}: #{err_msg}"
|
820
|
+
end
|
821
|
+
private :eapi_action
|
822
|
+
|
823
|
+
##
|
824
|
+
# @return [URI] the URI of the server
|
825
|
+
def uri
|
826
|
+
return @uri if @uri
|
827
|
+
if username && password
|
828
|
+
@uri = URI("http://#{username}:#{password}@#{address}:#{port}")
|
829
|
+
else
|
830
|
+
@uri = URI("http://#{address}:#{port}")
|
831
|
+
end
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
##
|
836
|
+
# EosProviderMethods is meant to be mixed into the provider to make api
|
837
|
+
# methods available.
|
838
|
+
module EosProviderMethods
|
839
|
+
##
|
840
|
+
# api returns a memoized instance of the EosApi. This method is intended
|
841
|
+
# to be used from providers that have mixed in the EosProviderMethods
|
842
|
+
# module.
|
843
|
+
#
|
844
|
+
# @return [PuppetX::NetDev::EosApi] api instance
|
845
|
+
def api
|
846
|
+
@api ||= EosApi.new
|
847
|
+
end
|
848
|
+
|
849
|
+
##
|
850
|
+
# bandwidth_to_speed converts a raw bandwidth integer to a Link speed
|
851
|
+
# [10m|100m|1g|10g|40g|56g|100g]
|
852
|
+
#
|
853
|
+
# @param [Fixnum] bandwidth The bandwdith value in bytes per second
|
854
|
+
#
|
855
|
+
# @api public
|
856
|
+
#
|
857
|
+
# @return [String] Link speed [10m|100m|1g|10g|40g|56g|100g]
|
858
|
+
def bandwidth_to_speed(bandwidth)
|
859
|
+
if bandwidth >= 1_000_000_000
|
860
|
+
"#{(bandwidth / 1_000_000_000).to_i}g"
|
861
|
+
else
|
862
|
+
"#{(bandwidth / 1_000_000).to_i}m"
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
##
|
867
|
+
# duplex_to_value Convert a duplex string from the API response to the
|
868
|
+
# provider value
|
869
|
+
#
|
870
|
+
# @param [String] duplex The value from the API response
|
871
|
+
#
|
872
|
+
# @api public
|
873
|
+
#
|
874
|
+
# @return [Symbol] the value for the provider
|
875
|
+
def duplex_to_value(duplex)
|
876
|
+
case duplex
|
877
|
+
when 'duplexFull' then :full
|
878
|
+
when 'duplexHalf' then :half
|
879
|
+
else fail ArgumentError, "Unknown duplex value #{duplex.inspect}"
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
##
|
884
|
+
# interface_status_to_enable maps the interfaceStatus attribute of the
|
885
|
+
# API response to the enable state of :true or :false
|
886
|
+
#
|
887
|
+
# The interfaceStatus reflects realtime status so its a bit funny how it
|
888
|
+
# works. If interfaceStatus == 'disabled' then the interface is
|
889
|
+
# administratively disabled (ie configured to be disabled) otherwise its
|
890
|
+
# enabled (ie no shutdown). So in your conversion here you can just
|
891
|
+
# reflect if interfaceStatus == 'disabled' or not as the state.
|
892
|
+
#
|
893
|
+
# @param [String] status the value of interfaceStatus returned by the API
|
894
|
+
#
|
895
|
+
# @return [Symbol] :true or :false
|
896
|
+
def interface_status_to_enable(status)
|
897
|
+
status == 'disabled' ? :false : :true
|
898
|
+
end
|
899
|
+
|
900
|
+
##
|
901
|
+
# interface_attributes takes an attribute hash from the EOS API and maps
|
902
|
+
# the values to provider attributes for the network_interface type.
|
903
|
+
#
|
904
|
+
# @param [Hash] attr_hash Interface attribute hash
|
905
|
+
#
|
906
|
+
# @api public
|
907
|
+
#
|
908
|
+
# @return [Hash] provider attributes suitable for merge into a provider
|
909
|
+
# hash that will be passed to the provider initializer.
|
910
|
+
def interface_attributes(attr_hash)
|
911
|
+
hsh = {}
|
912
|
+
status = attr_hash['interfaceStatus']
|
913
|
+
hsh[:enable] = interface_status_to_enable(status)
|
914
|
+
hsh[:mtu] = attr_hash['mtu']
|
915
|
+
hsh[:speed] = bandwidth_to_speed(attr_hash['bandwidth'])
|
916
|
+
hsh[:duplex] = duplex_to_value(attr_hash['duplex'])
|
917
|
+
hsh[:description] = attr_hash['description']
|
918
|
+
hsh
|
919
|
+
end
|
920
|
+
|
921
|
+
##
|
922
|
+
# port_channel_attributes takes an attribute hash from the EOS API and
|
923
|
+
# maps the values to provider attributes for the port_channel type.
|
924
|
+
#
|
925
|
+
# @param [Hash] attr_hash Interface attribute hash
|
926
|
+
#
|
927
|
+
# @api public
|
928
|
+
#
|
929
|
+
# @return [Hash] provider attributes suitable for merge into a provider
|
930
|
+
# hash that will be passed to the provider initializer.
|
931
|
+
def port_channel_attributes(attr_hash)
|
932
|
+
hsh = {}
|
933
|
+
hsh[:speed] = bandwidth_to_speed(attr_hash['bandwidth'])
|
934
|
+
hsh[:description] = attr_hash['description']
|
935
|
+
hsh
|
936
|
+
end
|
937
|
+
|
938
|
+
##
|
939
|
+
# flush_speed_and_duplex consolidates the duplex and speed settings into one
|
940
|
+
# API call to manage the interface speed.
|
941
|
+
#
|
942
|
+
# @param [String] name The name of the interface, e.g. 'Ethernet1'
|
943
|
+
def flush_speed_and_duplex(name)
|
944
|
+
speed = convert_speed(@property_flush[:speed])
|
945
|
+
duplex = @property_flush[:duplex]
|
946
|
+
return nil unless speed || duplex
|
947
|
+
|
948
|
+
speed_out = speed ? speed : convert_speed(@property_hash[:speed])
|
949
|
+
duplex_out = duplex ? duplex.downcase : @property_hash[:duplex].to_s
|
950
|
+
|
951
|
+
api.set_interface_speed(name, "#{speed_out}#{duplex_out}")
|
952
|
+
end
|
953
|
+
|
954
|
+
##
|
955
|
+
# convert_speed takes a speed value from the catalog as a string and converts
|
956
|
+
# it to a speed prefix suitable for the Arista API. The following table is
|
957
|
+
# used to perform the conversion.
|
958
|
+
#
|
959
|
+
# 10000full Disable autoneg and force 10 Gbps/full duplex operation
|
960
|
+
# 1000full Disable autoneg and force 1 Gbps/full duplex operation
|
961
|
+
# 1000half Disable autoneg and force 1 Gbps/half duplex operation
|
962
|
+
# 100full Disable autoneg and force 100 Mbps/full duplex operation
|
963
|
+
# 100gfull Disable autoneg and force 100 Gbps/full duplex operation
|
964
|
+
# 100half Disable autoneg and force 100 Mbps/half duplex operation
|
965
|
+
# 10full Disable autoneg and force 10 Mbps/full duplex operation
|
966
|
+
# 10half Disable autoneg and force 10 Mbps/half duplex operation
|
967
|
+
# 40gfull Disable autoneg and force 40 Gbps/full duplex operation
|
968
|
+
#
|
969
|
+
# @param [String] speed The speed specified in the catalog, e.g. 1g
|
970
|
+
#
|
971
|
+
# @api private
|
972
|
+
#
|
973
|
+
# @return [String] The speed for the API, e.g. 1000
|
974
|
+
def convert_speed(value)
|
975
|
+
speed = value.to_s
|
976
|
+
if /g$/i.match(speed) && (speed.to_i > 40)
|
977
|
+
speed
|
978
|
+
elsif /g$/i.match(speed)
|
979
|
+
(speed.to_i * 1000).to_s
|
980
|
+
elsif /m$/i.match(speed)
|
981
|
+
speed.to_i.to_s
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
##
|
987
|
+
# EosProviderClassMethods implements common methods, e.g. `self.prefetch`
|
988
|
+
# for EOS providers.
|
989
|
+
module EosProviderClassMethods
|
990
|
+
##
|
991
|
+
# prefetch associates resources declared in the Puppet catalog with
|
992
|
+
# resources discovered on the system using the instances class method.
|
993
|
+
# Each resource that has a matching provider in the instances list will
|
994
|
+
# have the provider bound to the resource.
|
995
|
+
#
|
996
|
+
# @param [Hash] resources The set of resources declared in the catalog.
|
997
|
+
#
|
998
|
+
# @return [Hash<String,Puppet::Type>] catalog resources with updated
|
999
|
+
# provider instances.
|
1000
|
+
def prefetch(resources)
|
1001
|
+
provider_hash = instances.each_with_object({}) do |provider, hsh|
|
1002
|
+
hsh[provider.name] = provider
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
resources.each_pair do |name, resource|
|
1006
|
+
resource.provider = provider_hash[name] if provider_hash[name]
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
end
|