net-connector 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +82 -0
- data/LICENSE +21 -0
- data/README.md +277 -0
- data/docs/RELEASING.md +81 -0
- data/docs/VERIFICATION.md +73 -0
- data/docs/architecture.md +268 -0
- data/exe/net-connector-backup +6 -0
- data/lib/net/connector/device/base.rb +368 -0
- data/lib/net/connector/device/interface_description.rb +41 -0
- data/lib/net/connector/device/interface_name.rb +56 -0
- data/lib/net/connector/device/profile.rb +446 -0
- data/lib/net/connector/device/running_config/strategy.rb +29 -0
- data/lib/net/connector/device/running_config.rb +69 -0
- data/lib/net/connector/engine/authentication.rb +77 -0
- data/lib/net/connector/engine/base.rb +4 -0
- data/lib/net/connector/engine/command.rb +101 -0
- data/lib/net/connector/engine/configuration.rb +131 -0
- data/lib/net/connector/engine/core.rb +6 -0
- data/lib/net/connector/engine/dialogue.rb +223 -0
- data/lib/net/connector/engine/errors.rb +183 -0
- data/lib/net/connector/engine/execution.rb +64 -0
- data/lib/net/connector/engine/log.rb +227 -0
- data/lib/net/connector/engine/log_messages.rb +47 -0
- data/lib/net/connector/engine/profile.rb +4 -0
- data/lib/net/connector/engine/recovery.rb +57 -0
- data/lib/net/connector/engine/result.rb +67 -0
- data/lib/net/connector/engine/session.rb +333 -0
- data/lib/net/connector/engine/terminal_renderer.rb +158 -0
- data/lib/net/connector/engine/transport.rb +184 -0
- data/lib/net/connector/engine.rb +4 -0
- data/lib/net/connector/netdisco/batch.rb +71 -0
- data/lib/net/connector/netdisco/cli.rb +142 -0
- data/lib/net/connector/netdisco/client.rb +148 -0
- data/lib/net/connector/netdisco/config_file.rb +87 -0
- data/lib/net/connector/netdisco/device.rb +75 -0
- data/lib/net/connector/netdisco/fleet.rb +258 -0
- data/lib/net/connector/netdisco/planner.rb +77 -0
- data/lib/net/connector/netdisco/result_store.rb +43 -0
- data/lib/net/connector/netdisco/rules.rb +131 -0
- data/lib/net/connector/netdisco/settings.rb +183 -0
- data/lib/net/connector/netdisco/worker.rb +75 -0
- data/lib/net/connector/netdisco.rb +14 -0
- data/lib/net/connector/operations/local_backup.rb +49 -0
- data/lib/net/connector/operations/parse_output.rb +60 -0
- data/lib/net/connector/operations/private_file.rb +25 -0
- data/lib/net/connector/operations/running_config/cisco.rb +14 -0
- data/lib/net/connector/operations/running_config/hillstone.rb +12 -0
- data/lib/net/connector/operations/running_config/palo_alto.rb +12 -0
- data/lib/net/connector/operations/running_config/strategy.rb +3 -0
- data/lib/net/connector/operations/running_config.rb +16 -0
- data/lib/net/connector/operations/saved_config.rb +72 -0
- data/lib/net/connector/operations/tftp/cisco_ios.rb +11 -0
- data/lib/net/connector/operations/tftp/cisco_nxos.rb +11 -0
- data/lib/net/connector/operations/tftp/h3c.rb +11 -0
- data/lib/net/connector/operations/tftp/hillstone.rb +11 -0
- data/lib/net/connector/operations/tftp/huawei.rb +11 -0
- data/lib/net/connector/operations/tftp/palo_alto.rb +11 -0
- data/lib/net/connector/operations/tftp/radware.rb +11 -0
- data/lib/net/connector/operations/tftp/strategy.rb +77 -0
- data/lib/net/connector/operations/tftp_backup.rb +120 -0
- data/lib/net/connector/operations/topology/cisco.rb +11 -0
- data/lib/net/connector/operations/topology/h3c.rb +11 -0
- data/lib/net/connector/operations/topology/hillstone.rb +11 -0
- data/lib/net/connector/operations/topology/palo_alto.rb +11 -0
- data/lib/net/connector/operations/topology/radware.rb +11 -0
- data/lib/net/connector/operations/topology/strategy.rb +53 -0
- data/lib/net/connector/operations/topology.rb +192 -0
- data/lib/net/connector/operations.rb +20 -0
- data/lib/net/connector/templates/cisco_cdp_neighbors_detail.textfsm +7 -0
- data/lib/net/connector/templates/cisco_ios_running_config_interfaces.textfsm +12 -0
- data/lib/net/connector/templates/cisco_ios_show_ip_interface_brief.textfsm +7 -0
- data/lib/net/connector/templates/h3c_interface_descriptions.textfsm +11 -0
- data/lib/net/connector/templates/h3c_lldp_local_first.textfsm +8 -0
- data/lib/net/connector/templates/h3c_lldp_name_first.textfsm +8 -0
- data/lib/net/connector/templates/hillstone_interface_descriptions.textfsm +11 -0
- data/lib/net/connector/templates/hillstone_lldp_neighbors.textfsm +7 -0
- data/lib/net/connector/templates/index +8 -0
- data/lib/net/connector/templates/palo_alto_interface_descriptions.textfsm +6 -0
- data/lib/net/connector/templates/palo_alto_lldp_neighbors.textfsm +11 -0
- data/lib/net/connector/templates/radware_port_names.textfsm +13 -0
- data/lib/net/connector/vendor/cisco_ios/running_config.rb +19 -0
- data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +32 -0
- data/lib/net/connector/vendor/cisco_ios/topology.rb +33 -0
- data/lib/net/connector/vendor/cisco_ios.rb +48 -0
- data/lib/net/connector/vendor/cisco_nxos/running_config.rb +22 -0
- data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +34 -0
- data/lib/net/connector/vendor/cisco_nxos.rb +55 -0
- data/lib/net/connector/vendor/h3c/tftp_backup.rb +43 -0
- data/lib/net/connector/vendor/h3c/topology.rb +56 -0
- data/lib/net/connector/vendor/h3c.rb +77 -0
- data/lib/net/connector/vendor/h3c_wireless.rb +14 -0
- data/lib/net/connector/vendor/hillstone/running_config.rb +13 -0
- data/lib/net/connector/vendor/hillstone/tftp_backup.rb +40 -0
- data/lib/net/connector/vendor/hillstone/topology.rb +48 -0
- data/lib/net/connector/vendor/hillstone.rb +43 -0
- data/lib/net/connector/vendor/huawei/tftp_backup.rb +34 -0
- data/lib/net/connector/vendor/huawei.rb +64 -0
- data/lib/net/connector/vendor/palo_alto/running_config.rb +70 -0
- data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +36 -0
- data/lib/net/connector/vendor/palo_alto/topology.rb +63 -0
- data/lib/net/connector/vendor/palo_alto.rb +43 -0
- data/lib/net/connector/vendor/radware/tftp_backup.rb +40 -0
- data/lib/net/connector/vendor/radware/topology.rb +14 -0
- data/lib/net/connector/vendor/radware.rb +62 -0
- data/lib/net/connector/version.rb +7 -0
- data/lib/net/connector.rb +48 -0
- data/lib/net-connector.rb +3 -0
- metadata +517 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../device/running_config/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module CiscoIos
|
|
8
|
+
class RunningConfig < Net::Connector::RunningConfig::Rendered
|
|
9
|
+
def clean(text)
|
|
10
|
+
config = super
|
|
11
|
+
config.gsub!(/^[^\n]*#\s*terminal\s+length\s+0\s*\n/i, "")
|
|
12
|
+
config.sub!(/^[^\n]*#\s*copy\s+run(?:ning-config)?\s+(?:start|startup-config)\s*\n.*\z/im, "")
|
|
13
|
+
config.gsub!(/^!.*(?:Last\s+configuration\s+change|NVRAM\s+config).*\n/i, "")
|
|
14
|
+
config
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/tftp/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module CiscoIos
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
10
|
+
def script(target, source_file:, vrf: nil)
|
|
11
|
+
raise ArgumentError, "Cisco IOS does not use source_file" if source_file
|
|
12
|
+
raise ArgumentError, "Cisco IOS TFTP backup does not use vrf" if vrf
|
|
13
|
+
|
|
14
|
+
Script.new([Command.new("copy running-config tftp:", timeout: 180,
|
|
15
|
+
interactions: [
|
|
16
|
+
interaction(/Address\s+or\s+name\s+of\s+remote\s+host\s*\[[^\]]*\]\?\s*\z/i,
|
|
17
|
+
"#{target.host}\n"),
|
|
18
|
+
interaction(/Destination\s+filename\s*\[[^\]]*\]\?\s*\z/i,
|
|
19
|
+
"#{target.path}\n")
|
|
20
|
+
])])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
24
|
+
def complete?(result)
|
|
25
|
+
completion_lines(result).any? do |line|
|
|
26
|
+
copied_bytes?(line) || line.match?(/\A\[OK(?:\s*-\s*[1-9]\d*\s+bytes)?\]\z/i)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/topology/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module CiscoIos
|
|
8
|
+
class Topology < Operations::Topology::Strategy
|
|
9
|
+
def self.supports?(capability)
|
|
10
|
+
%i[neighbors interface_descriptions interface_description_changes].include?(capability)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def neighbor_command = "show cdp neighbors detail"
|
|
14
|
+
def description_template = "cisco_ios_running_config_interfaces.textfsm"
|
|
15
|
+
def protocol = :cdp
|
|
16
|
+
def enter_configuration = "configure terminal"
|
|
17
|
+
def finish_commands = ["end"] + @device.profile.save_commands
|
|
18
|
+
|
|
19
|
+
def expected_neighbor_count(output, _template)
|
|
20
|
+
output.scan(/^\s*Device ID:\s*\S+/i).size
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def empty_neighbor_output?(output)
|
|
24
|
+
output.match?(/Total cdp entries displayed\s*:\s*0|no cdp neighbors/i)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def change_commands(change)
|
|
28
|
+
InterfaceDescription.commands(interface: change.interface, description: change.new_description)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "cisco_ios/running_config"
|
|
5
|
+
require_relative "cisco_ios/tftp_backup"
|
|
6
|
+
require_relative "cisco_ios/topology"
|
|
7
|
+
|
|
8
|
+
module Net
|
|
9
|
+
module Connector
|
|
10
|
+
module CiscoIos
|
|
11
|
+
# Cisco IOS 和 IOS-XE 配置采集连接器。
|
|
12
|
+
class Connector < Net::Connector::Base
|
|
13
|
+
vendor :cisco_ios
|
|
14
|
+
# 声明 IOS 的命令、提示、分页、诊断和保存确认规则。
|
|
15
|
+
profile do
|
|
16
|
+
running_config_strategy Net::Connector::CiscoIos::RunningConfig
|
|
17
|
+
tftp_strategy Net::Connector::CiscoIos::TftpBackup
|
|
18
|
+
topology_strategy Net::Connector::CiscoIos::Topology
|
|
19
|
+
commands do
|
|
20
|
+
running_config "terminal length 0", "show running-config"
|
|
21
|
+
save_config "copy running-config startup-config"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
prompts do
|
|
25
|
+
login(/(?:\A|(?<=[\r\n]))\S+[>#]\s*\z/)
|
|
26
|
+
command(/(?:\A|(?<=[\r\n]))\S+#\s*\z/)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
pager do
|
|
30
|
+
pattern(/(?:\A|(?<=[\r\n]))[^\S\r\n]*--More--[^\r\n]*(?=\r|\n|\z)/i)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
errors do
|
|
34
|
+
command(/^[ \t]*%[ \t]+(?:Invalid|Incomplete|Ambiguous)\s+(?:input|command)\b/i, /^[ \t]*\^/)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
command_timeout 60
|
|
38
|
+
|
|
39
|
+
interactions do
|
|
40
|
+
confirm(/Destination\s+filename\s+\[[^\]]+\]\?\s*\z/i, response: "\n")
|
|
41
|
+
confirm(%r{Do\s+you\s+want\s+to\s+overwrite\?\s*\[yes/no\]\s*\z}i, response: "yes\n")
|
|
42
|
+
confirm(/Proceed\s+with\s+copy\?\s*\[confirm\]\s*\z/i, response: "\n")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../cisco_ios/running_config"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module CiscoNxos
|
|
8
|
+
class RunningConfig < CiscoIos::RunningConfig
|
|
9
|
+
def clean(text)
|
|
10
|
+
without_pager = text.to_s.gsub(
|
|
11
|
+
/[ \t]*(?:\x1b\[[0-9;]*[A-Za-z])*--More--
|
|
12
|
+
(?:\x1b\[[0-9;]*[A-Za-z])*(?:[\r\x00\x08 ]|\x1b\[[0-9;]*[A-Za-z])*/ix, ""
|
|
13
|
+
)
|
|
14
|
+
config = super(without_pager)
|
|
15
|
+
config.sub!(/\A.*?^(?=[^\n]*#\s*show\s+running-config\s*$)/im, "")
|
|
16
|
+
config.gsub(/^[ \t]*(?:\[[# ]*\]|\])[ \t]*\d+%[ \t]*\n?/i, "")
|
|
17
|
+
.gsub(/^[ \t]*Copy\s+complete\b[^\n]*\n?/i, "")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/tftp/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module CiscoNxos
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
10
|
+
def script(target, source_file:, vrf: nil)
|
|
11
|
+
raise ArgumentError, "Cisco NX-OS does not use source_file" if source_file
|
|
12
|
+
|
|
13
|
+
vrf ||= "management"
|
|
14
|
+
command = "copy running-config tftp://#{target.host}/#{target.path} vrf #{vrf}"
|
|
15
|
+
Script.new([Command.new(command, timeout: 180,
|
|
16
|
+
interactions: [
|
|
17
|
+
interaction(/Enter\s+vrf\s*\([^\r\n]*\):\s*\z/i,
|
|
18
|
+
"#{vrf}\n")
|
|
19
|
+
])])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
23
|
+
def complete?(result)
|
|
24
|
+
completion_lines(result).any? do |line|
|
|
25
|
+
line.match?(/\A
|
|
26
|
+
(?:transfer|copy)(?:\s+operation)?\s+
|
|
27
|
+
(?:success(?:ful(?:ly)?)?|succeeded|complete(?:d)?(?:\s+successfully)?)[.!]?
|
|
28
|
+
\z/ix) || copied_bytes?(line)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "cisco_nxos/running_config"
|
|
5
|
+
require_relative "cisco_nxos/tftp_backup"
|
|
6
|
+
require_relative "cisco_ios/topology"
|
|
7
|
+
|
|
8
|
+
module Net
|
|
9
|
+
module Connector
|
|
10
|
+
module CiscoNxos
|
|
11
|
+
# Cisco Nexus 9000 和 NX-OS 配置采集连接器。
|
|
12
|
+
class Connector < Net::Connector::Base
|
|
13
|
+
vendor :cisco_nxos
|
|
14
|
+
# 声明 NX-OS 的命令、提示、分页、诊断和保存确认规则。
|
|
15
|
+
profile do
|
|
16
|
+
running_config_strategy Net::Connector::CiscoNxos::RunningConfig
|
|
17
|
+
tftp_strategy Net::Connector::CiscoNxos::TftpBackup
|
|
18
|
+
topology_strategy Net::Connector::CiscoIos::Topology
|
|
19
|
+
commands do
|
|
20
|
+
running_config "terminal length 0", "show running-config"
|
|
21
|
+
save_config "copy run start"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
prompts do
|
|
25
|
+
login(/\S+[>#]\s*\z/)
|
|
26
|
+
command(/\S+#\s*\z/)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
pager do
|
|
30
|
+
pattern(/(?:\A|(?<=[\r\n]))[ \t]*(?:\x1b\[[0-9;]*[A-Za-z])*--More--
|
|
31
|
+
(?:[ \t\x00\x08]|\x1b\[[0-9;]*[A-Za-z])*(?=\r|\n|\z)/ix)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
errors do
|
|
35
|
+
command(
|
|
36
|
+
/^[ \t]*%?[ \t]*(?:(?:Invalid|Ambiguous|Incomplete)\s+command(?:\s+\([^)]+\))?|
|
|
37
|
+
Invalid\s+parameter\s+detected)\s+at\s+'\^'\s+marker\./ix,
|
|
38
|
+
/^[ \t]*\^/,
|
|
39
|
+
/^[ \t]*%[ \t]+(?:Invalid|Ambiguous|Incomplete)\s+command\b/i,
|
|
40
|
+
/^[ \t]*%?[ \t]*Invalid\s+parameter\s+detected\b/i,
|
|
41
|
+
/^[ \t]*syntax\s+error\b/i
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
command_timeout 60
|
|
46
|
+
|
|
47
|
+
interactions do
|
|
48
|
+
confirm(/Destination\s+filename\s+\[[^\]]+\]\?\s*\z/i, response: "\n")
|
|
49
|
+
confirm(%r{Do\s+you\s+want\s+to\s+overwrite\?\s*\[yes/no\]\s*\z}i, response: "yes\n")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/tftp/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module H3c
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 选取并校验 H3C 启动配置文件路径。
|
|
10
|
+
def source_file(value)
|
|
11
|
+
return TftpTarget.validate_source_file!(value) if value
|
|
12
|
+
|
|
13
|
+
output = @device.execute("display startup").value!
|
|
14
|
+
rendered = TerminalRenderer.render(output)
|
|
15
|
+
path = rendered[/^\s*Next main startup saved-configuration file:\s*(\S+)/i, 1]
|
|
16
|
+
path ||= rendered[/^\s*Current startup saved-configuration file:\s*(\S+)/i, 1]
|
|
17
|
+
if path.nil? || path.casecmp?("NULL")
|
|
18
|
+
raise DeviceError.new("device has no saved startup configuration",
|
|
19
|
+
code: :startup_config_missing, host: @device.host, phase: :tftp_backup)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
TftpTarget.validate_source_file!(path.sub(/\(\*\)\z/, ""))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 根据 H3C 源文件确定默认导出名称。
|
|
26
|
+
def default_path(source_file) = File.basename(source_file)
|
|
27
|
+
|
|
28
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
29
|
+
def script(target, source_file:, vrf: nil)
|
|
30
|
+
raise ArgumentError, "H3C TFTP backup does not use vrf" if vrf
|
|
31
|
+
|
|
32
|
+
destination = target.explicit_path? ? " #{target.path}" : ""
|
|
33
|
+
Script.new([Command.new("tftp #{target.host} put #{source_file}#{destination}", timeout: 180)])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
37
|
+
def complete?(result)
|
|
38
|
+
completion_lines(result).any? { |line| completed_upload?(line) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/topology/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module H3c
|
|
8
|
+
class Topology < Operations::Topology::Strategy
|
|
9
|
+
TABLE_HEADER = /^\s*(?:System Name\s+Local Interface\b|(?:Local Interface|LocalIf)\s+)/i
|
|
10
|
+
def self.supports?(capability)
|
|
11
|
+
%i[neighbors interface_descriptions interface_description_changes].include?(capability)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def neighbor_command = "display lldp neighbor-information list"
|
|
15
|
+
def description_template = "h3c_interface_descriptions.textfsm"
|
|
16
|
+
def enter_configuration = "system-view"
|
|
17
|
+
def finish_commands = ["return"] + @device.profile.save_commands
|
|
18
|
+
|
|
19
|
+
def neighbor_template(output)
|
|
20
|
+
return "h3c_lldp_name_first.textfsm" if output.match?(/^\s*System Name\s+Local Interface\b/im)
|
|
21
|
+
return "h3c_lldp_local_first.textfsm" if output.match?(/^\s*(?:Local Interface|LocalIf)\s+/im)
|
|
22
|
+
|
|
23
|
+
raise ParsingError.new("H3C LLDP table header was not recognized", code: :unrecognized_output,
|
|
24
|
+
host: @device.host, phase: :discover)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def expected_neighbor_count(output, template)
|
|
28
|
+
local_column = template == "h3c_lldp_name_first.textfsm" ? 1 : 0
|
|
29
|
+
lines = neighbor_lines(output)
|
|
30
|
+
return unless lines.all? do |line|
|
|
31
|
+
interface = line.split.fetch(local_column, nil)
|
|
32
|
+
interface&.match?(/\A[A-Za-z][A-Za-z-]*\d+(?:\/\d+)+\z/)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
lines.size
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 表头之后的未知行不能作为零邻居证据。
|
|
39
|
+
def empty_neighbor_output?(output) = neighbor_lines(output).empty?
|
|
40
|
+
|
|
41
|
+
def change_commands(change)
|
|
42
|
+
InterfaceDescription.commands(interface: change.interface, description: change.new_description, leave: "quit")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def neighbor_lines(output)
|
|
48
|
+
output.lines.drop_while { |line| !line.match?(TABLE_HEADER) }.map(&:strip).reject do |line|
|
|
49
|
+
line.empty? || line.match?(TABLE_HEADER) || line.match?(/\A[-=+ ]+\z/) ||
|
|
50
|
+
line.match?(/\A(?:<[^>]+>|\[[^\]]+\])\z/)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "h3c/tftp_backup"
|
|
5
|
+
require_relative "h3c/topology"
|
|
6
|
+
|
|
7
|
+
module Net
|
|
8
|
+
module Connector
|
|
9
|
+
module H3c
|
|
10
|
+
# H3C 直接使用登录后的命令视图,并在单个批次内解析设备分配的规则编号。
|
|
11
|
+
class Connector < Net::Connector::Base
|
|
12
|
+
vendor :h3c
|
|
13
|
+
# 声明 H3C 的命令、提示、诊断和保存确认规则。
|
|
14
|
+
profile do
|
|
15
|
+
running_config_strategy Net::Connector::RunningConfig::Rendered
|
|
16
|
+
tftp_strategy Net::Connector::H3c::TftpBackup
|
|
17
|
+
topology_strategy Net::Connector::H3c::Topology
|
|
18
|
+
commands do
|
|
19
|
+
running_config "dis cur"
|
|
20
|
+
save_config "save force"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
prompts do
|
|
24
|
+
login(/(?:\A|(?<=[\r\n]))(?:[^\S\n]|\x00)*\S+[>\]](?:[^\S\n]|\x00)*\z/)
|
|
25
|
+
command(/(?:\A|(?<=[\r\n]))(?:[^\S\n]|\x00)*[^\r\n]+[>\]](?:[^\S\n]|\x00)*\z/)
|
|
26
|
+
username(/(?:login|Username):\s*\z/i)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
errors do
|
|
30
|
+
command(
|
|
31
|
+
/
|
|
32
|
+
^[ \t]*%[ \t]+(?:(?:Unrecognized|Ambiguous|Incomplete)\s+command|Too\s+many\s+parameters)
|
|
33
|
+
\s+found\s+at\s+'\^'\s+position\.
|
|
34
|
+
/ix,
|
|
35
|
+
/^[ \t]*error:/i,
|
|
36
|
+
/^[ \t]*Permission\s+denied\./i,
|
|
37
|
+
/^\s*\^/
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
command_timeout 10
|
|
42
|
+
|
|
43
|
+
interactions do
|
|
44
|
+
confirm %r{Are\s+you\s+sure\?\s*\[Y/N\]}i, response: "y\n"
|
|
45
|
+
confirm(/press\s+the\s+enter\s+key\)/i, response: "\n")
|
|
46
|
+
confirm %r{Continue\?\s*\[Y/N\]}i, response: "y\n"
|
|
47
|
+
confirm(/Please\s+input\s+the\s+file\s+name\(\*\.cfg\)\[[^\]]+\]\s*\z/i, response: "\n")
|
|
48
|
+
confirm %r{overwrite\?\s*\[Y/N\]}i, response: "y\n"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
protected
|
|
53
|
+
|
|
54
|
+
# 发送规则追加命令前,用本批次已获取的真实规则编号替换 XXX 占位符。
|
|
55
|
+
def prepare_command(command, execution)
|
|
56
|
+
return command unless /\A[ \t]*rule[ \t]+XXX[ \t]+append\b/i.match?(command.text)
|
|
57
|
+
|
|
58
|
+
rule_id = execution.context[:rule_id]
|
|
59
|
+
raise execution.failure("new rule ID has not been acquired") unless rule_id
|
|
60
|
+
|
|
61
|
+
command.with_text(command.text.sub(/XXX/i, rule_id))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 规则创建成功后查询当前配置,提取设备分配的编号并保存到本批次变量。
|
|
65
|
+
def after_command(command, _response, execution)
|
|
66
|
+
return unless /\A[ \t]*rule[ \t]+pass\b/i.match?(command.text)
|
|
67
|
+
|
|
68
|
+
response = execution.query("dis this")
|
|
69
|
+
rule_id = response.raw[/rule\s+(\d+)\s+pass[^\n]+\n\s*#/i, 1]
|
|
70
|
+
raise execution.failure("device did not return the new rule ID") unless rule_id
|
|
71
|
+
|
|
72
|
+
execution.context[:rule_id] = rule_id
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../device/running_config/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module Hillstone
|
|
8
|
+
class RunningConfig < Net::Connector::RunningConfig::Strategy
|
|
9
|
+
def clean(text) = text.gsub(/\x00?\x08+[ \t]+\x08+/, "")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/tftp/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module Hillstone
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
10
|
+
def script(target, source_file:, vrf: nil)
|
|
11
|
+
raise ArgumentError, "Hillstone TFTP export does not use source_file" if source_file
|
|
12
|
+
if target.explicit_path? && target.path.include?("/")
|
|
13
|
+
raise ArgumentError, "Hillstone TFTP filename cannot contain a directory"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
command = "export configuration startup to tftp server #{target.host} vrouter #{vrf || "mgt-vr"}"
|
|
17
|
+
command += " #{target.path}" if target.explicit_path?
|
|
18
|
+
Script.new([Command.new(command, timeout: 180)])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
22
|
+
def complete?(result)
|
|
23
|
+
!completion_line(result).nil?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 从山石成功回显提取实际目标文件名。
|
|
27
|
+
def remote_path(_target, result)
|
|
28
|
+
filename = completion_line(result)&.split&.last
|
|
29
|
+
TftpTarget.validate_path!(filename)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def completion_line(result)
|
|
35
|
+
completion_lines(result).find { |line| line.match?(/\AExport\s+ok\s*,\s*target\s+file\s+name\s+\S+\z/i) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/topology/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module Hillstone
|
|
8
|
+
class Topology < Operations::Topology::Strategy
|
|
9
|
+
def self.supports?(capability)
|
|
10
|
+
%i[neighbors interface_descriptions interface_description_changes].include?(capability)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def neighbor_command = "show lldp neighbor-information"
|
|
14
|
+
def description_template = "hillstone_interface_descriptions.textfsm"
|
|
15
|
+
def enter_configuration = "configure"
|
|
16
|
+
def finish_commands = ["exit"] + @device.profile.save_commands
|
|
17
|
+
|
|
18
|
+
def expected_neighbor_count(output, _template)
|
|
19
|
+
reported = output[/Total lldp neighbor number:\s*(\d+)/i, 1]
|
|
20
|
+
lines = neighbor_lines(output)
|
|
21
|
+
return unless lines.all? { |line| line.match?(/\A\S+\s+ethernet\d+\/\d+\s/i) }
|
|
22
|
+
return if reported && reported.to_i != lines.size
|
|
23
|
+
|
|
24
|
+
lines.size
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def empty_neighbor_output?(output)
|
|
28
|
+
neighbor_lines(output).empty? && (output.match?(/Total lldp neighbor number:\s*0\b/i) ||
|
|
29
|
+
output.match?(/System Name\s+Local Interface/i))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def change_commands(change)
|
|
33
|
+
InterfaceDescription.commands(interface: change.interface, description: change.new_description)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def neighbor_lines(output)
|
|
39
|
+
output.lines.map(&:strip).reject do |line|
|
|
40
|
+
line.empty? || line == neighbor_command || line.match?(/\ASystem Name\s+Local Interface\b/i) ||
|
|
41
|
+
line.match?(/\ATotal lldp neighbor number:\s*\d+\.?\z/i) ||
|
|
42
|
+
line.match?(/\A[-=+ ]+\z/) || line.match?(/\A\S+(?:#|\([MBF]\))\z/)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "hillstone/running_config"
|
|
5
|
+
require_relative "hillstone/tftp_backup"
|
|
6
|
+
require_relative "hillstone/topology"
|
|
7
|
+
|
|
8
|
+
module Net
|
|
9
|
+
module Connector
|
|
10
|
+
module Hillstone
|
|
11
|
+
# 山石 StoneOS 命令行和原生启动配置导出连接器。
|
|
12
|
+
class Connector < Net::Connector::Base
|
|
13
|
+
vendor :hillstone
|
|
14
|
+
profile do
|
|
15
|
+
running_config_strategy Net::Connector::Hillstone::RunningConfig
|
|
16
|
+
tftp_strategy Net::Connector::Hillstone::TftpBackup
|
|
17
|
+
topology_strategy Net::Connector::Hillstone::Topology
|
|
18
|
+
commands do
|
|
19
|
+
running_config "terminal length 0", "show configuration running"
|
|
20
|
+
save_config "save all"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
prompts do
|
|
24
|
+
login(/(?:\A|(?<=[\r\n]))\S+(?:[#>$]|\([MBF]\))\s*\z/)
|
|
25
|
+
command(/(?:\A|(?<=[\r\n]))\S+(?:#|\([MBF]\))\s*\z/)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
pager do
|
|
29
|
+
pattern(/(?:\A|(?<=[\r\n]))[ \t]*--More--[ \t\x00\x08]*(?=\r|\n|\z)/i)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
errors do
|
|
33
|
+
command(/^[ \t]*\^-+/i, /^\s*\^/,
|
|
34
|
+
/^[ \t]*(?:Unknown|Invalid|Error)\s+command/i,
|
|
35
|
+
/^[ \t]*Error:\s*[^\r\n]+/i)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
command_timeout 60
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/tftp/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module Huawei
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 要求调用方提供合法的华为启动配置文件路径。
|
|
10
|
+
def source_file(value)
|
|
11
|
+
raise ArgumentError, "Huawei TFTP backup requires source_file" unless value
|
|
12
|
+
|
|
13
|
+
TftpTarget.validate_source_file!(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# 根据华为配置源文件确定默认导出名称。
|
|
17
|
+
def default_path(source_file) = File.basename(source_file)
|
|
18
|
+
|
|
19
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
20
|
+
def script(target, source_file:, vrf: nil)
|
|
21
|
+
raise ArgumentError, "Huawei TFTP backup does not use vrf" if vrf
|
|
22
|
+
|
|
23
|
+
destination = target.explicit_path? ? " #{target.path}" : ""
|
|
24
|
+
Script.new([Command.new("tftp #{target.host} put #{source_file}#{destination}", timeout: 180)])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
28
|
+
def complete?(result)
|
|
29
|
+
completion_lines(result).any? { |line| completed_upload?(line) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|