blob: a3bb65d3581f5caf1a463053151c3a0538677d18 [file] [log] [blame]
Radek Krejcid23f0df2017-08-31 16:34:49 +02001"""
2Manipulation with the YANG schemas.
3File: schemas.py
4Author: Radek Krejci <rkrejci@cesnet.cz>
5"""
6
Radek Krejci67c922d2017-09-21 13:56:41 +02007import json
8import os
9import errno
10import time
11
12from liberouterapi import auth
Radek Krejcid23f0df2017-08-31 16:34:49 +020013from flask import request
Radek Krejci0f793fe2018-02-14 08:33:45 +010014import yang
Radek Krejcid23f0df2017-08-31 16:34:49 +020015
Radek Krejci2b9bbc22017-09-21 13:20:48 +020016from .inventory import INVENTORY, inventory_check
Radek Krejcid23f0df2017-08-31 16:34:49 +020017from .error import NetopeerException
18
Radek Krejcid23f0df2017-08-31 16:34:49 +020019__SCHEMAS_EMPTY = '{"schemas":{"timestamp":0,"schema":[]}}'
20
Radek Krejci0f793fe2018-02-14 08:33:45 +010021
22def __schema_parse(path, format = yang.LYS_IN_UNKNOWN):
Radek Krejcid23f0df2017-08-31 16:34:49 +020023 try:
Radek Krejci0f793fe2018-02-14 08:33:45 +010024 ctx = yang.Context(os.path.dirname(path))
Radek Krejcid23f0df2017-08-31 16:34:49 +020025 except Exception as e:
26 raise NetopeerException(str(e))
27
Radek Krejci3cb753f2017-09-08 16:14:29 +020028 try:
Radek Krejci0f793fe2018-02-14 08:33:45 +010029 module = ctx.parse_path(path, yang.LYS_IN_YANG if format == yang.LYS_IN_UNKNOWN else format)
Radek Krejci3cb753f2017-09-08 16:14:29 +020030 except Exception as e:
Radek Krejci0f793fe2018-02-14 08:33:45 +010031 if format != yang.LYS_IN_UNKOWN:
Radek Krejci3cb753f2017-09-08 16:14:29 +020032 raise NetopeerException(str(e))
33 try:
34 module = ctx.parse_path(path, ly_LYS_IN_YIN)
35 except Exception as e:
36 raise NetopeerException(str(e))
37
38 return module
Radek Krejcid23f0df2017-08-31 16:34:49 +020039
40
41def __schemas_init():
42 schemas = json.loads(__SCHEMAS_EMPTY)
43 try:
Radek Krejci0f793fe2018-02-14 08:33:45 +010044 ctx = yang.Context()
Radek Krejcid23f0df2017-08-31 16:34:49 +020045 except Exception as e:
46 raise NetopeerException(str(e))
47
48 # initialize the list with libyang's internal modules
49 modules = ctx.get_module_iter()
50 for module in modules:
51 schemas['schemas']['schema'].append({'name':module.name(),'revision':module.rev().date()})
52 return schemas
53
54
Radek Krejci3cb753f2017-09-08 16:14:29 +020055def __schemas_inv_load(path):
56 schemainv_path = os.path.join(path, 'schemas.json')
Radek Krejcid23f0df2017-08-31 16:34:49 +020057 try:
58 with open(schemainv_path, 'r') as schemas_file:
59 schemas = json.load(schemas_file)
60 except OSError as e:
61 if e.errno == errno.ENOENT:
62 schemas = __schemas_init()
63 else:
64 raise NetopeerException('Unable to use user\'s schemas inventory ' + schemainv_path + ' (' + str(e) + ').')
65 except ValueError:
66 schemas = __schemas_init()
Radek Krejci3cb753f2017-09-08 16:14:29 +020067
68 return schemas
69
70def __schemas_inv_save(path, schemas):
71 schemainv_path = os.path.join(path, 'schemas.json')
Radek Krejcid23f0df2017-08-31 16:34:49 +020072
73 # update the timestamp
74 schemas['schemas']['timestamp'] = time.time()
Radek Krejci3cb753f2017-09-08 16:14:29 +020075
Radek Krejcid23f0df2017-08-31 16:34:49 +020076 #store the list
77 try:
78 with open(schemainv_path, 'w') as schema_file:
79 json.dump(schemas, schema_file)
80 except Exception:
81 pass
Radek Krejci3cb753f2017-09-08 16:14:29 +020082
83 return schemas
84
85def __schemas_update(path):
86 # get schemas database
87 schemas = __schemas_inv_load(path)
88
89 # get the previous timestamp
90 timestamp = schemas['schemas']['timestamp']
91
92 # check the current content of the storage
93 for file in os.listdir(path):
94 if file[-5:] == '.yang':
Radek Krejci0f793fe2018-02-14 08:33:45 +010095 format = yang.LYS_IN_YANG
Radek Krejci3cb753f2017-09-08 16:14:29 +020096 elif file[-4:] == '.yin':
Radek Krejci0f793fe2018-02-14 08:33:45 +010097 format = yang.LYS_IN_YIN
Radek Krejci3cb753f2017-09-08 16:14:29 +020098 else:
99 continue
100
101 schemapath = os.path.join(path, file);
102 if os.path.getmtime(schemapath) > timestamp:
103 # update the list
Radek Krejci3cb753f2017-09-08 16:14:29 +0200104 try:
105 module = __schema_parse(schemapath, format)
106 if module.rev_size():
107 schemas['schemas']['schema'].append({'name':module.name(), 'revision':module.rev().date()})
108 else:
109 schemas['schemas']['schema'].append({'name':module.name()})
110 except Exception as e:
111 continue
112
113 #store the list
114 __schemas_inv_save(path, schemas)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200115
116 # return the up-to-date list
117 return schemas['schemas']['schema']
118
Radek Krejcid23f0df2017-08-31 16:34:49 +0200119@auth.required()
120def schemas_list():
121 session = auth.lookup(request.headers.get('Authorization', None))
122 user = session['user']
Radek Krejci2b9bbc22017-09-21 13:20:48 +0200123 path = os.path.join(INVENTORY, user.username)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200124
Radek Krejcib2feaac2017-09-21 13:16:54 +0200125 inventory_check(path)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200126 schemas = __schemas_update(path)
127
128 return(json.dumps(schemas))
129
130@auth.required()
131def schemas_add():
132 if 'schema' not in request.files:
133 raise NetopeerException('Missing schema file in upload request.')
134
135 session = auth.lookup(request.headers.get('Authorization', None))
136 user = session['user']
137 file = request.files['schema']
138
139 # store the file
Radek Krejci2b9bbc22017-09-21 13:20:48 +0200140 path = os.path.join(INVENTORY, user.username, file.filename)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200141 file.save(path)
142
143 # parse file
144 try:
Radek Krejci3cb753f2017-09-08 16:14:29 +0200145 if file.filename[-5:] == '.yang':
Radek Krejci0f793fe2018-02-14 08:33:45 +0100146 format = yang.LYS_IN_YANG
Radek Krejci3cb753f2017-09-08 16:14:29 +0200147 elif file.filename[-4:] == '.yin':
Radek Krejci0f793fe2018-02-14 08:33:45 +0100148 format = yang.LYS_IN_YIN
Radek Krejci3cb753f2017-09-08 16:14:29 +0200149 else:
Radek Krejci0f793fe2018-02-14 08:33:45 +0100150 format = yang.LYS_IN_UNKNOWN
Radek Krejci3cb753f2017-09-08 16:14:29 +0200151 module = __schema_parse(path, format)
152 # TODO: normalize file name to allow removing without remembering schema path
Radek Krejcid23f0df2017-08-31 16:34:49 +0200153 except Exception:
154 os.remove(path)
155 return(json.dumps({'success': False}))
156
157 return(json.dumps({'success': True}))
Radek Krejci3cb753f2017-09-08 16:14:29 +0200158
159@auth.required()
160def schemas_rm():
161 session = auth.lookup(request.headers.get('Authorization', None))
162 user = session['user']
Radek Krejci2b9bbc22017-09-21 13:20:48 +0200163 path = os.path.join(INVENTORY, user.username)
Radek Krejci3cb753f2017-09-08 16:14:29 +0200164
165 schema_rm = request.get_json()
166 if not schema_rm:
167 raise NetopeerException('Invalid schema remove request.')
168
169 schemas = __schemas_inv_load(path)
170 for i in range(len(schemas['schemas']['schema'])):
171 schema = schemas['schemas']['schema'][i]
172 if 'revision' in schema_rm:
173 if schema['name'] != schema_rm['name'] or not 'revision' in schema or schema['revision'] != schema_rm['revision']:
174 schema = None
175 continue
176 else:
177 if schema['name'] != schema_rm['name'] or 'revision' in schema:
178 schema = None
179 continue
180 schemas['schemas']['schema'].pop(i)
181 break;
182
183 if not schema:
184 # schema not in inventory
185 return (json.dumps({'success': False}))
186
187 # update the inventory database
188 __schemas_inv_save(path, schemas)
189
190 # remove the schema file
191 if 'revision' in schema:
192 path = os.path.join(path, schema['name'] + '@' + schema['revision'] + '.yang')
193 else:
194 path = os.path.join(path, schema['name'] + '.yang')
195 os.remove(path)
196
197 # TODO: resolve dependencies
198
199 return(json.dumps({'success': True}))