1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
# Copyright (c) 2016 Hochikong
"""
.. module:: vymgmt
:platform: Unix
:synopsis: Provides a programmatic interface to VyOS router configuration sessions
.. moduleauthor:: VyOS Team <maintainers@vyos.net>, Hochikong
"""
import re
from pexpect import pxssh
class VyOSError(Exception):
""" Raised on general errors """
pass
class ConfigError(VyOSError):
""" Raised when an error is found in configuration """
pass
class CommitError(ConfigError):
""" Raised on commit failures """
pass
class ConfigLocked(CommitError):
""" Raised when commit failes due to another commit in progress """
pass
class Router(object):
""" Router configuration interface class
"""
def __init__(self, address, user, password='', port=22):
""" Router configuration interface class
:param address: Router address,example:'192.0.2.1'
:param user: Router user
:param password: Router user's password
:param port: SSH port
"""
self.__address = address
self.__user = user
self.__password = password
self.__port = port
# Session flags
self.__logged_in = False
self.__session_modified = False
self.__session_saved = True
self.__conf_mode = False
# String codec, hardcoded for now
self.__codec = "utf8"
def __execute_command(self, command):
""" Executed a command on the router
:param command: The configuration command
:returns: string -- Command output
:raises: VyOSError
"""
self.__conn.sendline(command)
if not self.__conn.prompt():
raise VyOSError("Connection timed out")
output = self.__conn.before
# XXX: In python3 it's bytes rather than str
if isinstance(output, bytes):
output = output.decode(self.__codec)
return output
def _status(self):
""" Returns the router object status for debugging
:returns: dict -- Router object status
"""
return {"logged_in": self.__logged_in,
"session_modified": self.__session_modified,
"session_saved": self.__session_saved,
"conf_mode": self.__conf_mode}
def login(self):
""" Logins to the router
"""
# XXX: after logout, old pxssh instance stops working,
# so we create a new one for each login
# There may or may not be a better way to handle it
self.__conn = pxssh.pxssh()
self.__conn.login(self.__address, self.__user, password=self.__password, port=self.__port)
self.__logged_in = True
def logout(self):
""" Logouts from the router
:raises: VyOSError
"""
if not self.__logged_in:
raise VyOSError("Not logged in")
else:
if self.__conf_mode:
raise VyOSError("Cannot logout before exiting configuration mode")
else:
self.__conn.close()
self.__conn = None
self.__logged_in = False
def run_op_mode_command(self, command):
""" Executes a VyOS operational command
:param command: VyOS operational command
:type command: str
:returns: string -- Command output
"""
prefix = ""
# In cond mode, op mode commands require the "run" prefix
if self.__conf_mode:
prefix = "run"
return self.__execute_command("{0} {1}".format(prefix, command))
def run_conf_mode_command(self, command):
""" Executes a VyOS configuration command
:param command: VyOS configuration command
:returns: Command output
:raises: VyOSError
"""
if not self.__conf_mode:
raise VyOSError("Cannot execute configuration mode commands outside of configuration mode")
else:
return self.__execute_command(command)
def configure(self):
""" Enters configuration mode on the router
You cannot use this methods before you log in.
You cannot call this method twice, unless you log out and log back in.
:raises: VyOSError
"""
if not self.__logged_in:
raise VyOSError("Cannot enter configuration mode when not logged in")
else:
if self.__conf_mode:
raise VyOSError("Session is already in configuration mode")
else:
# configure changes the prompt (from $ to #), so this is
# a bit of a special case, and we use pxssh directly instead
# of the __execute_command wrapper...
self.__conn.sendline("configure")
# XXX: set_unique_prompt() after this breaks things, for some reason
# We should find out why.
self.__conn.PROMPT = "[#$]"
if not self.__conn.prompt():
raise VyOSError("Entering configure mode failed (possibly due to timeout)")
self.__conf_mode = True
# XXX: There should be a check for operator vs. admin
# mode and appropriate exception, but pexpect doesn't work
# with operator's overly restricted shell...
def commit(self):
"""Commits configuration changes
You must call the configure() method before using this one.
:raises: VyOSError, ConfigError, CommitError, ConfigLocked
"""
if not self.__conf_mode:
raise VyOSError("Cannot commit without entering configuration mode")
else:
if not self.__session_modified:
raise ConfigError("No configuration changes to commit")
else:
output = self.__execute_command("commit")
if re.search(r"Commit\s+failed", output):
raise CommitError(output)
if re.search(r"another\s+commit\s+in\s+progress", output):
raise ConfigLocked("Configuration is locked due to another commit in progress")
self.__session_modified = False
self.__session_saved = False
def save(self):
"""Saves the configuration after commit
You must call the configure() method before using this one.
You do not need to make any changes and commit then to use this method.
You cannot save if there are uncommited changes.
:raises: VyOSError
"""
if not self.__conf_mode:
raise VyOSError("Cannot save when not in configuration mode")
elif self.__session_modified:
raise VyOSError("Cannot save when there are uncommited changes")
else:
self.__execute_command("save")
self.__session_saved = True
def exit(self, force=False):
""" Exits configuration mode on the router
You must call the configure() method before using this one.
Unless the force argument is True, it disallows exit when there are unsaved
or uncommited changes. Any uncommited changes are discarded on forced exit.
If the session is not in configuration mode, this method does nothing.
:param force: Force exit despite uncommited or unsaved changes
:type force: bool
:raises: VyOSError
"""
if not self.__conf_mode:
pass
else:
# XXX: would be nice to simplify these conditionals
if self.__session_modified:
if not force:
raise VyOSError("Cannot exit a session with uncommited changes, use force flag to discard")
else:
self.__execute_command("exit discard")
self.__conf_mode = False
return
elif (not self.__session_saved) and (not force):
raise VyOSError("Cannot exit a session with unsaved changes, use force flag to ignore")
else:
self.__execute_command("exit")
self.__conf_mode = False
def set(self, path):
""" Creates a new configuration node on the router
You must call the configure() method before using this one.
:param path: Configuration node path.
e.g. 'protocols static route ... next-hop ... distance ...'
:raises: ConfigError
"""
if not self.__conf_mode:
raise ConfigError("Cannot execute set commands when not in configuration mode")
else:
output = self.__execute_command("{0} {1}". format("set", path))
if re.search(r"Set\s+failed", output):
raise ConfigError(output)
elif re.search(r"already exists", output):
raise ConfigError("Configuration path already exists")
self.__session_modified = True
def delete(self, path):
""" Deletes a node from configuration on the router
You must call the configure() method before using this one.
:param path: Configuration node path.
e.g. 'protocols static route ... next-hop ... distance ...'
:raises: ConfigError
"""
if not self.__conf_mode:
raise ConfigError("Cannot execute delete commands when not in configuration mode")
else:
output = self.__execute_command("{0} {1}". format("delete", path))
if re.search(r"Nothing\s+to\s+delete", output):
raise ConfigError(output)
self.__session_modified = True
|