1""" 2This file contains the FactoryLoader class that is used to dynamically 3create Runstat Table and View objects from a <dict> of data. The <dict> can 4originate from any kind of source: YAML, JSON, program. For examples of YAML 5refer to the .yml files in this jnpr.junos.op directory. 6""" 7# stdlib 8from copy import deepcopy 9import re 10 11from jinja2 import Environment 12 13# locally 14from jnpr.junos.factory.factory_cls import * 15from jnpr.junos.factory.viewfields import * 16 17__all__ = ["FactoryLoader"] 18 19# internally used shortcuts 20 21_VIEW = FactoryView 22_CMDVIEW = FactoryCMDView 23_FIELDS = ViewFields 24_GET = FactoryOpTable 25_TABLE = FactoryTable 26_CFGTBL = FactoryCfgTable 27_CMDTBL = FactoryCMDTable 28_CMDCHILDTBL = FactoryCMDChildTable 29 30 31class FactoryLoader(object): 32 33 """ 34 Used to load a <dict> of data that contains Table and View definitions. 35 36 The primary method is :load(): which will return a <dict> of item-name and 37 item-class definitions. 38 39 If you want to import these definitions directly into your namespace, 40 (like a module) you would do the following: 41 42 loader = FactoryLoader() 43 catalog = loader.load( <catalog_dict> ) 44 globals().update( catalog ) 45 46 If you did not want to do this, you can access the items as the catalog. 47 For example, if your <catalog_dict> contained a Table called MyTable, then 48 you could do something like: 49 50 MyTable = catalog['MyTable'] 51 table = MyTable(dev) 52 table.get() 53 ... 54 """ 55 56 def __init__(self): 57 self._catalog_dict = None # YAML data 58 59 self._item_optables = [] # list of the get/op-tables 60 self._item_cfgtables = [] # list of get/cfg-tables 61 self._item_cmdtables = [] # list of commands with unstructured data o/p 62 self._item_views = [] # list of views to build 63 self._item_tables = [] # list of tables to build 64 65 self.catalog = {} # catalog of built classes 66 67 # ----------------------------------------------------------------------- 68 # Create a View class from YAML definition 69 # ----------------------------------------------------------------------- 70 71 def _fieldfunc_True(self, value_rhs): 72 def true_test(x): 73 if value_rhs.startswith("regex("): 74 return True if bool(re.search(value_rhs.strip("regex()"), x)) else False 75 return x == value_rhs 76 77 return true_test 78 79 def _fieldfunc_False(self, value_rhs): 80 def false_test(x): 81 if value_rhs.startswith("regex("): 82 return False if bool(re.search(value_rhs.strip("regex()"), x)) else True 83 return x != value_rhs 84 85 return false_test 86 87 def _fieldfunc_Search(self, regex_pattern): 88 def search_field(field_text): 89 """ Returns the first occurrence of regex_pattern within given field_text.""" 90 match = re.search(regex_pattern, field_text) 91 if match: 92 return match.groups()[0] 93 else: 94 return None 95 96 return search_field 97 98 def _add_dictfield(self, fields, f_name, f_dict, kvargs): 99 """ add a field based on its associated dictionary """ 100 # at present if a field is a <dict> then there is **one 101 # item** - { the xpath value : the option control }. typically 102 # the option would be a bultin class type like 'int' 103 # however, as this framework expands in capability, this 104 # will be enhaced, yo! 105 106 xpath, opt = list(f_dict.items())[0] # get first/only key,value 107 108 if opt == "group": 109 fields.group(f_name, xpath) 110 return 111 112 if "flag" == opt: 113 opt = "bool" # flag is alias for bool 114 115 # first check to see if the option is a built-in Python 116 # type, most commonly would be 'int' for numbers, like counters 117 if isinstance(opt, dict): 118 kvargs.update(opt) 119 fields.str(f_name, xpath, **kvargs) 120 return 121 122 astype = __builtins__.get(opt) or globals().get(opt) 123 if astype is not None: 124 kvargs["astype"] = astype 125 fields.astype(f_name, xpath, **kvargs) 126 return 127 128 # next check to see if this is a "field-function" 129 # operator in the form "func=value", like "True=enabled" 130 131 if isinstance(opt, str) and opt.find("=") > 0: 132 field_cmd, value_rhs = opt.split("=") 133 fn_field = "_fieldfunc_" + field_cmd 134 if not hasattr(self, fn_field): 135 raise ValueError("Unknown field-func: '%'" % field_cmd) 136 kvargs["astype"] = getattr(self, fn_field)(value_rhs) 137 fields.astype(f_name, xpath, **kvargs) 138 return 139 140 raise RuntimeError("Dont know what to do with field: '%s'" % f_name) 141 142 # ---[ END: _add_dictfield ] --------------------------------------------- 143 144 def _add_view_fields(self, view_dict, fields_name, fields): 145 """ add a group of fields to the view """ 146 fields_dict = view_dict[fields_name] 147 try: 148 # see if this is a 'fields_<group>' collection, and if so 149 # then we automatically setup using the group mechanism 150 mark = fields_name.index("_") 151 group = {"group": fields_name[mark + 1 :]} 152 except: 153 # otherwise, no group, just standard 'fields' 154 group = {} 155 156 for f_name, f_data in fields_dict.items(): 157 # each field could have its own unique set of properties 158 # so create a kvargs <dict> each time. but copy in the 159 # groups <dict> (single item) generically. 160 kvargs = {} 161 kvargs.update(group) 162 163 if isinstance(f_data, dict): 164 self._add_dictfield(fields, f_name, f_data, kvargs) 165 continue 166 167 if f_data in self._catalog_dict: 168 # f_data is the table name 169 cls_tbl = self.catalog.get(f_data, self._build_table(f_data)) 170 fields.table(f_name, cls_tbl) 171 continue 172 173 # if we are here, then it means that the field is a string value 174 xpath = f_name if f_data is True else f_data 175 fields.str(f_name, xpath, **kvargs) 176 177 def _add_cmd_view_fields(self, view_dict, fields_name, fields): 178 """ add a group of fields to the view """ 179 fields_dict = view_dict[fields_name] 180 for f_name, f_data in fields_dict.items(): 181 if f_data in self._catalog_dict: 182 cls_tbl = self.catalog.get(f_data, self._build_cmdtable(f_data)) 183 fields.table(f_name, cls_tbl) 184 continue 185 186 # if we are here, it means we need to filter fields from textfsm 187 fields._fields.update({f_name: f_data}) 188 189 # ------------------------------------------------------------------------- 190 191 def _build_view(self, view_name): 192 """ build a new View definition """ 193 if view_name in self.catalog: 194 return self.catalog[view_name] 195 196 view_dict = self._catalog_dict[view_name] 197 kvargs = {"view_name": view_name} 198 199 # if there are field groups, then get that now. 200 if "groups" in view_dict: 201 kvargs["groups"] = view_dict["groups"] 202 203 # if there are eval, then get that now. 204 if "eval" in view_dict: 205 kvargs["eval"] = {} 206 for key, exp in view_dict["eval"].items(): 207 env = Environment() 208 kvargs["eval"][key] = env.parse(exp) 209 210 # if this view extends another ... 211 if "extends" in view_dict: 212 base_cls = self.catalog.get(view_dict["extends"]) 213 # @@@ should check for base_cls is None! 214 kvargs["extends"] = base_cls 215 216 fields = _FIELDS() 217 fg_list = [name for name in view_dict if name.startswith("fields")] 218 for fg_name in fg_list: 219 self._add_view_fields(view_dict, fg_name, fields) 220 221 cls = _VIEW(fields.end, **kvargs) 222 self.catalog[view_name] = cls 223 return cls 224 225 # ------------------------------------------------------------------------- 226 227 def _build_cmdview(self, view_name): 228 """ build a new View definition """ 229 if view_name in self.catalog: 230 return self.catalog[view_name] 231 232 view_dict = self._catalog_dict[view_name] 233 kvargs = {"view_name": view_name} 234 235 if "columns" in view_dict: 236 kvargs["columns"] = view_dict["columns"] 237 elif "title" in view_dict: 238 kvargs["title"] = view_dict["title"] 239 if "regex" in view_dict: 240 kvargs["regex"] = view_dict["regex"] 241 if "exists" in view_dict: 242 kvargs["exists"] = view_dict["exists"] 243 if "filters" in view_dict: 244 kvargs["filters"] = view_dict["filters"] 245 if "eval" in view_dict: 246 kvargs["eval"] = {} 247 for key, exp in view_dict["eval"].items(): 248 env = Environment() 249 kvargs["eval"][key] = env.parse(exp) 250 fields = _FIELDS() 251 fg_list = [name for name in view_dict if name.startswith("fields")] 252 for fg_name in fg_list: 253 self._add_cmd_view_fields(view_dict, fg_name, fields) 254 255 cls = _CMDVIEW(fields.end, **kvargs) 256 self.catalog[view_name] = cls 257 return cls 258 259 # ----------------------------------------------------------------------- 260 # Create a Get-Table from YAML definition 261 # ----------------------------------------------------------------------- 262 263 def _build_optable(self, table_name): 264 """ build a new Get-Table definition """ 265 if table_name in self.catalog: 266 return self.catalog[table_name] 267 268 tbl_dict = self._catalog_dict[table_name] 269 kvargs = deepcopy(tbl_dict) 270 271 rpc = kvargs.pop("rpc") 272 kvargs["table_name"] = table_name 273 274 if "view" in tbl_dict: 275 view_name = tbl_dict["view"] 276 cls_view = self.catalog.get(view_name, self._build_view(view_name)) 277 kvargs["view"] = cls_view 278 279 cls = _GET(rpc, **kvargs) 280 self.catalog[table_name] = cls 281 return cls 282 283 # ----------------------------------------------------------------------- 284 # Create a Get-Table from YAML definition 285 # ----------------------------------------------------------------------- 286 287 def _build_cmdtable(self, table_name): 288 """ build a new command-Table definition """ 289 if table_name in self.catalog: 290 return self.catalog[table_name] 291 292 tbl_dict = self._catalog_dict[table_name] 293 kvargs = deepcopy(tbl_dict) 294 295 if "command" in kvargs: 296 cmd = kvargs.pop("command") 297 kvargs["table_name"] = table_name 298 299 if "view" in tbl_dict: 300 view_name = tbl_dict["view"] 301 cls_view = self.catalog.get(view_name, self._build_cmdview(view_name)) 302 kvargs["view"] = cls_view 303 304 cls = _CMDTBL(cmd, **kvargs) 305 self.catalog[table_name] = cls 306 return cls 307 elif "title" in kvargs: 308 cmd = kvargs.pop("title") 309 kvargs["table_name"] = table_name 310 311 if "view" in tbl_dict: 312 view_name = tbl_dict["view"] 313 cls_view = self.catalog.get(view_name, self._build_cmdview(view_name)) 314 kvargs["view"] = cls_view 315 316 cls = _CMDCHILDTBL(cmd, **kvargs) 317 self.catalog[table_name] = cls 318 return cls 319 else: 320 kvargs["table_name"] = table_name 321 322 if "view" in tbl_dict: 323 view_name = tbl_dict["view"] 324 cls_view = self.catalog.get(view_name, self._build_cmdview(view_name)) 325 kvargs["view"] = cls_view 326 327 cls = _CMDCHILDTBL(**kvargs) 328 self.catalog[table_name] = cls 329 return cls 330 331 # ----------------------------------------------------------------------- 332 # Create a Table class from YAML definition 333 # ----------------------------------------------------------------------- 334 335 def _build_table(self, table_name): 336 """ build a new Table definition """ 337 if table_name in self.catalog: 338 return self.catalog[table_name] 339 340 tbl_dict = self._catalog_dict[table_name] 341 342 table_item = tbl_dict.pop("item") 343 kvargs = deepcopy(tbl_dict) 344 kvargs["table_name"] = table_name 345 346 if "view" in tbl_dict: 347 view_name = tbl_dict["view"] 348 cls_view = self.catalog.get(view_name, self._build_view(view_name)) 349 kvargs["view"] = cls_view 350 351 cls = _TABLE(table_item, **kvargs) 352 self.catalog[table_name] = cls 353 return cls 354 355 def _build_cfgtable(self, table_name): 356 """ build a new Config-Table definition """ 357 if table_name in self.catalog: 358 return self.catalog[table_name] 359 tbl_dict = deepcopy(self._catalog_dict[table_name]) 360 361 if "view" in tbl_dict: 362 # transpose name to class 363 view_name = tbl_dict["view"] 364 tbl_dict["view"] = self.catalog.get(view_name, self._build_view(view_name)) 365 366 cls = _CFGTBL(table_name, tbl_dict) 367 self.catalog[table_name] = cls 368 return cls 369 370 # ----------------------------------------------------------------------- 371 # Primary builders ... 372 # ----------------------------------------------------------------------- 373 374 def _sortitems(self): 375 for k, v in self._catalog_dict.items(): 376 if "rpc" in v: 377 self._item_optables.append(k) 378 elif "get" in v: 379 self._item_cfgtables.append(k) 380 elif "set" in v: 381 self._item_cfgtables.append(k) 382 elif "command" in v or "title" in v: 383 self._item_cmdtables.append(k) 384 elif "view" in v and "item" in v and v["item"] == "*": 385 self._item_cmdtables.append(k) 386 elif "view" in v: 387 self._item_tables.append(k) 388 else: 389 self._item_views.append(k) 390 391 def load(self, catalog_dict, envrion={}): 392 393 # load the yaml data and extract the item names. these names will 394 # become the new class definitions 395 396 self._catalog_dict = catalog_dict 397 self._sortitems() 398 399 list(map(self._build_optable, self._item_optables)) 400 list(map(self._build_cfgtable, self._item_cfgtables)) 401 list(map(self._build_cmdtable, self._item_cmdtables)) 402 list(map(self._build_table, self._item_tables)) 403 list(map(self._build_view, self._item_views)) 404 405 return self.catalog 406