Skip to content

Commit 5b4315f

Browse files
authored
Merge pull request #3691 from FestplattenSchnitzel/aruba_os_file_transfer
Aruba OS: Add file transfer support
2 parents f9b000b + 95b5d83 commit 5b4315f

File tree

4 files changed

+158
-4
lines changed

4 files changed

+158
-4
lines changed

PLATFORMS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@
311311

312312
###### Supported Secure Copy device_type values
313313

314+
- aruba_os
314315
- arista_eos
315316
- ciena_saos
316317
- cisco_asa

netmiko/aruba/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from netmiko.aruba.aruba_aoscx import ArubaCxSSH
2-
from netmiko.aruba.aruba_os import ArubaOsSSH
2+
from netmiko.aruba.aruba_os import ArubaOsSSH, ArubaOsFileTransfer
33

4-
__all__ = ["ArubaOsSSH", "ArubaCxSSH"]
4+
__all__ = ["ArubaOsSSH", "ArubaCxSSH", "ArubaOsFileTransfer"]

netmiko/aruba/aruba_os.py

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
66
"""
77

8-
from typing import Any
8+
import os
9+
from typing import Any, Optional
10+
911
from netmiko.cisco_base_connection import CiscoSSHConnection
12+
from netmiko.scp_handler import BaseFileTransfer
1013

1114

1215
class ArubaOsSSH(CiscoSSHConnection):
@@ -52,3 +55,152 @@ def config_mode(
5255
return super().config_mode(
5356
config_command=config_command, pattern=pattern, re_flags=re_flags
5457
)
58+
59+
60+
class ArubaOsFileTransfer(BaseFileTransfer):
61+
"""Aruba OS SCP File Transfer driver"""
62+
63+
def __init__(
64+
self,
65+
file_system: Optional[str] = "/mm/mynode",
66+
hash_supported: bool = False,
67+
**kwargs: Any,
68+
) -> None:
69+
super().__init__(
70+
file_system=file_system, hash_supported=hash_supported, **kwargs
71+
)
72+
73+
def file_md5(self, file_name: str, add_newline: bool = False) -> str:
74+
msg = "Aruba OS does not support an MD5-hash operation."
75+
raise AttributeError(msg)
76+
77+
@staticmethod
78+
def process_md5(md5_output: str, pattern: str = "") -> str:
79+
msg = "Aruba OS does not support an MD5-hash operation."
80+
raise AttributeError(msg)
81+
82+
def compare_md5(self) -> bool:
83+
msg = "Aruba OS does not support an MD5-hash operation."
84+
raise AttributeError(msg)
85+
86+
def remote_md5(self, base_cmd: str = "", remote_file: Optional[str] = None) -> str:
87+
msg = "Aruba OS does not support an MD5-hash operation."
88+
raise AttributeError(msg)
89+
90+
def check_file_exists(self, remote_cmd: str = "") -> bool:
91+
"""Check if the dest_file already exists on the file system (return boolean)."""
92+
if self.direction == "put":
93+
if not remote_cmd:
94+
remote_cmd = f"dir search {self.dest_file.rpartition('/')[-1]}"
95+
remote_out = self.ssh_ctl_chan._send_command_str(remote_cmd)
96+
97+
if "Cannot get directory information" in remote_out:
98+
return False
99+
100+
return self.dest_file in [
101+
split_line[-1]
102+
for line in remote_out.splitlines()
103+
if (split_line := line.split())
104+
]
105+
elif self.direction == "get":
106+
return os.path.exists(self.dest_file)
107+
else:
108+
raise ValueError("Unexpected value for self.direction")
109+
110+
def remote_file_size(
111+
self, remote_cmd: str = "", remote_file: Optional[str] = None
112+
) -> int:
113+
"""Get the file size of the remote file."""
114+
if remote_file is None:
115+
if self.direction == "put":
116+
remote_file = self.dest_file
117+
elif self.direction == "get":
118+
remote_file = self.source_file
119+
120+
assert isinstance(remote_file, str)
121+
remote_file_search = remote_file.rpartition("/")[-1]
122+
123+
if not remote_cmd:
124+
remote_cmd = f"dir search {remote_file_search}"
125+
remote_out = self.ssh_ctl_chan._send_command_str(remote_cmd)
126+
127+
if "Cannot get directory information" in remote_out:
128+
msg = "Unable to find file on remote system"
129+
raise IOError(msg)
130+
131+
file_size = [
132+
split_line[-5]
133+
for line in remote_out.splitlines()
134+
if (split_line := line.split()) and split_line[-1] == remote_file_search
135+
]
136+
137+
if len(file_size) != 1:
138+
msg = (
139+
"Unable to parse remote file size, found file count is not equal to one"
140+
)
141+
raise IOError(msg)
142+
143+
try:
144+
return int(file_size[0])
145+
except ValueError as ve:
146+
msg = "Unable to parse remote file size, wrong field in use or malformed command output"
147+
raise IOError(msg) from ve
148+
149+
def verify_file(self) -> bool:
150+
"""Verify the file has been transferred correctly based on filesize."""
151+
if self.direction == "put":
152+
return os.stat(self.source_file).st_size == self.remote_file_size(
153+
remote_file=self.dest_file
154+
)
155+
elif self.direction == "get":
156+
return (
157+
self.remote_file_size(remote_file=self.source_file)
158+
== os.stat(self.dest_file).st_size
159+
)
160+
else:
161+
raise ValueError("Unexpected value of self.direction")
162+
163+
def remote_space_available(self, search_pattern: str = "") -> int:
164+
"""Return space available on remote device."""
165+
remote_cmd = "show storage"
166+
remote_output = self.ssh_ctl_chan._send_command_str(remote_cmd).strip()
167+
168+
# df -h ouput
169+
available_sizes = [
170+
split_line[-3]
171+
for line in remote_output.splitlines()
172+
if (split_line := line.split()) and split_line[-1] == "/flash"
173+
]
174+
175+
if not available_sizes:
176+
msg = "Could not determine remote space available."
177+
raise ValueError(msg)
178+
179+
space_available = 0
180+
181+
for available_size in available_sizes:
182+
size_names = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"]
183+
184+
size_str, suffix = available_size[:-1], available_size[-1]
185+
186+
if suffix not in size_names:
187+
msg = "Could not determine remote space available."
188+
raise ValueError(msg)
189+
190+
try:
191+
size = float(size_str)
192+
except ValueError as ve:
193+
msg = "Could not determine remote space available."
194+
raise ValueError(msg) from ve
195+
196+
space_available += size * 1024 ** size_names.index(suffix)
197+
198+
return int(space_available)
199+
200+
def enable_scp(self, cmd: str = "service scp") -> None:
201+
"""Enable SCP on remote device."""
202+
super().enable_scp(cmd)
203+
204+
def disable_scp(self, cmd: str = "no service scp") -> None:
205+
"""Disable SCP on remote device."""
206+
super().disable_scp(cmd)

netmiko/ssh_dispatcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from netmiko.arista import AristaFileTransfer
1717
from netmiko.arris import ArrisCERSSH
1818
from netmiko.apresia import ApresiaAeosSSH, ApresiaAeosTelnet
19-
from netmiko.aruba import ArubaOsSSH, ArubaCxSSH
19+
from netmiko.aruba import ArubaOsSSH, ArubaCxSSH, ArubaOsFileTransfer
2020
from netmiko.asterfusion import AsterfusionAsterNOSSSH
2121
from netmiko.audiocode import (
2222
Audiocode72SSH,
@@ -337,6 +337,7 @@
337337
}
338338

339339
FILE_TRANSFER_MAP = {
340+
"aruba_os": ArubaOsFileTransfer,
340341
"arista_eos": AristaFileTransfer,
341342
"ciena_saos": CienaSaosFileTransfer,
342343
"cisco_asa": CiscoAsaFileTransfer,

0 commit comments

Comments
 (0)