blob: 1500d0d22beab1c5fa8bf111134e8683471bae63 [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
13from .error import NetopeerException
14
15__INVENTORY = config.modules['netopeer']['usersdata_path']
16__SCHEMAS_EMPTY = '{"schemas":{"timestamp":0,"schema":[]}}'
17
Radek Krejci3cb753f2017-09-08 16:14:29 +020018def __schema_parse(path, format=ly.LYS_IN_UNKNOWN):
Radek Krejcid23f0df2017-08-31 16:34:49 +020019 try:
Radek Krejci3cb753f2017-09-08 16:14:29 +020020 ctx = ly.Context(os.path.dirname(path))
Radek Krejcid23f0df2017-08-31 16:34:49 +020021 except Exception as e:
22 raise NetopeerException(str(e))
23
Radek Krejci3cb753f2017-09-08 16:14:29 +020024 try:
25 module = ctx.parse_path(path, ly.LYS_IN_YANG if format == ly.LYS_IN_UNKNOWN else format)
26 except Exception as e:
27 if format != ly.LYS_IN_UNKOWN:
28 raise NetopeerException(str(e))
29 try:
30 module = ctx.parse_path(path, ly_LYS_IN_YIN)
31 except Exception as e:
32 raise NetopeerException(str(e))
33
34 return module
Radek Krejcid23f0df2017-08-31 16:34:49 +020035
36
37def __schemas_init():
38 schemas = json.loads(__SCHEMAS_EMPTY)
39 try:
40 ctx = ly.Context()
41 except Exception as e:
42 raise NetopeerException(str(e))
43
44 # initialize the list with libyang's internal modules
45 modules = ctx.get_module_iter()
46 for module in modules:
47 schemas['schemas']['schema'].append({'name':module.name(),'revision':module.rev().date()})
48 return schemas
49
50
Radek Krejci3cb753f2017-09-08 16:14:29 +020051def __schemas_inv_load(path):
52 schemainv_path = os.path.join(path, 'schemas.json')
Radek Krejcid23f0df2017-08-31 16:34:49 +020053 try:
54 with open(schemainv_path, 'r') as schemas_file:
55 schemas = json.load(schemas_file)
56 except OSError as e:
57 if e.errno == errno.ENOENT:
58 schemas = __schemas_init()
59 else:
60 raise NetopeerException('Unable to use user\'s schemas inventory ' + schemainv_path + ' (' + str(e) + ').')
61 except ValueError:
62 schemas = __schemas_init()
Radek Krejci3cb753f2017-09-08 16:14:29 +020063
64 return schemas
65
66def __schemas_inv_save(path, schemas):
67 schemainv_path = os.path.join(path, 'schemas.json')
Radek Krejcid23f0df2017-08-31 16:34:49 +020068
69 # update the timestamp
70 schemas['schemas']['timestamp'] = time.time()
Radek Krejci3cb753f2017-09-08 16:14:29 +020071
Radek Krejcid23f0df2017-08-31 16:34:49 +020072 #store the list
73 try:
74 with open(schemainv_path, 'w') as schema_file:
75 json.dump(schemas, schema_file)
76 except Exception:
77 pass
Radek Krejci3cb753f2017-09-08 16:14:29 +020078
79 return schemas
80
81def __schemas_update(path):
82 # get schemas database
83 schemas = __schemas_inv_load(path)
84
85 # get the previous timestamp
86 timestamp = schemas['schemas']['timestamp']
87
88 # check the current content of the storage
89 for file in os.listdir(path):
90 if file[-5:] == '.yang':
91 format = ly.LYS_IN_YANG
92 elif file[-4:] == '.yin':
93 format = ly.LYS_IN_YIN
94 else:
95 continue
96
97 schemapath = os.path.join(path, file);
98 if os.path.getmtime(schemapath) > timestamp:
99 # update the list
100 print("updating by " + schemapath)
101 try:
102 module = __schema_parse(schemapath, format)
103 if module.rev_size():
104 schemas['schemas']['schema'].append({'name':module.name(), 'revision':module.rev().date()})
105 else:
106 schemas['schemas']['schema'].append({'name':module.name()})
107 except Exception as e:
108 continue
109
110 #store the list
111 __schemas_inv_save(path, schemas)
Radek Krejcid23f0df2017-08-31 16:34:49 +0200112
113 # return the up-to-date list
114 return schemas['schemas']['schema']
115
116def __schemas_check(path):
117 try:
118 os.makedirs(path, mode=0o750)
119 except OSError as e:
120 if e.errno == errno.EEXIST and os.path.isdir(path):
121 pass
122 elif e.errno == errno.EEXIST:
123 raise NetopeerException('User\'s schemas inventory (' + path + ') already exists and it\'s not a directory.')
124 else:
125 raise NetopeerException('Unable to use schemas inventory path ' + path +' (' + str(e) + ').')
126
127
128@auth.required()
129def schemas_list():
130 session = auth.lookup(request.headers.get('Authorization', None))
131 user = session['user']
132 path = os.path.join(__INVENTORY, user.username)
133
134 __schemas_check(path)
135 schemas = __schemas_update(path)
136
137 return(json.dumps(schemas))
138
139@auth.required()
140def schemas_add():
141 if 'schema' not in request.files:
142 raise NetopeerException('Missing schema file in upload request.')
143
144 session = auth.lookup(request.headers.get('Authorization', None))
145 user = session['user']
146 file = request.files['schema']
147
148 # store the file
149 path = os.path.join(__INVENTORY, user.username, file.filename)
150 file.save(path)
151
152 # parse file
153 try:
Radek Krejci3cb753f2017-09-08 16:14:29 +0200154 if file.filename[-5:] == '.yang':
155 format = ly.LYS_IN_YANG
156 elif file.filename[-4:] == '.yin':
157 format = ly.LYS_IN_YIN
158 else:
159 format = ly.LYS_IN_UNKNOWN
160 module = __schema_parse(path, format)
161 # TODO: normalize file name to allow removing without remembering schema path
Radek Krejcid23f0df2017-08-31 16:34:49 +0200162 except Exception:
163 os.remove(path)
164 return(json.dumps({'success': False}))
165
166 return(json.dumps({'success': True}))
Radek Krejci3cb753f2017-09-08 16:14:29 +0200167
168@auth.required()
169def schemas_rm():
170 session = auth.lookup(request.headers.get('Authorization', None))
171 user = session['user']
172 path = os.path.join(__INVENTORY, user.username)
173
174 schema_rm = request.get_json()
175 if not schema_rm:
176 raise NetopeerException('Invalid schema remove request.')
177
178 schemas = __schemas_inv_load(path)
179 for i in range(len(schemas['schemas']['schema'])):
180 schema = schemas['schemas']['schema'][i]
181 if 'revision' in schema_rm:
182 if schema['name'] != schema_rm['name'] or not 'revision' in schema or schema['revision'] != schema_rm['revision']:
183 schema = None
184 continue
185 else:
186 if schema['name'] != schema_rm['name'] or 'revision' in schema:
187 schema = None
188 continue
189 schemas['schemas']['schema'].pop(i)
190 break;
191
192 if not schema:
193 # schema not in inventory
194 return (json.dumps({'success': False}))
195
196 # update the inventory database
197 __schemas_inv_save(path, schemas)
198
199 # remove the schema file
200 if 'revision' in schema:
201 path = os.path.join(path, schema['name'] + '@' + schema['revision'] + '.yang')
202 else:
203 path = os.path.join(path, schema['name'] + '.yang')
204 os.remove(path)
205
206 # TODO: resolve dependencies
207
208 return(json.dumps({'success': True}))