initial revision of pycol
[pycol.git] / gre.py
1 #!/usr/bin/python3
2
3 import pycol
4 from clicmd import *
5 import sys
6 import traceback
7 import textwrap
8 import xml.etree.ElementTree as ET
9 import xml.dom.minidom
10 import pydoc
11
12 default_conf = """
13 <configuration name="test_cli">
14   <interfaces>
15     <interface>
16       <name>gre1</name>
17       <description>GRE Tunnel</description>
18       <encapsulation>
19         <gre-tunnel>
20           <gre-port>gre1</gre-port>
21           <local-endpoint>1.1.1.1</local-endpoint>
22           <remote-endpoint>1.1.1.2</remote-endpoint>
23         </gre-tunnel>
24       </encapsulation>
25     </interface>
26     <interface>
27       <name>gre2</name>
28       <description>GRE Tunnel</description>
29       <encapsulation>
30         <gre-tunnel>
31           <gre-port>gre2</gre-port>
32           <local-endpoint>2.2.2.1</local-endpoint>
33           <remote-endpoint>2.2.2.2</remote-endpoint>
34         </gre-tunnel>
35       </encapsulation>
36     </interface>
37   </interfaces>
38 </configuration>
39 """
40
41 xmlconf = ET.XML(default_conf)
42 cur_xmlconf = xmlconf
43 ip_list = []
44
45 # from http://stackoverflow.com/questions/749796/pretty-printing-xml-in-python
46 def xml_indent(elem, level=0):
47     i = "\n" + level*"  "
48     if len(elem):
49         if not elem.text or not elem.text.strip():
50             elem.text = i + "  "
51         if not elem.tail or not elem.tail.strip():
52             elem.tail = i
53         for elem in elem:
54             xml_indent(elem, level+1)
55         if not elem.tail or not elem.tail.strip():
56             elem.tail = i
57     else:
58         if level and (not elem.tail or not elem.tail.strip()):
59             elem.tail = i
60
61 def text_indent(s, indent = 0, end = "\n"):
62     return " " * indent + s + end
63
64 def help_callback(cmdline, kvargs):
65     cmd_wr = textwrap.TextWrapper(initial_indent="  ",
66                                    subsequent_indent="  ")
67     token_wr = textwrap.TextWrapper(initial_indent="  ",
68                                    subsequent_indent="      ")
69     s = ""
70     for c in cmdline.context:
71         s += "%s\n"%(c.to_expr())
72         lines = c.get_help().split("\n")
73         # first line is the command + its help
74         s += ("\n".join(cmd_wr.wrap(lines[0]))) + "\n"
75         for l in lines[1:]:
76             s += ("\n".join(token_wr.wrap(l))) + "\n"
77     pydoc.pager(s)
78
79 # used in main and gre context
80 help_cmd = CmdBuilder(
81     expr = "help",
82     token_desc = {
83         "help": TextCliCmd("help", help_str = "Show the help."),
84     },
85     cb = help_callback,
86     help_str = "help for command toto"
87 )
88
89 def get_interface_text_conf(conf, indent = 0):
90     s = ""
91     local = conf.find("encapsulation/gre-tunnel/local-endpoint")
92     remote = conf.find("encapsulation/gre-tunnel/remote-endpoint")
93     if local != None and remote != None:
94         bind_str = "bind %s %s"%(local.text, remote.text)
95         vrfid = conf.find("encapsulation/gre-tunnel/link-vrf-id")
96         if vrfid != None:
97             bind_str += " link-vrfid %s"%(vrfid.text)
98         s += text_indent(bind_str, indent)
99     key = conf.find("encapsulation/gre-tunnel/key")
100     if key != None:
101         if key.text == None:
102             s += text_indent("key enable", indent)
103         else:
104             s += text_indent("key %s"%(key.text), indent)
105     checksum = conf.find("encapsulation/gre-tunnel/checksum")
106     if checksum != None:
107         s += text_indent("checksum enable", indent)
108     return s
109
110 def get_main_text_conf(conf, indent = 0):
111     s = text_indent("# GRE", indent)
112     for node in xmlconf.findall("interfaces/interface"):
113         s += text_indent(node.find("name").text, indent)
114         s += get_interface_text_conf(node, indent=(indent + 4))
115     return s
116
117 def display_cb(cmdline, kvargs):
118     if "xml" in kvargs:
119         xml_indent(cur_xmlconf)
120         s = ET.tostring(cur_xmlconf, method="xml", encoding="unicode")
121     else:
122         if cmdline.context == main_ctx:
123             s = get_main_text_conf(cur_xmlconf)
124         else:
125             s = get_interface_text_conf(cur_xmlconf)
126     pydoc.pager(s)
127
128 # used in main and gre context
129 display_cmd = CmdBuilder(
130     expr = "display [xml]",
131     token_desc = {
132         "display": TextCliCmd("display", help_str = "Display the " +
133                               "current configuration."),
134         "xml": TextCliCmd("xml", help_str = "If present, dump in XML format."),
135     },
136     cb = display_cb,
137     help_str = "Display the current configuration."
138 )
139
140 def bind_cb(cmdline, kvargs):
141     tunnel = cur_xmlconf.find("encapsulation/gre-tunnel")
142     local = tunnel.find("local-endpoint")
143     if local == None:
144         local = ET.XML("<local-endpoint/>")
145         tunnel.append(local)
146     local.text = kvargs["<local-ip>"]
147     remote = tunnel.find("remote-endpoint")
148     if remote == None:
149         remote = ET.XML("<remote-endpoint/>")
150         tunnel.append(remote)
151     remote.text = kvargs["<remote-ip>"]
152     vrfid = tunnel.find("link-vrf-id")
153     if vrfid != None:
154         tunnel.remove(vrfid)
155     if "<vrfid>" in kvargs:
156         vrfid = ET.XML("<link-vrf-id/>")
157         tunnel.append(vrfid)
158         vrfid.text = str(kvargs["<vrfid>"])
159
160 def key_cb(cmdline, kvargs):
161     tunnel = cur_xmlconf.find("encapsulation/gre-tunnel")
162     key = tunnel.find("key")
163     if key != None:
164         tunnel.remove(key)
165     if "disable" in kvargs:
166         return
167     key = ET.XML("<key/>")
168     tunnel.append(key)
169     if "<key>" in kvargs:
170         key.text = str(kvargs["<key>"])
171
172 def checksum_cb(cmdline, kvargs):
173     # XXX handle input/output
174     tunnel = cur_xmlconf.find("encapsulation/gre-tunnel")
175     checksum = tunnel.find("checksum")
176     if checksum != None:
177         tunnel.remove(checksum)
178     if "disable" in kvargs:
179         return
180     checksum = ET.XML("<checksum/>")
181     tunnel.append(checksum)
182
183 def exit_cb(cmdline, kvargs):
184     global cur_xmlconf, xmlconf
185     cmdline.set_context("main")
186     cmdline.set_prompt("router{} ")
187     cur_xmlconf = xmlconf
188
189 gre_ctx = PycolContext([
190     CmdBuilder(
191         expr = "bind <local-ip> <remote-ip> [link-vrfid <vrfid>]",
192         token_desc = {
193             "bind": TextCliCmd("bind", help_str = "Bind the tunnel."),
194             "<local-ip>": IPv4CliCmd(help_str = "The local IPv4 address (A.B.C.D)."),
195             "<remote-ip>": IPv4CliCmd(help_str = "The remote IPv4 address (A.B.C.D)."),
196             "link-vrfid": TextCliCmd("link-vrfid", help_str = ""),
197             "<vrfid>": IntCliCmd(help_str = "Link vrfid."),
198         },
199         cb = bind_cb,
200         help_str = "Bind the GRE tunnel on local and remote addresses."
201     ),
202     CmdBuilder(
203         expr = "checksum|checksum-input|checksum-output enable|disable",
204         token_desc = {
205             "checksum": TextCliCmd("checksum", help_str = ""),
206             "checksum-input": TextCliCmd("checksum-input", help_str = ""),
207             "checksum-output": TextCliCmd("checksum-output", help_str = ""),
208             "enable": TextCliCmd("enable", help_str = ""),
209             "disable": TextCliCmd("disable", help_str = ""),
210         },
211         cb = checksum_cb,
212         help_str = "Enable or disable GRE checksum."
213     ),
214     CmdBuilder(
215         expr = "key <key>|enable|disable",
216         token_desc = {
217             "key": TextCliCmd("key", help_str = ""),
218             "<key>": IntCliCmd(val_min = 0, val_max = (1<<32) - 1,
219                                help_str = "The GRE key."),
220             "enable": TextCliCmd("enable", help_str = ""),
221             "disable": TextCliCmd("disable", help_str = ""),
222         },
223         cb = key_cb,
224         help_str = "Enable or disable GRE checksum."
225     ),
226     CmdBuilder(
227         expr = "exit",
228         token_desc = {
229             "exit": TextCliCmd("exit", help_str = "Exit from GRE context."),
230         },
231         cb = exit_cb,
232         help_str = "Exit from GRE context."
233     ),
234     display_cmd,
235     help_cmd,
236 ])
237
238 def list_gre_ctx():
239     return [ node.text for node in \
240              xmlconf.findall("interfaces/interface/encapsulation/gre-tunnel/gre-port") ]
241
242 def enter_gre(ifname):
243     global xmlconf, cur_xmlconf
244     for node in xmlconf.findall("interfaces/interface"):
245         if node.find("name").text == ifname:
246             cur_xmlconf = node
247             break
248     cmdline.set_prompt("router{%s} "%(ifname))
249     cmdline.set_context("gre")
250
251 def enter_gre_cb(cmdline, kvargs):
252     ifname = kvargs["<gre-ctx>"]
253     return enter_gre(ifname)
254
255 def create_gre_cb(cmdline, kvargs):
256     global xmlconf, cur_xmlconf
257
258     # create the context first
259     ifname = kvargs["<new-gre-ctx>"]
260     xml_str = """
261           <interface>
262             <name>{name}</name>
263             <description>GRE Tunnel</description>
264             <encapsulation>
265               <gre-tunnel>
266                 <gre-port>{name}</gre-port>
267               </gre-tunnel>
268             </encapsulation>
269           </interface>
270         """.format(name = ifname)
271     node = ET.XML(xml_str)
272     xmlconf.find("interfaces").append(node)
273     return enter_gre(ifname)
274
275 def delete_gre(cmdline, kvargs):
276     interfaces = xmlconf.find("interfaces")
277     for interface in interfaces.findall("interface"):
278         if interface.find("name").text == kvargs["<gre-ctx>"]:
279             interfaces.remove(interface)
280             break
281
282 main_ctx = PycolContext([
283     CmdBuilder(
284         expr = "<gre-ctx>",
285         token_desc = {
286             "<gre-ctx>": ChoiceCliCmd(choice = list_gre_ctx,
287                                       help_str = "An existing gre context."),
288         },
289         cb = enter_gre_cb,
290         help_str = "Enter an existing GRE context."
291     ),
292     CmdBuilder(
293         expr = "<new-gre-ctx>",
294         token_desc = {
295             "<new-gre-ctx>": RegexpCliCmd("gre[0-9]+",
296                                           help_str = "A new gre context (format " +
297                                           "is gre[0-9]+)."),
298         },
299         cb = create_gre_cb,
300         help_str = "Create and enter a new GRE context."
301     ),
302     CmdBuilder(
303         expr = "delete <gre-ctx>",
304         token_desc = {
305             "delete": TextCliCmd("delete", help_str = ""),
306             "<gre-ctx>": ChoiceCliCmd(choice = list_gre_ctx,
307                                       help_str = "An existing gre context."),
308         },
309         cb = delete_gre,
310         help_str = "Delete a GRE context."
311     ),
312     display_cmd,
313     help_cmd,
314 ])
315
316
317 cmdline = pycol.Cmdline()
318 cmdline.add_context("main", main_ctx)
319 cmdline.add_context("gre", gre_ctx)
320 cmdline.set_context("main")
321 cmdline.set_prompt("router{} ")
322 cmdline.input_loop()