Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 1 | """ |
| 2 | Manipulation with the YANG schemas. |
| 3 | File: schemas.py |
| 4 | Author: Radek Krejci <rkrejci@cesnet.cz> |
| 5 | """ |
| 6 | |
Radek Krejci | 67c922d | 2017-09-21 13:56:41 +0200 | [diff] [blame] | 7 | import json |
| 8 | import os |
| 9 | import errno |
| 10 | import time |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 11 | from subprocess import check_output |
| 12 | from shutil import copy |
Radek Krejci | 67c922d | 2017-09-21 13:56:41 +0200 | [diff] [blame] | 13 | |
| 14 | from liberouterapi import auth |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 15 | from flask import request |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 16 | import yang |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 17 | |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 18 | from .inventory import INVENTORY, inventory_check |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 19 | from .error import NetopeerException |
| 20 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 21 | __SCHEMAS_EMPTY = '{"timestamp":0, "schemas":{}}' |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 22 | |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 23 | |
| 24 | def __schema_parse(path, format = yang.LYS_IN_UNKNOWN): |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 25 | try: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 26 | ctx = yang.Context(os.path.dirname(path)) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 27 | except Exception as e: |
| 28 | raise NetopeerException(str(e)) |
| 29 | |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 30 | try: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 31 | module = ctx.parse_path(path, yang.LYS_IN_YANG if format == yang.LYS_IN_UNKNOWN else format) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 32 | except Exception as e: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 33 | if format != yang.LYS_IN_UNKOWN: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 34 | raise NetopeerException(str(e)) |
| 35 | try: |
| 36 | module = ctx.parse_path(path, ly_LYS_IN_YIN) |
| 37 | except Exception as e: |
| 38 | raise NetopeerException(str(e)) |
| 39 | |
| 40 | return module |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 41 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 42 | |
| 43 | def __schemas_init(path): |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 44 | schemas = json.loads(__SCHEMAS_EMPTY) |
| 45 | try: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 46 | ctx = yang.Context() |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 47 | except Exception as e: |
| 48 | raise NetopeerException(str(e)) |
| 49 | |
| 50 | # initialize the list with libyang's internal modules |
| 51 | modules = ctx.get_module_iter() |
| 52 | for module in modules: |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 53 | name_norm = module.name() + '@' + module.rev().date() + '.yang' |
| 54 | schemas['schemas'][name_norm] = {'name':module.name(), 'revision':module.rev().date()} |
| 55 | try: |
| 56 | with open(os.path.join(path, name_norm), 'w') as schema_file: |
| 57 | schema_file.write(module.print_mem(yang.LYS_OUT_YANG, 0)) |
| 58 | except: |
| 59 | pass |
| 60 | try: |
| 61 | nc_schemas_dir = check_output("pkg-config --variable=LNC2_SCHEMAS_DIR libnetconf2", shell = True).decode() |
| 62 | nc_schemas_dir = nc_schemas_dir[:len(nc_schemas_dir) - 1] |
| 63 | for file in os.listdir(nc_schemas_dir): |
| 64 | if file[-5:] == '.yang' or file[-4:] == '.yin': |
| 65 | try: |
| 66 | copy(os.path.join(nc_schemas_dir, file), path) |
| 67 | except: |
| 68 | pass |
| 69 | else: |
| 70 | continue |
| 71 | except: |
| 72 | pass |
| 73 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 74 | return schemas |
| 75 | |
| 76 | |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 77 | def __schemas_inv_load(path): |
| 78 | schemainv_path = os.path.join(path, 'schemas.json') |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 79 | try: |
| 80 | with open(schemainv_path, 'r') as schemas_file: |
| 81 | schemas = json.load(schemas_file) |
| 82 | except OSError as e: |
| 83 | if e.errno == errno.ENOENT: |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 84 | schemas = __schemas_init(path) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 85 | else: |
| 86 | raise NetopeerException('Unable to use user\'s schemas inventory ' + schemainv_path + ' (' + str(e) + ').') |
| 87 | except ValueError: |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 88 | schemas = __schemas_init(path) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 89 | |
| 90 | return schemas |
| 91 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 92 | |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 93 | def __schemas_inv_save(path, schemas): |
| 94 | schemainv_path = os.path.join(path, 'schemas.json') |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 95 | |
| 96 | # update the timestamp |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 97 | schemas['timestamp'] = time.time() |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 98 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 99 | #store the list |
| 100 | try: |
| 101 | with open(schemainv_path, 'w') as schema_file: |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 102 | json.dump(schemas, schema_file, sort_keys = True) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 103 | except Exception: |
| 104 | pass |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 105 | |
| 106 | return schemas |
| 107 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 108 | |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 109 | def __schemas_update(path): |
| 110 | # get schemas database |
| 111 | schemas = __schemas_inv_load(path) |
| 112 | |
| 113 | # get the previous timestamp |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 114 | timestamp = schemas['timestamp'] |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 115 | |
| 116 | # check the current content of the storage |
| 117 | for file in os.listdir(path): |
| 118 | if file[-5:] == '.yang': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 119 | format = yang.LYS_IN_YANG |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 120 | elif file[-4:] == '.yin': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 121 | format = yang.LYS_IN_YIN |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 122 | else: |
| 123 | continue |
| 124 | |
| 125 | schemapath = os.path.join(path, file); |
| 126 | if os.path.getmtime(schemapath) > timestamp: |
| 127 | # update the list |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 128 | try: |
| 129 | module = __schema_parse(schemapath, format) |
| 130 | if module.rev_size(): |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 131 | name_norm = module.name() + '@' + module.rev().date() + '.yang' |
| 132 | schemas['schemas'][name_norm] = {'name': module.name(), 'revision': module.rev().date()} |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 133 | else: |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 134 | name_norm = module.name() + '.yang' |
| 135 | schemas['schemas'][name_norm] = {'name': module.name()} |
| 136 | if file != name_norm: |
| 137 | try: |
| 138 | with open(os.path.join(path, name_norm), 'w') as schema_file: |
| 139 | schema_file.write(module.print_mem(yang.LYS_OUT_YANG, 0)) |
| 140 | except: |
| 141 | pass |
| 142 | |
| 143 | try: |
| 144 | os.remove(schemapath) |
| 145 | except: |
| 146 | pass |
| 147 | except: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 148 | continue |
| 149 | |
| 150 | #store the list |
| 151 | __schemas_inv_save(path, schemas) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 152 | |
| 153 | # return the up-to-date list |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 154 | return schemas['schemas'] |
| 155 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 156 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 157 | @auth.required() |
| 158 | def schemas_list(): |
| 159 | session = auth.lookup(request.headers.get('Authorization', None)) |
| 160 | user = session['user'] |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 161 | path = os.path.join(INVENTORY, user.username) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 162 | |
Radek Krejci | b2feaac | 2017-09-21 13:16:54 +0200 | [diff] [blame] | 163 | inventory_check(path) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 164 | schemas = __schemas_update(path) |
| 165 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 166 | return(json.dumps(schemas, sort_keys = True)) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 167 | |
Radek Krejci | 6be087d | 2018-02-14 08:53:20 +0100 | [diff] [blame] | 168 | |
| 169 | @auth.required() |
| 170 | def schema_get(): |
| 171 | session = auth.lookup(request.headers.get('Authorization', None)) |
| 172 | user = session['user'] |
| 173 | req = request.args.to_dict() |
| 174 | path = os.path.join(INVENTORY, user.username) |
| 175 | |
| 176 | if not 'key' in req: |
| 177 | return(json.dumps({'success': False, 'error-msg': 'Missing schema key.'})) |
| 178 | key = req['key'] |
| 179 | |
| 180 | schemas = __schemas_inv_load(path) |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 181 | if key in schemas['schemas']: |
| 182 | try: |
| 183 | with open(os.path.join(path, key), 'r') as schema_file: |
| 184 | data = schema_file.read() |
Radek Krejci | 6be087d | 2018-02-14 08:53:20 +0100 | [diff] [blame] | 185 | return(json.dumps({'success': True, 'data': data})) |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 186 | except: |
| 187 | pass; |
Radek Krejci | 6be087d | 2018-02-14 08:53:20 +0100 | [diff] [blame] | 188 | return(json.dumps({'success': False, 'error-msg':'Schema ' + key + ' not found.'})) |
| 189 | |
| 190 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 191 | @auth.required() |
| 192 | def schemas_add(): |
| 193 | if 'schema' not in request.files: |
| 194 | raise NetopeerException('Missing schema file in upload request.') |
| 195 | |
| 196 | session = auth.lookup(request.headers.get('Authorization', None)) |
| 197 | user = session['user'] |
| 198 | file = request.files['schema'] |
| 199 | |
| 200 | # store the file |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 201 | path = os.path.join(INVENTORY, user.username, file.filename) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 202 | file.save(path) |
| 203 | |
| 204 | # parse file |
| 205 | try: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 206 | if file.filename[-5:] == '.yang': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 207 | format = yang.LYS_IN_YANG |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 208 | elif file.filename[-4:] == '.yin': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 209 | format = yang.LYS_IN_YIN |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 210 | else: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 211 | format = yang.LYS_IN_UNKNOWN |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 212 | module = __schema_parse(path, format) |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 213 | |
| 214 | # normalize file name to allow removing without remembering schema path |
| 215 | if module.rev_size(): |
| 216 | name_norm = module.name() + '@' + module.rev().date() + '.yang' |
| 217 | else: |
| 218 | name_norm = module.name() + '.yang' |
| 219 | if file.filename != name_norm: |
| 220 | with open(os.path.join(INVENTORY, user.username, name_norm), 'w') as schema_file: |
| 221 | schema_file.write(module.print_mem(yang.LYS_OUT_YANG, 0)) |
| 222 | try: |
| 223 | os.remove(path) |
| 224 | except: |
| 225 | pass |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 226 | except Exception: |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 227 | try: |
| 228 | os.remove(path) |
| 229 | except: |
| 230 | pass |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 231 | return(json.dumps({'success': False})) |
| 232 | |
| 233 | return(json.dumps({'success': True})) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 234 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 235 | |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 236 | @auth.required() |
| 237 | def schemas_rm(): |
| 238 | session = auth.lookup(request.headers.get('Authorization', None)) |
| 239 | user = session['user'] |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 240 | path = os.path.join(INVENTORY, user.username) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 241 | |
Radek Krejci | 6be087d | 2018-02-14 08:53:20 +0100 | [diff] [blame] | 242 | key = request.get_json() |
| 243 | if not key: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 244 | raise NetopeerException('Invalid schema remove request.') |
| 245 | |
| 246 | schemas = __schemas_inv_load(path) |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 247 | try: |
Radek Krejci | a70c980 | 2018-02-20 15:48:33 +0100 | [diff] [blame^] | 248 | schemas['schemas'].pop(key) |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 249 | except KeyError: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 250 | # schema not in inventory |
| 251 | return (json.dumps({'success': False})) |
| 252 | |
| 253 | # update the inventory database |
| 254 | __schemas_inv_save(path, schemas) |
| 255 | |
| 256 | # remove the schema file |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 257 | try: |
| 258 | os.remove(os.path.join(path, key)) |
| 259 | except Exception as e: |
| 260 | print(e) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 261 | |
Radek Krejci | 5a1571a | 2018-02-16 13:45:53 +0100 | [diff] [blame] | 262 | # TODO: resolve dependencies ? |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 263 | |
| 264 | return(json.dumps({'success': True})) |