tdl-xoa-driver 1.4.0__py3-none-any.whl → 1.5.0b1__py3-none-any.whl
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.
- {tdl_xoa_driver-1.4.0.dist-info → tdl_xoa_driver-1.5.0b1.dist-info}/METADATA +2 -2
- {tdl_xoa_driver-1.4.0.dist-info → tdl_xoa_driver-1.5.0b1.dist-info}/RECORD +45 -33
- xoa_driver/__init__.py +2 -2
- xoa_driver/enums.py +2 -0
- xoa_driver/exceptions.py +2 -0
- xoa_driver/functions/anlt.py +2 -0
- xoa_driver/functions/anlt_ll_debug.py +2 -0
- xoa_driver/functions/cli/__init__.py +21 -0
- xoa_driver/functions/cli/_cli_manager.py +541 -0
- xoa_driver/functions/cli/_config_block.py +334 -0
- xoa_driver/functions/cli/_socket_driver.py +111 -0
- xoa_driver/functions/cli/port_config.py +107 -0
- xoa_driver/functions/cli/test_case_config.py +172 -0
- xoa_driver/functions/cmis/__init__.py +8 -0
- xoa_driver/functions/cmis/_constants.py +25 -0
- xoa_driver/functions/cmis/_replies.py +600 -0
- xoa_driver/functions/cmis/_utils.py +49 -0
- xoa_driver/functions/cmis/cdb.py +1266 -0
- xoa_driver/functions/exceptions.py +2 -0
- xoa_driver/functions/headers.py +2 -0
- xoa_driver/functions/mgmt.py +42 -19
- xoa_driver/functions/tools.py +9 -3
- xoa_driver/hlfuncs.py +6 -2
- xoa_driver/internals/commands/c_commands.py +6 -10
- xoa_driver/internals/commands/enums.py +25 -1
- xoa_driver/internals/commands/pr_commands.py +17 -16
- xoa_driver/internals/commands/px_commands.py +54 -54
- xoa_driver/internals/core/transporter/logger/__state_on_user.py +1 -1
- xoa_driver/internals/exceptions/modules.py +4 -3
- xoa_driver/internals/hli/modules/modules_l23/family_edun.py +82 -0
- xoa_driver/internals/hli/modules/modules_l23/family_g.py +1 -1
- xoa_driver/internals/hli/modules/modules_l23/family_l1.py +19 -0
- xoa_driver/internals/hli/ports/port_l23/family_edun.py +82 -0
- xoa_driver/internals/hli/ports/port_l23/family_l1.py +6 -0
- xoa_driver/internals/state_storage/modules_state.py +20 -0
- xoa_driver/internals/state_storage/testers_state.py +10 -0
- xoa_driver/lli.py +1 -0
- xoa_driver/misc.py +1 -0
- xoa_driver/modules.py +22 -0
- xoa_driver/ports.py +22 -0
- xoa_driver/testers.py +2 -0
- xoa_driver/utils.py +2 -0
- xoa_driver/functions/cli.py +0 -581
- {tdl_xoa_driver-1.4.0.dist-info → tdl_xoa_driver-1.5.0b1.dist-info}/WHEEL +0 -0
- {tdl_xoa_driver-1.4.0.dist-info → tdl_xoa_driver-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
- {tdl_xoa_driver-1.4.0.dist-info → tdl_xoa_driver-1.5.0b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
from xoa_driver import __version__, __name__
|
|
2
|
+
from typing import Union, List
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
class ConfigMetadataType(Enum):
|
|
7
|
+
DEFAULT = "XENA"
|
|
8
|
+
PORT = "XENAPORT"
|
|
9
|
+
MODULE = "XENAMODULE"
|
|
10
|
+
TESTCASE = "XENACASE"
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class BaseConfigMetadata:
|
|
14
|
+
type: ConfigMetadataType = ConfigMetadataType.DEFAULT
|
|
15
|
+
formated_version: int = 3
|
|
16
|
+
saved_by: str = __name__ + __version__
|
|
17
|
+
|
|
18
|
+
def to_str(self) -> str:
|
|
19
|
+
header_lines = [
|
|
20
|
+
f";{self.type.value}",
|
|
21
|
+
f";FormatVersion: {self.formated_version}",
|
|
22
|
+
f";Savedby: {self.saved_by}",
|
|
23
|
+
]
|
|
24
|
+
return "\n".join(header_lines)
|
|
25
|
+
|
|
26
|
+
# Module config metadata dataclass
|
|
27
|
+
@dataclass
|
|
28
|
+
class TestCaseMetadata(BaseConfigMetadata):
|
|
29
|
+
type: ConfigMetadataType = ConfigMetadataType.TESTCASE
|
|
30
|
+
testbed_name: str = "<testbed>"
|
|
31
|
+
chassis_name: str = "<chassis_name>"
|
|
32
|
+
chassis_sn: str = "<chassis_sn>"
|
|
33
|
+
chassis_version_str: str = "<chassis_version_str>"
|
|
34
|
+
module_name: str = "<module_name>"
|
|
35
|
+
module_model: str = "<module_model>"
|
|
36
|
+
module_sn: str = "<module_sn>"
|
|
37
|
+
module_version_str: str = "<module_version_str>"
|
|
38
|
+
module_revision: str = "<module_revision>"
|
|
39
|
+
|
|
40
|
+
def to_str(self) -> str:
|
|
41
|
+
header_lines = [
|
|
42
|
+
f";{self.type.value}",
|
|
43
|
+
f";FormatVersion: {super().formated_version}",
|
|
44
|
+
f";Savedby: {super().saved_by}",
|
|
45
|
+
f";Testbed: {self.testbed_name}",
|
|
46
|
+
]
|
|
47
|
+
return "\n".join(header_lines)
|
|
48
|
+
|
|
49
|
+
# Module config metadata dataclass
|
|
50
|
+
@dataclass
|
|
51
|
+
class ModuleConfigMetadata(BaseConfigMetadata):
|
|
52
|
+
type: ConfigMetadataType = ConfigMetadataType.MODULE
|
|
53
|
+
testbed_name: str = "<testbed>"
|
|
54
|
+
chassis_name: str = "<chassis_name>"
|
|
55
|
+
chassis_sn: str = "<chassis_sn>"
|
|
56
|
+
chassis_version_str: str = "<chassis_version_str>"
|
|
57
|
+
module_name: str = "<module_name>"
|
|
58
|
+
module_model: str = "<module_model>"
|
|
59
|
+
module_sn: str = "<module_sn>"
|
|
60
|
+
module_version_str: str = "<module_version_str>"
|
|
61
|
+
module_revision: str = "<module_revision>"
|
|
62
|
+
module_id: str = "<module_id>"
|
|
63
|
+
|
|
64
|
+
def to_str(self) -> str:
|
|
65
|
+
header_lines = [
|
|
66
|
+
f";{self.type.value}",
|
|
67
|
+
f";FormatVersion: {super().formated_version}",
|
|
68
|
+
f";Savedby: {super().saved_by}",
|
|
69
|
+
f";ChassisName: {self.chassis_name}",
|
|
70
|
+
f";ChassisSerial: {self.chassis_sn}",
|
|
71
|
+
f";ChassisVersion: {self.chassis_version_str}",
|
|
72
|
+
f";ModuleName: {self.module_name}",
|
|
73
|
+
f";ModuleModel: {self.module_model.replace('_', '')}",
|
|
74
|
+
f";ModuleSerial: {self.module_sn}",
|
|
75
|
+
f";ModuleVersion: {self.module_version_str}",
|
|
76
|
+
f";ModuleRevision: {self.module_revision}",
|
|
77
|
+
f";Module: {self.module_id}",
|
|
78
|
+
]
|
|
79
|
+
return "\n".join(header_lines)
|
|
80
|
+
|
|
81
|
+
# Port config metadata dataclass
|
|
82
|
+
@dataclass
|
|
83
|
+
class PortConfigMetadata(BaseConfigMetadata):
|
|
84
|
+
type = ConfigMetadataType.PORT
|
|
85
|
+
testbed_name: str = "<testbed>"
|
|
86
|
+
chassis_name: str = "<chassis_name>"
|
|
87
|
+
chassis_sn: str = "<chassis_sn>"
|
|
88
|
+
chassis_version_str: str = "<chassis_version_str>"
|
|
89
|
+
module_name: str = "<module_name>"
|
|
90
|
+
module_model: str = "<module_model>"
|
|
91
|
+
module_sn: str = "<module_sn>"
|
|
92
|
+
module_version_str: str = "<module_version_str>"
|
|
93
|
+
module_revision: str = "<module_revision>"
|
|
94
|
+
port_id: str = "<port_id>"
|
|
95
|
+
|
|
96
|
+
def to_str(self) -> str:
|
|
97
|
+
header_lines = [
|
|
98
|
+
f";{self.type.value}",
|
|
99
|
+
f";FormatVersion: {super().formated_version}",
|
|
100
|
+
f";Savedby: {super().saved_by}",
|
|
101
|
+
f";ChassisName: {self.chassis_name}",
|
|
102
|
+
f";ChassisSerial: {self.chassis_sn}",
|
|
103
|
+
f";ChassisVersion: {self.chassis_version_str}",
|
|
104
|
+
f";ModuleName: {self.module_name}",
|
|
105
|
+
f";ModuleModel: {self.module_model.replace('_', '')}",
|
|
106
|
+
f";ModuleSerial: {self.module_sn}",
|
|
107
|
+
f";ModuleVersion: {self.module_version_str}",
|
|
108
|
+
f";ModuleRevision: {self.module_revision}",
|
|
109
|
+
f";Port: {self.port_id}",
|
|
110
|
+
]
|
|
111
|
+
return "\n".join(header_lines)
|
|
112
|
+
|
|
113
|
+
# Config block
|
|
114
|
+
class ConfigBlock:
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self._config_block_str: str
|
|
117
|
+
self._metadata: Union[TestCaseMetadata, PortConfigMetadata, ModuleConfigMetadata]
|
|
118
|
+
self._commands: List[str] = []
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def type(self) -> ConfigMetadataType:
|
|
122
|
+
return self._metadata.type
|
|
123
|
+
|
|
124
|
+
@type.setter
|
|
125
|
+
def type(self, v: ConfigMetadataType) -> None:
|
|
126
|
+
if v == ConfigMetadataType.MODULE:
|
|
127
|
+
self._metadata = ModuleConfigMetadata()
|
|
128
|
+
self._metadata.type = v
|
|
129
|
+
elif v == ConfigMetadataType.PORT:
|
|
130
|
+
self._metadata = PortConfigMetadata()
|
|
131
|
+
self._metadata.type = v
|
|
132
|
+
elif v == ConfigMetadataType.TESTCASE:
|
|
133
|
+
self._metadata = TestCaseMetadata()
|
|
134
|
+
self._metadata.type = v
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError("Invalid config type")
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def config_block_str(self) -> str:
|
|
140
|
+
header_str = self._metadata.to_str()
|
|
141
|
+
if len(self._commands) == 0:
|
|
142
|
+
return header_str
|
|
143
|
+
else:
|
|
144
|
+
commands_str = "\n".join(self._commands)
|
|
145
|
+
return header_str + "\n" + commands_str
|
|
146
|
+
|
|
147
|
+
@config_block_str.setter
|
|
148
|
+
def config_block_str(self, v: str) -> None:
|
|
149
|
+
self._config_block_str = v
|
|
150
|
+
|
|
151
|
+
metadata_list = []
|
|
152
|
+
line_list = self._config_block_str.splitlines()
|
|
153
|
+
|
|
154
|
+
for line in line_list:
|
|
155
|
+
if line.startswith(';'):
|
|
156
|
+
metadata_list.append(line)
|
|
157
|
+
else:
|
|
158
|
+
self._commands.append(line)
|
|
159
|
+
|
|
160
|
+
if line_list[0].startswith(f";{ConfigMetadataType.MODULE.value}"):
|
|
161
|
+
self._metadata = ModuleConfigMetadata()
|
|
162
|
+
for line in metadata_list:
|
|
163
|
+
if line.startswith(f";ChassisName:"):
|
|
164
|
+
self._metadata.chassis_name = line.split(" ")[1].strip()
|
|
165
|
+
elif line.startswith(f";ChassisSerial:"):
|
|
166
|
+
self._metadata.chassis_sn = line.split(" ")[1].strip()
|
|
167
|
+
elif line.startswith(f";ChassisVersion:"):
|
|
168
|
+
self._metadata.chassis_version_str = line.split(" ")[1].strip()
|
|
169
|
+
elif line.startswith(f";ModuleName:"):
|
|
170
|
+
self._metadata.module_name = line.split(" ")[1].strip()
|
|
171
|
+
elif line.startswith(f";ModuleModel:"):
|
|
172
|
+
self._metadata.module_model = line.split(" ")[1].strip()
|
|
173
|
+
elif line.startswith(f";ModuleSerial:"):
|
|
174
|
+
self._metadata.module_sn = line.split(" ")[1].strip()
|
|
175
|
+
elif line.startswith(f";ModuleVersion:"):
|
|
176
|
+
self._metadata.module_version_str = line.split(" ")[1].strip()
|
|
177
|
+
elif line.startswith(f";ModuleRevision:"):
|
|
178
|
+
self._metadata.module_revision = line.split(" ")[1].strip()
|
|
179
|
+
elif line.startswith(f";Module:"):
|
|
180
|
+
self._metadata.module_id = line.split(" ")[1].strip()
|
|
181
|
+
|
|
182
|
+
if line_list[0].startswith(f";{ConfigMetadataType.PORT.value}"):
|
|
183
|
+
self._metadata = PortConfigMetadata()
|
|
184
|
+
for line in metadata_list:
|
|
185
|
+
if line.startswith(f";ChassisName:"):
|
|
186
|
+
self._metadata.chassis_name = line.split(" ")[1].strip()
|
|
187
|
+
elif line.startswith(f";ChassisSerial:"):
|
|
188
|
+
self._metadata.chassis_sn = line.split(" ")[1].strip()
|
|
189
|
+
elif line.startswith(f";ChassisVersion:"):
|
|
190
|
+
self._metadata.chassis_version_str = line.split(" ")[1].strip()
|
|
191
|
+
elif line.startswith(f";ModuleName:"):
|
|
192
|
+
self._metadata.module_name = line.split(" ")[1].strip()
|
|
193
|
+
elif line.startswith(f";ModuleModel:"):
|
|
194
|
+
self._metadata.module_model = line.split(" ")[1].strip()
|
|
195
|
+
elif line.startswith(f";ModuleSerial:"):
|
|
196
|
+
self._metadata.module_sn = line.split(" ")[1].strip()
|
|
197
|
+
elif line.startswith(f";ModuleVersion:"):
|
|
198
|
+
self._metadata.module_version_str = line.split(" ")[1].strip()
|
|
199
|
+
elif line.startswith(f";ModuleRevision:"):
|
|
200
|
+
self._metadata.module_revision = line.split(" ")[1].strip()
|
|
201
|
+
elif line.startswith(f";Port:"):
|
|
202
|
+
self._metadata.port_id = line.split(" ")[1].strip()
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def testbed_name(self) -> str:
|
|
206
|
+
return self._metadata.testbed_name
|
|
207
|
+
|
|
208
|
+
@testbed_name.setter
|
|
209
|
+
def testbed_name(self, v: str) -> None:
|
|
210
|
+
self._metadata.testbed_name = v
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def chassis_name(self) -> str:
|
|
214
|
+
return self._metadata.chassis_name
|
|
215
|
+
|
|
216
|
+
@chassis_name.setter
|
|
217
|
+
def chassis_name(self, v: str) -> None:
|
|
218
|
+
self._metadata.chassis_name = v
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def chassis_sn(self) -> str:
|
|
222
|
+
return self._metadata.chassis_sn
|
|
223
|
+
|
|
224
|
+
@chassis_sn.setter
|
|
225
|
+
def chassis_sn(self, v: str) -> None:
|
|
226
|
+
self._metadata.chassis_sn = v
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def chassis_version_str(self) -> str:
|
|
230
|
+
return self._metadata.chassis_version_str
|
|
231
|
+
|
|
232
|
+
@chassis_version_str.setter
|
|
233
|
+
def chassis_version_str(self, v: str) -> None:
|
|
234
|
+
self._metadata.chassis_version_str = v
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def module_name(self) -> str:
|
|
238
|
+
return self._metadata.module_name
|
|
239
|
+
|
|
240
|
+
@module_name.setter
|
|
241
|
+
def module_name(self, v: str) -> None:
|
|
242
|
+
self._metadata.module_name = v
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def module_model(self) -> str:
|
|
246
|
+
return self._metadata.module_model
|
|
247
|
+
|
|
248
|
+
@module_model.setter
|
|
249
|
+
def module_model(self, v: str) -> None:
|
|
250
|
+
self._metadata.module_model = v
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def module_sn(self) -> str:
|
|
254
|
+
return self._metadata.module_sn
|
|
255
|
+
|
|
256
|
+
@module_sn.setter
|
|
257
|
+
def module_sn(self, v: str) -> None:
|
|
258
|
+
self._metadata.module_sn = v
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def module_version_str(self) -> str:
|
|
262
|
+
return self._metadata.module_version_str
|
|
263
|
+
|
|
264
|
+
@module_version_str.setter
|
|
265
|
+
def module_version_str(self, v: str) -> None:
|
|
266
|
+
self._metadata.module_version_str = v
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def module_revision(self) -> str:
|
|
270
|
+
return self._metadata.module_revision
|
|
271
|
+
|
|
272
|
+
@module_revision.setter
|
|
273
|
+
def module_revision(self, v: str) -> None:
|
|
274
|
+
self._metadata.module_revision = v
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def module_id(self) -> str:
|
|
278
|
+
if isinstance(self._metadata, ModuleConfigMetadata):
|
|
279
|
+
return self._metadata.module_id
|
|
280
|
+
return ""
|
|
281
|
+
|
|
282
|
+
@module_id.setter
|
|
283
|
+
def module_id(self, v: str) -> None:
|
|
284
|
+
if isinstance(self._metadata, ModuleConfigMetadata):
|
|
285
|
+
self._metadata.module_id = v
|
|
286
|
+
else:
|
|
287
|
+
raise AttributeError("module_id is only available for ModuleConfigMetadata")
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def port_id(self) -> str:
|
|
291
|
+
if isinstance(self._metadata, PortConfigMetadata):
|
|
292
|
+
return self._metadata.port_id
|
|
293
|
+
return ""
|
|
294
|
+
|
|
295
|
+
@port_id.setter
|
|
296
|
+
def port_id(self, v: str) -> None:
|
|
297
|
+
if isinstance(self._metadata, PortConfigMetadata):
|
|
298
|
+
self._metadata.port_id = v
|
|
299
|
+
else:
|
|
300
|
+
raise AttributeError("port_id is only available for PortConfigMetadata")
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def metadata(self) -> Union[TestCaseMetadata, PortConfigMetadata, ModuleConfigMetadata]:
|
|
304
|
+
return self._metadata
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def commands(self) -> List[str]:
|
|
308
|
+
return self._commands
|
|
309
|
+
|
|
310
|
+
@commands.setter
|
|
311
|
+
def commands(self, v: str) -> None:
|
|
312
|
+
# Remove the <SYNC> tag
|
|
313
|
+
v = v.replace('<SYNC>', '')
|
|
314
|
+
|
|
315
|
+
if self.type == ConfigMetadataType.PORT:
|
|
316
|
+
v = v.replace(f"{self.port_id} ", '')
|
|
317
|
+
v = f"P_RESET\n" + v
|
|
318
|
+
|
|
319
|
+
if self.type == ConfigMetadataType.MODULE:
|
|
320
|
+
v = v.replace(f"{self.module_id} ", '')
|
|
321
|
+
|
|
322
|
+
self._commands = v.splitlines()
|
|
323
|
+
|
|
324
|
+
# Determine type of config block
|
|
325
|
+
def config_block_type(config_block_str: str) -> ConfigMetadataType:
|
|
326
|
+
line_list = config_block_str.splitlines()
|
|
327
|
+
if line_list[0].startswith(f";{ConfigMetadataType.MODULE.value}"):
|
|
328
|
+
return ConfigMetadataType.MODULE
|
|
329
|
+
elif line_list[0].startswith(f";{ConfigMetadataType.PORT.value}"):
|
|
330
|
+
return ConfigMetadataType.PORT
|
|
331
|
+
elif line_list[0].startswith(f";{ConfigMetadataType.TESTCASE.value}"):
|
|
332
|
+
return ConfigMetadataType.TESTCASE
|
|
333
|
+
else:
|
|
334
|
+
return ConfigMetadataType.DEFAULT
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import time
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, Callable, Union, List, Dict, Any
|
|
5
|
+
|
|
6
|
+
class ServerUnavaliable(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class SimpleSocket(object):
|
|
10
|
+
def __init__(self, hostname: str, port: int = 22611, timeout: int = 20) -> None:
|
|
11
|
+
self.server_addr = (hostname, port)
|
|
12
|
+
self.is_connected = False
|
|
13
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
14
|
+
self.sock.settimeout(timeout)
|
|
15
|
+
self.retry_connect = 3
|
|
16
|
+
self._connect()
|
|
17
|
+
|
|
18
|
+
def __del__(self) -> None:
|
|
19
|
+
self.sock.close()
|
|
20
|
+
|
|
21
|
+
def _connect(self):
|
|
22
|
+
try:
|
|
23
|
+
if hasattr(self, "sock"):
|
|
24
|
+
connid = 1
|
|
25
|
+
err = self.sock.connect_ex(self.server_addr)
|
|
26
|
+
if err == 0:
|
|
27
|
+
self.retry_connect = 3
|
|
28
|
+
self.is_connected = True
|
|
29
|
+
else:
|
|
30
|
+
self.reconnect()
|
|
31
|
+
|
|
32
|
+
except socket.error as msg:
|
|
33
|
+
logging.error(f"[Socket connection error in <_connect>] Cannot connect to { self.server_addr[0] }, error: {msg}\n")
|
|
34
|
+
self.reconnect()
|
|
35
|
+
|
|
36
|
+
def reconnect(self):
|
|
37
|
+
time.sleep(5)
|
|
38
|
+
self.retry_connect -= 1
|
|
39
|
+
if self.retry_connect > 0:
|
|
40
|
+
self._connect()
|
|
41
|
+
else:
|
|
42
|
+
raise ServerUnavaliable(f'Cannot connect to <{ self.server_addr[0] }>, host is unavailable')
|
|
43
|
+
|
|
44
|
+
def close(self):
|
|
45
|
+
if hasattr(self, "sock"):
|
|
46
|
+
self.sock.close()
|
|
47
|
+
|
|
48
|
+
# Send a string command to a socket
|
|
49
|
+
def send_only(self, cmd: str):
|
|
50
|
+
"""Send command string to server"""
|
|
51
|
+
if hasattr(self, "sock") and self.is_connected:
|
|
52
|
+
sent = self.sock.send((cmd + '\n').encode('utf-8'))
|
|
53
|
+
if sent == 0:
|
|
54
|
+
raise RuntimeError("Socket connection broken in <send_command>")
|
|
55
|
+
|
|
56
|
+
# Send a string command to a socket and return a string response from the socket
|
|
57
|
+
def send_and_response(self, cmd: str, sync_on: bool) -> str:
|
|
58
|
+
"""Send a command string to server and return the response"""
|
|
59
|
+
if hasattr(self, "sock") and self.is_connected:
|
|
60
|
+
try:
|
|
61
|
+
sent = self.sock.send((cmd + '\n').encode('utf-8'))
|
|
62
|
+
if sent == 0:
|
|
63
|
+
raise RuntimeError("Socket connection broken in <ask>")
|
|
64
|
+
if sync_on:
|
|
65
|
+
terminator = b'<SYNC>\n'
|
|
66
|
+
buffer = b''
|
|
67
|
+
while True:
|
|
68
|
+
chunk = self.sock.recv(4096)
|
|
69
|
+
if not chunk:
|
|
70
|
+
continue
|
|
71
|
+
buffer += chunk
|
|
72
|
+
if buffer.endswith(terminator):
|
|
73
|
+
break
|
|
74
|
+
return buffer.decode('utf-8')
|
|
75
|
+
else:
|
|
76
|
+
chunk = self.sock.recv(4096)
|
|
77
|
+
while not chunk:
|
|
78
|
+
chunk = self.sock.recv(4096)
|
|
79
|
+
return chunk.decode('utf-8')
|
|
80
|
+
except socket.error as msg:
|
|
81
|
+
logging.error(f"[Socket connection error in <ask>] { msg }")
|
|
82
|
+
return ''
|
|
83
|
+
return ''
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Send a long string data to the socket and return a long string of response from the socket
|
|
87
|
+
def send_and_response_multiple(self, cmd:str, num:int) -> str:
|
|
88
|
+
"""Send a number of commands to server and return the responses"""
|
|
89
|
+
if hasattr(self, "sock") and self.is_connected:
|
|
90
|
+
try:
|
|
91
|
+
self.sock.sendall((cmd).encode('utf-8'))
|
|
92
|
+
chunk = self.sock.recv(4096)
|
|
93
|
+
while not chunk:
|
|
94
|
+
chunk = self.sock.recv(4096)
|
|
95
|
+
data = chunk.decode('utf-8')
|
|
96
|
+
while True:
|
|
97
|
+
if data.count('\n') < num:
|
|
98
|
+
data2 = self.sock.recv(4096).decode('utf-8')
|
|
99
|
+
if data2:
|
|
100
|
+
data = data + data2
|
|
101
|
+
else:
|
|
102
|
+
break
|
|
103
|
+
return data
|
|
104
|
+
except socket.error as msg:
|
|
105
|
+
logging.error(f"[Socket connection error in <ask_multi>] { msg }")
|
|
106
|
+
return ''
|
|
107
|
+
return ''
|
|
108
|
+
|
|
109
|
+
def set_keepalives(self):
|
|
110
|
+
if hasattr(self, "sock") and self.is_connected:
|
|
111
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 2)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from xoa_driver import testers, ports
|
|
3
|
+
from ._cli_manager import XOACLIManager
|
|
4
|
+
from ._config_block import *
|
|
5
|
+
|
|
6
|
+
async def save_port_config(tester: testers.L23Tester, port: ports.GenericL23Port, path: str, debug=False, halt_on_error=False) -> str:
|
|
7
|
+
"""Save configuration from the test port to the specified filepath
|
|
8
|
+
|
|
9
|
+
:param tester: Chassis object
|
|
10
|
+
:type tester: testers.L23Tester
|
|
11
|
+
:param port: Port object to save configuration from
|
|
12
|
+
:type port: ports.GenericL23Port
|
|
13
|
+
:param save_path: File path to save the port configuration
|
|
14
|
+
:type save_path: str
|
|
15
|
+
:return: Port configuration string
|
|
16
|
+
:rtype: str
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
tester_ip = tester.info.host
|
|
20
|
+
resp = await tester.password.get()
|
|
21
|
+
tester_password = resp.password
|
|
22
|
+
port_index = f"{port.kind.module_id}/{port.kind.port_id}"
|
|
23
|
+
module = tester.modules.obtain(port.kind.module_id)
|
|
24
|
+
|
|
25
|
+
# Connect to the tester on tcp port 22611
|
|
26
|
+
xm = XOACLIManager(host=tester_ip, debug=debug, halt_on_error=halt_on_error)
|
|
27
|
+
|
|
28
|
+
# Log on and set username
|
|
29
|
+
xm.logon_set_owner(tester_password)
|
|
30
|
+
|
|
31
|
+
# Get full port configuration
|
|
32
|
+
raw_resp = xm.get_port_full_config_raw(port_index)
|
|
33
|
+
|
|
34
|
+
# Create config block
|
|
35
|
+
port_config_block = ConfigBlock()
|
|
36
|
+
# Fill in metadata
|
|
37
|
+
port_config_block.type = ConfigMetadataType.PORT
|
|
38
|
+
port_config_block.chassis_name = tester.info.name
|
|
39
|
+
port_config_block.chassis_sn = str(tester.info.serial_number)
|
|
40
|
+
port_config_block.chassis_version_str = tester.info.version_string
|
|
41
|
+
port_config_block.module_name = module.info.model_name
|
|
42
|
+
port_config_block.module_model = module.info.model
|
|
43
|
+
port_config_block.module_sn = module.info.serial_number
|
|
44
|
+
port_config_block.module_version_str = module.info.version_string
|
|
45
|
+
port_config_block.module_revision = module.info.revision
|
|
46
|
+
port_config_block.port_id = port_index
|
|
47
|
+
port_config_block.commands = raw_resp[0]
|
|
48
|
+
|
|
49
|
+
# Save configuration to file
|
|
50
|
+
result = port_config_block.config_block_str
|
|
51
|
+
with open(path, 'w+', newline='') as xpcfile:
|
|
52
|
+
xpcfile.write(result)
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
async def load_port_config(tester: testers.L23Tester, port: ports.GenericL23Port, path: str, debug=False, halt_on_error=False) -> None:
|
|
56
|
+
"""Load configuration to the test port from the specified filepath
|
|
57
|
+
|
|
58
|
+
:param tester: Chassis object
|
|
59
|
+
:type tester: testers.L23Tester
|
|
60
|
+
:param port: Port object to load configuration to
|
|
61
|
+
:type port: ports.GenericL23Port
|
|
62
|
+
:param load_path: File path to load the port configuration from
|
|
63
|
+
:type load_path: str
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
tester_ip = tester.info.host
|
|
67
|
+
resp = await tester.password.get()
|
|
68
|
+
tester_password = resp.password
|
|
69
|
+
port_index = f"{port.kind.module_id}/{port.kind.port_id}"
|
|
70
|
+
|
|
71
|
+
# Connect to the tester on tcp port 22611
|
|
72
|
+
xm = XOACLIManager(host=tester_ip, debug=debug, halt_on_error=halt_on_error)
|
|
73
|
+
|
|
74
|
+
# Log on and set username
|
|
75
|
+
xm.logon_set_owner(tester_password)
|
|
76
|
+
|
|
77
|
+
# Reserve the port before applying configuration
|
|
78
|
+
xm.reserve_port(port_index)
|
|
79
|
+
|
|
80
|
+
# Read configuration from file
|
|
81
|
+
with open(path, 'r', newline='') as xpcfile:
|
|
82
|
+
config_data = xpcfile.read()
|
|
83
|
+
|
|
84
|
+
# Deserialize config block and send CLI commands
|
|
85
|
+
config_block = ConfigBlock()
|
|
86
|
+
config_block.config_block_str = config_data
|
|
87
|
+
cli_cmds = config_block.commands
|
|
88
|
+
for cmd in cli_cmds:
|
|
89
|
+
if cmd.strip(): # Ensure the command is not empty
|
|
90
|
+
xm.send(cmd=f"{port_index} {cmd}", sync_on=False)
|
|
91
|
+
|
|
92
|
+
# Free the port after applying configuration
|
|
93
|
+
xm.free_port(port_index)
|
|
94
|
+
|
|
95
|
+
async def port_config_from_file(tester: testers.L23Tester, port: ports.GenericL23Port, path: str, debug=False, halt_on_error=False) -> None:
|
|
96
|
+
"""Load port configuration from the specifiied filepath. This function is a wrapper around load_port_config to provide backward compatibility.
|
|
97
|
+
|
|
98
|
+
:param tester: Chassis object
|
|
99
|
+
:type tester: testers.L23Tester
|
|
100
|
+
:param port: Port object to load configuration to
|
|
101
|
+
:type port: ports.GenericL23Port
|
|
102
|
+
:param load_path: File path to load the port configuration from
|
|
103
|
+
:type load_path: str
|
|
104
|
+
"""
|
|
105
|
+
await load_port_config(tester, port, path, debug=debug, halt_on_error=halt_on_error)
|
|
106
|
+
|
|
107
|
+
|