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 |
| 11 | |
| 12 | from liberouterapi import auth |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 13 | from flask import request |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 14 | import yang |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 15 | |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 16 | from .inventory import INVENTORY, inventory_check |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 17 | from .error import NetopeerException |
| 18 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 19 | __SCHEMAS_EMPTY = '{"schemas":{"timestamp":0,"schema":[]}}' |
| 20 | |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 21 | |
| 22 | def __schema_parse(path, format = yang.LYS_IN_UNKNOWN): |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 23 | try: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 24 | ctx = yang.Context(os.path.dirname(path)) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 25 | except Exception as e: |
| 26 | raise NetopeerException(str(e)) |
| 27 | |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 28 | try: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 29 | 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] | 30 | except Exception as e: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 31 | if format != yang.LYS_IN_UNKOWN: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 32 | 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 Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 39 | |
| 40 | |
| 41 | def __schemas_init(): |
| 42 | schemas = json.loads(__SCHEMAS_EMPTY) |
| 43 | try: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 44 | ctx = yang.Context() |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 45 | 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 Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 55 | def __schemas_inv_load(path): |
| 56 | schemainv_path = os.path.join(path, 'schemas.json') |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 57 | 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 Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 67 | |
| 68 | return schemas |
| 69 | |
| 70 | def __schemas_inv_save(path, schemas): |
| 71 | schemainv_path = os.path.join(path, 'schemas.json') |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 72 | |
| 73 | # update the timestamp |
| 74 | schemas['schemas']['timestamp'] = time.time() |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 75 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 76 | #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 Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 82 | |
| 83 | return schemas |
| 84 | |
| 85 | def __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 Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 95 | format = yang.LYS_IN_YANG |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 96 | elif file[-4:] == '.yin': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 97 | format = yang.LYS_IN_YIN |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 98 | else: |
| 99 | continue |
| 100 | |
| 101 | schemapath = os.path.join(path, file); |
| 102 | if os.path.getmtime(schemapath) > timestamp: |
| 103 | # update the list |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 104 | 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 Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 115 | |
| 116 | # return the up-to-date list |
| 117 | return schemas['schemas']['schema'] |
| 118 | |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 119 | @auth.required() |
| 120 | def schemas_list(): |
| 121 | session = auth.lookup(request.headers.get('Authorization', None)) |
| 122 | user = session['user'] |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 123 | path = os.path.join(INVENTORY, user.username) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 124 | |
Radek Krejci | b2feaac | 2017-09-21 13:16:54 +0200 | [diff] [blame] | 125 | inventory_check(path) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 126 | schemas = __schemas_update(path) |
| 127 | |
| 128 | return(json.dumps(schemas)) |
| 129 | |
| 130 | @auth.required() |
| 131 | def 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 Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 140 | path = os.path.join(INVENTORY, user.username, file.filename) |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 141 | file.save(path) |
| 142 | |
| 143 | # parse file |
| 144 | try: |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 145 | if file.filename[-5:] == '.yang': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 146 | format = yang.LYS_IN_YANG |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 147 | elif file.filename[-4:] == '.yin': |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 148 | format = yang.LYS_IN_YIN |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 149 | else: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame^] | 150 | format = yang.LYS_IN_UNKNOWN |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 151 | module = __schema_parse(path, format) |
| 152 | # TODO: normalize file name to allow removing without remembering schema path |
Radek Krejci | d23f0df | 2017-08-31 16:34:49 +0200 | [diff] [blame] | 153 | except Exception: |
| 154 | os.remove(path) |
| 155 | return(json.dumps({'success': False})) |
| 156 | |
| 157 | return(json.dumps({'success': True})) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 158 | |
| 159 | @auth.required() |
| 160 | def schemas_rm(): |
| 161 | session = auth.lookup(request.headers.get('Authorization', None)) |
| 162 | user = session['user'] |
Radek Krejci | 2b9bbc2 | 2017-09-21 13:20:48 +0200 | [diff] [blame] | 163 | path = os.path.join(INVENTORY, user.username) |
Radek Krejci | 3cb753f | 2017-09-08 16:14:29 +0200 | [diff] [blame] | 164 | |
| 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})) |