github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/docs/js_extension.md (about) 1 # Javascript extension 2 3 Gohan support a extension written by JavaScript thanks to Otto project. 4 In the gohan extension code, you need to register context using 5 gohan_register_handler function. 6 gohan_register_handler talkes event_type (string)_ and handler (function(context)). 7 8 ``` 9 gohan_register_handler("pre_show", function(context){ 10 context.resp = "pre_show event" 11 }); 12 13 gohan_register_handler("pre_update", function(context){ 14 context.resp = "pre_update event " 15 }); 16 ``` 17 18 context has following items 19 20 context.schema : schema information 21 context.path : url path 22 context.params : query params 23 context.role : user role 24 context.auth : auth_context information 25 context.http_request : Go HTTP request object 26 context.http_response : Go HTTP response writer object 27 28 29 ## Build in exception types 30 31 In an effort to simplify writing extensions for validation Gohan supports 32 throwing some exceptions and handles them internally. 33 Gohan provides the following exception types. 34 35 - BaseException(msg) 36 37 The base type of exception, should never be raised as itself, only extended. 38 39 - CustomException(msg, code) 40 41 A BaseException with an additional code property. When thrown will result in 42 an http response with the provided code and message being written. 43 44 One can extend the CustomException. An example follows. 45 46 ``` 47 function ValidationException(msg) { 48 CustomException.call(this, msg, 400); 49 this.name = "ValidationException"; 50 } 51 ValidationException.prototype = Object.create(CustomException.prototype); 52 ``` 53 54 ### Build in javascript functions 55 56 Gohan extension supports some build-in functions. 57 58 - ``gohan_log_critical(message)`` 59 - ``gohan_log_error(message)`` 60 - ``gohan_log_warning(message)`` 61 - ``gohan_log_notice(message)`` 62 - ``gohan_log_info(message)`` 63 - ``gohan_log_debug(message)`` 64 65 log ``message`` in Gohan log. 66 67 ``gohan_log_<lowercase level>(message)`` is equivalent to 68 ``gohan_log(MODULE, LOG_LEVEL.<uppercase level>, message)``. 69 70 - ``gohan_log(module, log_level, message)`` 71 72 log ``message`` in Gohan log (general version). 73 74 - ``module`` 75 The module to be used for logging. You can use ``LOG_MODULE`` for 76 the current log module. See ``gohan_log_module_push``. 77 78 - ``log_level`` 79 One of ``LOG_LEVEL.CRITICAL``, ``LOG_LEVEL.ERROR``, 80 ``LOG_LEVEL.WARNING``, ``LOG_LEVEL.NOTICE``, ``LOG_LEVEL.INFO``, 81 ``LOG_LEVEL.DEBUG``. 82 83 Example usage:: 84 85 gohan_log(LOG_MODULE, DEBUG, "It works"); 86 87 This will print something like the following:: 88 89 17:52:40.921 gohan.extension.network.post_list_in_transaction DEBUG It works 90 91 - ``gohan_log_module_push(new_module) : <old log module>`` 92 Appends ``new_module`` to the current ``LOG_MODULE``. 93 94 - ``gohan_log_module_restore(old_module)`` 95 Restores ``LOG_MODULE`` to ``old_module``. Example usage:: 96 97 old_module = gohan_log_module_push("low_level"); 98 try { 99 ... 100 } finally { 101 gohan_restore_log_module(old_module) 102 } 103 104 - gohan_http(method, url, headers, data, opaque, timeout) 105 106 fetch data from url 107 method : GET | POST | PUT | DELETE 108 url : destination url 109 headers : additional headers (eg. AUTH_TOKEN) 110 data : post or put data 111 opaque (optional) : boolean - whether to parse URL or to treat it as raw 112 timeout (optional) : int - timeout in milliseconds. Default is no timeout. 113 114 - gohan_raw_http(method, url, headers, data) 115 116 Fetch data from url. It uses GO RoundTripper instead of Client (as in gohan_http), 117 which allows for more control. 118 method : GET | POST | PUT | DELETE 119 url : destination url 120 headers : additional headers (eg. AUTH_TOKEN) 121 data : request data (string) 122 123 - gohan_config(key, default_value) 124 125 get value from Gohan config. If key is not found, default value is returned. 126 Key should be specified as 127 `JSON Pointer <http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07>`_. 128 129 - gohan_db_list(transaction, schema_id, filter_object[, order_key[, limit[, offset]]]) 130 131 retrive all data from database 132 133 - gohan_db_fetch(transaction, schema_id, id, tenant_id) 134 135 get one data from db 136 137 - gohan_db_query(transaction, schema_id, query_string, arguments) 138 139 Retrieve data with a raw query 140 141 - transaction: The transaction to use. When null will use a new one. 142 - schema_id: The ID of the schema of which the query result populates instances 143 - query_string: Raw query string such as a SQL SELECT query 144 145 - You can put a "?" as the placeholder of variables in your query string 146 147 - arguments: An array of actual values that replace place holders in query_string 148 149 - gohan_db_create(transaction, schema_id, object) 150 151 create data in db 152 153 - gohan_db_update(transaction, schema_id, object) 154 155 update data in db 156 157 - gohan_db_state_update(transaction, schema_id, object) 158 159 update data in db without informing etcd 160 161 - gohan_db_delete(transaction, schema_id, object) 162 163 delete data in db 164 165 - gohan_db_transaction() 166 167 start a new DB transaction. You are responsible for managing tranansactions created by this function. Call .Close() or .Commit() after using the return value. 168 169 170 - gohan_model_list(context, schema_id, filter) 171 172 Retrieve data through Gohan. 173 174 - context: You need to have transaction in this dictionary which you can get from given context 175 - schema_id: The id of the schema of the objects we want to retrieve. 176 - filter: How to filter retrieved objects. Should be a dictionary with each key being either: 177 178 - A property of the schema we are retrieving. Then the value has to either be a string or an array of strings. 179 The response is then filtered by removing all entries that do not have the value for the given key in the provided array. 180 - Any of the strings 'sort_key', 'sort_order', 'limit', 'offset'. These are interpreted with their values as query parameters. 181 182 - gohan_model_fetch(context, schema_id, resource_ids) 183 184 Retrieve a specific resource through Gohan. 185 186 - context: You need to have transaction in this dictionary which you can get from given context 187 - schema_id: The id of the schema of the object we want to retrieve. 188 - resource_id: The id of the object we want to retrieve. 189 - tenant_ids: allowed tenant id 190 191 - gohan_model_create(context, schema_id, data) 192 193 Create an object through Gohan. 194 195 - context: You need to have transaction in this dictionary which you can get from given context 196 - schema_id: The id of the schema of the object we want to create. 197 - data: The data needed to create the object, in the form of a dictionary. 198 199 - gohan_model_update(context, schema_id, resource_id, data, tenant_ids) 200 201 Update an object through Gohan. 202 203 - context: You need to have transaction in this dictionary which you can get from given context 204 - schema_id: The id of the schema of the object we want to update. 205 - resource_id: The id of the object we want to update. 206 - data: The data needed to update the object, in the form of a dictionary. 207 - tenant_ids: allowed tenant id 208 209 - gohan_model_delete(context, schema_id, resource_id) 210 211 Delete an object through Gohan. 212 213 - context: You need to have transaction in this dictionary which you can get from given context 214 - schema_id: The id of the schema of the object we want to delete. 215 - resource_id: The id of the object we want to delete. 216 217 - gohan_schemas() 218 219 returns all registered schemas 220 221 - gohan_schema_url(schema) 222 223 returns the url for the schema 224 225 - gohan_policies() 226 227 returns all policies 228 229 - gohan_uuid() 230 231 generate uuid v4 232 233 - gohan_sleep(time) 234 235 sleep time (ms) 236 237 - gohan_execute(comand_name, args) 238 239 execute shell command 240 241 - gohan_template(template_string, variables) 242 243 apply go style template 244 245 - gohan_netconf_open(hostname, username) 246 247 open netconf session. 248 (Note: you need set up ssh key configuraion 249 on both of gohan and target node.) 250 In gohan, you need to setup ssh/key_file 251 configuraion. 252 253 - gohan_netconf_exec(session, command) 254 255 execute netconf command 256 257 - gohan_netconf_close(session) 258 259 close netconf session 260 261 - gohan_ssh_open(hostname, username) 262 263 open ssh session. 264 (Note: you need set up ssh key configuraion 265 on both of gohan and target node.) 266 In gohan, you need to setup ssh/key_file 267 configuraion. 268 269 - gohan_ssh_exec(session, command) 270 271 execute command on ssh session 272 273 - gohan_ssh_close(session) 274 275 close ssh session 276 277 - require(module) 278 279 Dynamically load modules loaded in source code or 280 installed via npm in node_modules at working directory 281 282 - gohan_file_list(dir) 283 284 List files in dir 285 286 - gohan_file_read(path) 287 288 Read file from path 289 290 - gohan_file_dir(path) 291 292 Check if dir 293 294 - gohan_sync_fetch(path) 295 296 Fetch a given path from Sync 297 298 - gohan_sync_watch(path, timeout, revision) 299 300 Watch a given path in Sync starting from a given revision. This call is blocking no longer 301 than a given timeout in milliseconds. If no event occurs in the given timeout, the function 302 returns an empty object. 303 304 # Testing javascript extensions 305 306 You can test extensions using a testing tool bundled with Gohan with the command 307 ``test_extensions`` (or ``test_ex`` for short). Build and install Gohan, then 308 run ``gohan test_extensions <paths to files/directories to test>``. The 309 framework will walk through files and recursively through directories, running 310 tests in files named ``test_*.js``. 311 312 By default, the framework doesn't show logs and results for passing tests, so 313 you won't see any output if all the tests pass. If you pass a 314 ``-v``/``--verbose`` flag, it will show these messages, and an additional ``All 315 tests have passed.`` message if all the tests pass. 316 317 ## Test file contents 318 319 Each test file must specify schema and path for preloading extensions: 320 321 * var SCHEMA - path to the schema that stores extensions to be tested 322 * var PATH - path for preloading extensions 323 324 Additionally each file can specify: 325 326 * one setUp() function that will be called before each test 327 * one tearDown() function that will be called after each test 328 * multiple test_<name>() functions that will be called by the framework 329 * multiple helper functions and variables, with names not starting with prefix 330 ``test_`` 331 332 ## Framework API 333 334 Test framework provides all built in function mentioned in subsection 335 describing `gohan built in functions`_. 336 337 In unit tests, you can use mocks of ``gohan_http``, ``gohan_config`` and 338 ``gohan_db_transaction``. You can pass values that will be returned for given 339 arguments during subsequent calls by calling 340 ``gohan_*.Expect(argument, ...).Return(value)``. One call to 341 ``gohan_*.Expect(arguments, ...).Return(value)`` provides one response of 342 ``gohan_*`` (FIFO queue). If no return value, or wrong arguments are provided 343 for a call then an unexpected call is assumed, which will result in test failures. 344 345 In addition to the abovementioned functions, the framework provides the 346 following API: 347 348 * ``Fail(format_string, ...)`` - stop execution of a single test case and 349 return an error 350 351 * ``GohanTrigger(event_type, context) : <new context>`` - triggers a specified 352 type of Gohan event 353 354 * ``event_type`` - one of the event types recognized by Gohan (see 355 event_ subsection) 356 357 * ``context`` - context passed to the event handler 358 359 * ``MockTransaction(is_new) : <mock transaction>`` - return a mock transaction that 360 can be used with built-in Gohan methods. Each test is run using a separate 361 database that is deleted after ``tearDown()``, so there is no need to 362 clean up the database between tests. Multiple calls to ``MockTransaction()`` 363 within a single ``setUp()``, test, ``tearDown()`` routine when no call to 364 ``CommitMockTransaction()`` has been made will yield the same transaction. 365 ``MockTransaction(true)`` returns forcibly new transaction. 366 367 * ``CommitMockTransaction()`` - commit and close the last nonclosed 368 transaction. After this call any calls to ``MockTransaction()`` return 369 a new transaction. 370 371 * ``MockPolicy() : <mock policy>`` - return a mock policy that 372 can be used with built-in Gohan methods. 373 374 * ``MockAuthorization() : <mock authorization>`` - return a mock authorization that 375 can be used with built-in Gohan methods. 376 377 ## Example 378 A sample test may look like this: 379 380 ``` 381 // Schema file containing extensions to be tested 382 var SCHEMA = "../test_schema.yaml"; 383 384 /** 385 * Sample contents of test_schema.yaml: 386 * 387 * extensions: 388 * - id: network 389 * path: /v2.0/network.* 390 * url: file://./etc/examples/neutron/network.js 391 * - id: exceptions 392 * path: "" 393 * url: file://./etc/examples/neutron/exceptions.js 394 * - id: urls 395 * path: /gohan/v0.1/schema.* 396 * url: file://./etc/examples/url.js 397 * schemas: 398 * - description: Network 399 * id: network 400 * parent: "" 401 * plural: networks 402 * schema: 403 * properties: 404 * id: 405 * format: uuid 406 * permission: 407 * - create 408 * title: ID 409 * type: string 410 * unique: true 411 * tenant_id: 412 * format: uuid 413 * permission: 414 * - create 415 * title: Tenant id 416 * type: string 417 * unique: false 418 * propertiesOrder: 419 * - name 420 * - id 421 * - tenant_id 422 * singular: network 423 * title: Network 424 */ 425 426 // With the following PATH, "network" and "exceptions" extensions will be loaded 427 var PATH = "/v2.0/networks"; 428 429 /** 430 * Sample contents of network.js: 431 * 432 * // filter removes the network with the unwanted id 433 * gohan_register_handler("post_list", function filter(context) { 434 * // This call will be mocked, see testNetworkListFilter below 435 * response = gohan_http("GET", "http://whatisunwanted.com", {}, null); 436 * 437 * for (var i = 0; i < context.response.networks.length; i++) { 438 * if (context.response.networks[i].id == response.unwanted) { 439 * context.response.networks.splice(i, 1); 440 * break; 441 * } 442 * } 443 * }); 444 */ 445 446 var context; 447 var network; 448 449 function setUp() { 450 var network_to_create = { 451 'id': 'new', 452 'tenant_id': 'azerty' 453 }; 454 network = gohan_db_create(MockTransaction(), "network", network_to_create); 455 context = { 456 'schema': { /* ... */ }, 457 'http_request': { /* ... */ }, 458 'http_response': { /* ... */ }, 459 'path': '/gohan/v0.1/schema', 460 'response': { 461 'networks': [ 462 network, 463 { 464 'id': 'foo', 465 'tenant_id': 'xyz' 466 } 467 ] 468 } 469 } 470 } 471 472 function tearDown() { 473 gohan_db_delete(MockTransaction(), "network", "new"); 474 } 475 476 function testNetworkListFilter() { 477 // First call to gohan_http will return {'unwanted': 'foo'} 478 gohan_http.Expect("GET", "http://whatisunwanted.com", {}, null).Return({'unwanted': 'foo'}); 479 // Second call to gohan_http will return empty response 480 gohan_http.Expect("GET", "http://whatisunwanted.com", {}, null).Return({}); 481 // Subsequent calls to gohan_http will fail since they are not expected 482 var new_context = GohanTrigger('post_list', context); 483 484 if (new_context.response.networks.length != 1) { 485 Fail('Expected 1 network but %d found.', new_context.response.networks.length); 486 } 487 488 if (new_context.response.networks[0].id != network.id) { 489 Fail('Expected network with id "%s" but "%s" found.', network.id, new_context.response.networks[0].id); 490 } 491 } 492 493 function testSomethingElse() { 494 /* ... */ 495 } 496 ```