blob: 35bc5bcb9c7fd07e6d739f560425021ce7f6f4e3 [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
7from liberouterapi import auth, config
8from flask import request
9import libyang as ly
10import json
Radek Krejci3cb753f2017-09-08 16:14:29 +020011import os, errno, time, sys
Radek Krejcid23f0df2017-08-31 16:34:49 +020012
Radek Krejcib2feaac2017-09-21 13:16:54 +020013from .inventory import inventory_check
Radek Krejcid23f0df2017-08-31 16:34:49 +020014from .error import NetopeerException
15
16__INVENTORY = config.modules['netopeer']['usersdata_path']
17__SCHEMAS_EMPTY = '{"schemas":{"timestamp":0,"schema":[]}}'
18
Radek Krejci3cb753f2017-09-08 16:14:29 +020019def __schema_parse(path, format=ly.LYS_IN_UNKNOWN):
Radek Krejcid23f0df2017-08-31 16:34:49 +020020 try:
Radek Krejci3cb753f2017-09-08 16:14:29 +020021 ctx = ly.Context(os.path.dirname(path))
Radek Krejcid23f0df2017-08-31 16:34:49 +020022 except Exception as e:
23 raise NetopeerException(str(e))
24
Radek Krejci3cb753f2017-09-08 16:14:29 +020025 try:
26 module = ctx.parse_path(path, ly.LYS_IN_YANG if format == ly.LYS_IN_UNKNOWN else format)
27 except Exception as e:
28 if format != ly.LYS_IN_UNKOWN:
29 raise NetopeerException(str(e))
30 try:
31 module = ctx.parse_path(path, ly_LYS_IN_YIN)
32 except Exception as e:
33 raise NetopeerException(str(e))
34
35 return module
Radek Krejcid23f0df2017-08-31 16:34:49 +020036
37
38def __schemas_init():
39 schemas = json.loads(__SCHEMAS_EMPTY)
40 try:
41 ctx = ly.Context()
42 except Exception as e:
43 raise NetopeerException(str(e))
44
45 # initialize the list with libyang's internal modules
46 modules = ctx.get_module_iter()
47 for module in modules:
48 schemas['schemas']['schema'].append({'name':module.name(),'revision':module.rev().date()})
49 return schemas
50
51
Radek Krejci3cb753f2017-09-08 16:14:29 +020052def __schemas_inv_load(path):
53 schemainv_path = os.path.join(path, 'schemas.json')
Radek Krejcid23f0df2017-08-31 16:34:49 +020054 try:
55 with open(schemainv_path, 'r') as schemas_file:
56 schemas = json.load(schemas_file)
57 except OSError as e:
58 if e.errno == errno.ENOENT:
59 schemas = __schemas_init()
60 else:
61 raise NetopeerException('Unable to use user\'s schemas inventory ' + schemainv_path + ' (' + str(e) + ').')
62 except ValueError:
63 schemas = __schemas_init()
Radek Krejci3cb753f2017-09-08 16:14:29 +020064
65 return schemas
66
67def __schemas_inv_save(path, schemas):
68 schemainv_path = os.path.join(path, 'schemas.json')
Radek Krejcid23f0df2017-08-31 16:34:49 +020069
70 # update the timestamp
71 schemas['schemas']['timestamp'] = time.time()
Radek Krejci3cb753f2017-09-08 16:14:29 +020072
Radek Krejcid23f0df2017-08-31 16:34:49 +020073 #store the list
74 try:
75 with open(schemainv_path, 'w') as schema_file:
76 json.dump(schemas, schema_file)
77 except Exception:
78 pass
Radek Krejci3cb753f2017-09-08 16:14:29 +020079
80 return schemas
81
82def __schemas_update(path):
83 # get schemas database
84 schemas = __schemas_inv_load(path)
85
86 # get the previous timestamp
87 timestamp = schemas['schemas']['timestamp']
88
89 # check the current content of the storage
90 for file in os.listdir(path):
91 if file[-5:] == '.yang':
92 format = ly.LYS_IN_YANG
93 elif file[-4:] == '.yin':
94 format = ly.LYS_IN_YIN
95 else:
96 continue
97
98 schemapath = os.path.join(path, file);
99 if os.path.getmtime(schemapath) > timestamp:
100 # update the list
101 print("updating by " + schemapath)
102 try:
103 module = __schema_parse(schemapath, format)
104 if module.rev_size():
105 schemas['schemas']['schema'].append({'name':module.name(), 'revision':module.rev().date()})
106 else:
107 schemas['schemas']['schema'].append({'name':module.name()})
108 except Exception as e:
109 continue
110
111 #store the list
112 __schemas_inv_save(path, schemas)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200113
114 # return the up-to-date list
115 return schemas['schemas']['schema']
116
Radek Krejcid23f0df2017-08-31 16:34:49 +0200117@auth.required()
118def schemas_list():
119 session = auth.lookup(request.headers.get('Authorization', None))
120 user = session['user']
121 path = os.path.join(__INVENTORY, user.username)
122
Radek Krejcib2feaac2017-09-21 13:16:54 +0200123 inventory_check(path)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200124 schemas = __schemas_update(path)
125
126 return(json.dumps(schemas))
127
128@auth.required()
129def schemas_add():
130 if 'schema' not in request.files:
131 raise NetopeerException('Missing schema file in upload request.')
132
133 session = auth.lookup(request.headers.get('Authorization', None))
134 user = session['user']
135 file = request.files['schema']
136
137 # store the file
138 path = os.path.join(__INVENTORY, user.username, file.filename)
139 file.save(path)
140
141 # parse file
142 try:
Radek Krejci3cb753f2017-09-08 16:14:29 +0200143 if file.filename[-5:] == '.yang':
144 format = ly.LYS_IN_YANG
145 elif file.filename[-4:] == '.yin':
146 format = ly.LYS_IN_YIN
147 else:
148 format = ly.LYS_IN_UNKNOWN
149 module = __schema_parse(path, format)
150 # TODO: normalize file name to allow removing without remembering schema path
Radek Krejcid23f0df2017-08-31 16:34:49 +0200151 except Exception:
152 os.remove(path)
153 return(json.dumps({'success': False}))
154
155 return(json.dumps({'success': True}))
Radek Krejci3cb753f2017-09-08 16:14:29 +0200156
157@auth.required()
158def schemas_rm():
159 session = auth.lookup(request.headers.get('Authorization', None))
160 user = session['user']
161 path = os.path.join(__INVENTORY, user.username)
162
163 schema_rm = request.get_json()
164 if not schema_rm:
165 raise NetopeerException('Invalid schema remove request.')
166
167 schemas = __schemas_inv_load(path)
168 for i in range(len(schemas['schemas']['schema'])):
169 schema = schemas['schemas']['schema'][i]
170 if 'revision' in schema_rm:
171 if schema['name'] != schema_rm['name'] or not 'revision' in schema or schema['revision'] != schema_rm['revision']:
172 schema = None
173 continue
174 else:
175 if schema['name'] != schema_rm['name'] or 'revision' in schema:
176 schema = None
177 continue
178 schemas['schemas']['schema'].pop(i)
179 break;
180
181 if not schema:
182 # schema not in inventory
183 return (json.dumps({'success': False}))
184
185 # update the inventory database
186 __schemas_inv_save(path, schemas)
187
188 # remove the schema file
189 if 'revision' in schema:
190 path = os.path.join(path, schema['name'] + '@' + schema['revision'] + '.yang')
191 else:
192 path = os.path.join(path, schema['name'] + '.yang')
193 os.remove(path)
194
195 # TODO: resolve dependencies
196
197 return(json.dumps({'success': True}))