From 2ca7d533b6fe01a10fcc3474812183e4c4a8431e Mon Sep 17 00:00:00 2001 From: Peter Lee Date: Fri, 29 Jan 2016 15:21:56 -0800 Subject: added copy of github.com/opnfv/promise source into the source directory Change-Id: Ib212302a4132aa492f7b701a7ca02f54a7d0a6af (cherry picked from commit a46af646972b6ff263fb207d28a59e7ce7417b5c) --- source/.gitignore | 16 + source/.npmignore | 2 + source/LICENSE | 202 ++ source/README.md | 144 + source/config/custom-environment-variables.yaml | 23 + source/config/default.yaml | 11 + source/config/demo.json | 118 + source/config/functest.yaml | 9 + source/config/test-intercloud.yaml | 21 + source/forge.yaml | 42 + source/index.yaml | 4120 +++++++++++++++++++++++ source/openstack.yaml | 13 + source/package.json | 41 + source/promise.yaml | 290 ++ source/schema/access-control-models.yang | 92 + source/schema/nfv-infrastructure.yang | 322 ++ source/schema/nfv-mano.yang | 149 + source/schema/openstack-compute.yang | 72 + source/schema/openstack-identity.yang | 84 + source/schema/openstack.yang | 74 + source/schema/opnfv-promise.yang | 640 ++++ source/spec/openstack-intents.coffee | 6 + source/spec/promise-intents.coffee | 360 ++ source/spec/promise-module.coffee | 72 + source/test/mocha.opts | 2 + source/test/promise-intents.coffee | 437 +++ 26 files changed, 7362 insertions(+) create mode 100644 source/.gitignore create mode 100644 source/.npmignore create mode 100644 source/LICENSE create mode 100644 source/README.md create mode 100644 source/config/custom-environment-variables.yaml create mode 100644 source/config/default.yaml create mode 100644 source/config/demo.json create mode 100644 source/config/functest.yaml create mode 100644 source/config/test-intercloud.yaml create mode 100644 source/forge.yaml create mode 100644 source/index.yaml create mode 100644 source/openstack.yaml create mode 100644 source/package.json create mode 100644 source/promise.yaml create mode 100644 source/schema/access-control-models.yang create mode 100644 source/schema/nfv-infrastructure.yang create mode 100644 source/schema/nfv-mano.yang create mode 100644 source/schema/openstack-compute.yang create mode 100644 source/schema/openstack-identity.yang create mode 100644 source/schema/openstack.yang create mode 100644 source/schema/opnfv-promise.yang create mode 100644 source/spec/openstack-intents.coffee create mode 100644 source/spec/promise-intents.coffee create mode 100644 source/spec/promise-module.coffee create mode 100644 source/test/mocha.opts create mode 100644 source/test/promise-intents.coffee diff --git a/source/.gitignore b/source/.gitignore new file mode 100644 index 0000000..f49cc53 --- /dev/null +++ b/source/.gitignore @@ -0,0 +1,16 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +pids +logs +results +node_modules +npm-debug.log +*~ +*# + diff --git a/source/.npmignore b/source/.npmignore new file mode 100644 index 0000000..4f726b0 --- /dev/null +++ b/source/.npmignore @@ -0,0 +1,2 @@ +.git* + diff --git a/source/LICENSE b/source/LICENSE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/source/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000..681e2b9 --- /dev/null +++ b/source/README.md @@ -0,0 +1,144 @@ +# Resource Management for Virtual Infrastructure + +**Promise** is a resource reservation and management project to identify NFV related requirements and realize resource reservation for future usage by capacity management of resource pools regarding compute, network and storage. + +The following are the key features provided by this module: + +* Resource Capacity Management +* Resource Reservation +* Resource Allocation + +This module also contains a collection of [YANG data models](schema/) as defined under the direction of [OPNFV Promise](http://wiki.opnfv.org/promise) project. + +## Installation + +`opnfv-promise` is built with [YangForge](http://github.com/opnfv/yangforge) data modeling +framework. You will need to first install `yangforge` and use the +provided `yfc` command line utility to run this module. + +```bash +$ npm install -g yangforge +``` + +There are also alternative installer plugins for [Fuel](http://github.com/opnfv/fuel-plugin-promise) and [Juju](http://github.com/opnfv/juju-plugin-promise). + +## Usage +```bash +$ yfc run promise.yaml +``` + +The `yfc run` command will load the primary application +package from this repository along with any other dependency +files/assets referenced within the YAML manifest and instantiate the +opnfv-promise module and run REST/JSON interface by default listening +on port 5000. + +You can also checkout this GIT repository or simply download the files +into your local system and run the application. + +## Testing + +```bash +$ npm install +$ npm test +``` + +TBD + +## Primary YANG Data Models + +name | description | status +--- | --- | --- +[opnfv-promise](schema/opnfv-promise.yang) | provide resource reservation and capacity management | 95% complete +[nfv-infrastructure](schema/nfv-infrastructure.yang) | common NFV Infrastructure resource models | 80% complete +[nfv-mano](schema/nfv-mano.yang) | common NFV MANO resource models including VIM | 20% complete +[openstack](schema/openstack.yang) | openstack specific VIM extensions | 50% complete + +## Promise Information Models + +### ResourceReservation + +The data model describing the required parameters regarding a resource +reservation. The schema definition expressed in Yang can be found +[here](schema/opnfv-promise.yang). + +#### Key Elements + +Name | Type | Description +--- | --- | --- +start | ys:date-and-time | Timestamp of when the consumption of reserved resources can begin +end | ys:date-and-time | Timestamp of when the consumption of reserved resource must end +expiry | number | Duration expressed in seconds since `start` when resource not yet allocated shall be released back to the available zone +zone | nfvi:AvailabilityZone | Reference to a zone where the resources will be reserved +capacity | object | Quantity of resources to be reserved per resource types +attributes | list | References to resource attributes needed for reservation +resources | list (nfvi:ResourceElement) | Reference to a collection of existing resource elements required + +#### State Elements (read-only) + +State Elements are available as part of lookup response about the data model. + +Name | Type | Description +--- | --- | --- +provider | nfvi:ResourceProvider | Reference to a specific provider when reservation service supports multiple providers +remaining | object | Quantity of resources remaining for consumption based on consumed allocations +allocations | list (nfvi:ResourceAllocation) | Reference to a collection of consumed allocations referencing this reservation + +#### Notification Elements + +Name | Type | Description +--- | --- | --- +reservation-event | Event | Subscribers will be notified if the reservation encounters an error or other events + +#### Inherited Elements + +##### Extended from [nfvi:ResourceElement](schema/nfv-infrastructure.yang) + +Name | Type | Description +--- | --- | --- +id | yang:uuid | A GUID identifier for the data model (usually auto-generated, but can also be specified) +name | string | Name of the data model +enabled | boolean | Enable/Disable the data model +protected | boolean | Prevent model from being destroyed when protected +owner | nfvi:AccessIdentity | An owner for the data model +visibility | enumeration | Visibility level of the given data model +tags | list (string) | List of string tags for query/filter +members | list (nfvi:AccessIdentity) | List of additional AccessIdentities that can operate on the data model + +### Resource Allocation + +The data model describing the required parameters regarding a resource +allocation. The schema definition expressed in YANG can be found +[here](schema/opnfv-promise.yang). + +#### Key Elements + +Name | Type | Description +--- | --- | --- +reservation | nfvi:ResourceReservation | Reference to an existing reservation identifier +allocate-on-start | boolean | Specify whether the allocation can take effect automatically upon reservation 'start' +resources | list (nfvi:ResourceElement) | Reference to a collection of new resource elements to be allocated + +#### State Elements (read-only) + +Name | Type | Description +--- | --- | --- +priority | number | Read-only state information about the priority classification of the reservation + +#### Inherited Elements + +##### Extended from [nfvi:ResourceElement](schema/nfv-infrastructure.yang) + +Name | Type | Description +--- | --- | --- +id | yang:uuid | A GUID identifier for the data model (usually auto-generated, but can also be specified) +name | string | Name of the data model +enabled | boolean | Enable/Disable the data model +protected | boolean | Prevent model from being destroyed when protected +owner | nfvi:AccessIdentity | An owner for the data model +visibility | enumeration | Visibility level of the given data model +tags | list (string) | List of string tags for query/filter +members | list (nfvi:AccessIdentity) | List of additional AccessIdentities that can operate on the data model + +## License + [Apache-2.0](LICENSE) diff --git a/source/config/custom-environment-variables.yaml b/source/config/custom-environment-variables.yaml new file mode 100644 index 0000000..30a72d2 --- /dev/null +++ b/source/config/custom-environment-variables.yaml @@ -0,0 +1,23 @@ +# OPNFV FuncTest config (refer to schema/opnfv-functest.yang) +opnfv-functest: + environment: + installer: + type: INSTALLER_TYPE + address: INSTALLER_IP + lab: NODE_NAME + +# OpenStack config (native) +openstack: + auth: + endpoint: OS_AUTH_URL + strategy: OS_AUTH_STRATEGY + tenant: + id: OS_TENANT_ID + name: OS_TENANT_NAME + username: OS_USERNAME + password: OS_PASSWORD + test: + image: OS_TEST_IMAGE + flavor: OS_TEST_FLAVOR + network: OS_TEST_NETWORK + diff --git a/source/config/default.yaml b/source/config/default.yaml new file mode 100644 index 0000000..52bb61a --- /dev/null +++ b/source/config/default.yaml @@ -0,0 +1,11 @@ +# default configuration for 'npm test' + +opnfv-promise: + promise: + policy: + reservation: + max-future-start-range: + max-future-end-range: + max-duration: + expiry: 600 + diff --git a/source/config/demo.json b/source/config/demo.json new file mode 100644 index 0000000..dffb3af --- /dev/null +++ b/source/config/demo.json @@ -0,0 +1,118 @@ +{ + "opnfv-promise": { + "promise": { + "providers": [ + { + "name": "example-demo-provider", + "token": "dummy-token" + } + ], + "pools": [ + { + "ResourcePool": { + "id": "4085f0da-8030-4252-a0ff-c6f93870eb5f", + "name": "OPNFV OpenStack - West", + "source": "example-demo-provider", + "capacity": { + "cores": 100, + "ram": 262144, + "instances": 500, + "networks": 100, + "ports": 100, + "routers": 30, + "subnets": 1000, + "addresses": 500, + "gigabytes": 10000, + "snapshots": 100, + "volumes": 100 + } + } + } + ], + "reservations": [ + { + "capacity": { + "cores": 10, + "ram": 4096, + "instances": 10, + "networks": 4, + "ports": 10, + "routers": 1, + "subnets": 1, + "addresses": 10, + "gigabytes": 0, + "snapshots": 0, + "volumes": 0 + }, + "start": "2015-11-07T10:17:12.747Z", + "end": "2016-02-13T10:17:18.226Z", + "pools": [ + "4085f0da-8030-4252-a0ff-c6f93870eb5f" + ] + }, + { + "capacity": { + "cores": 20, + "ram": 10000, + "instances": 5, + "networks": 2, + "ports": 10, + "routers": 1, + "subnets": 1, + "addresses": 5, + "gigabytes": 0, + "snapshots": 0, + "volumes": 0 + }, + "start": "2015-11-09T10:17:12.747Z", + "end": "2016-02-11T10:17:18.226Z", + "pools": [ + "4085f0da-8030-4252-a0ff-c6f93870eb5f" + ] + }, + { + "id": "c7287f30-2c65-4a88-a047-48724b8ff747", + "capacity": { + "cores": 10, + "ram": 4096, + "instances": 10, + "networks": 5, + "ports": 10, + "routers": 1, + "subnets": 5, + "addresses": 20, + "gigabytes": 0, + "snapshots": 0, + "volumes": 0 + }, + "start": "2015-11-10T10:17:12.747Z", + "end": "2015-12-13T10:17:18.226Z", + "pools": [ + "4085f0da-8030-4252-a0ff-c6f93870eb5f" + ] + }, + { + "id": "0f2e31f7-9760-416d-8d53-1ee68aa4b11f", + "capacity": { + "cores": 10, + "ram": 4096, + "instances": 5, + "networks": 2, + "ports": 10, + "routers": 1, + "subnets": 1, + "addresses": 5, + "gigabytes": 0, + "snapshots": 0, + "volumes": 0 + }, + "start": "2015-11-09T10:17:12.747Z", + "end": "2015-12-03T10:17:18.226Z", + "pools": [ + "4085f0da-8030-4252-a0ff-c6f93870eb5f" + ] + } + ] + } + } +} diff --git a/source/config/functest.yaml b/source/config/functest.yaml new file mode 100644 index 0000000..8cc84d3 --- /dev/null +++ b/source/config/functest.yaml @@ -0,0 +1,9 @@ +# NODE_ENV=functest + +opnfv-functest: + environment: + images: + - + name: cirros + path: /home/opnfv/functest/data/cirros-0.3.4-x86_64-disk.img + diff --git a/source/config/test-intercloud.yaml b/source/config/test-intercloud.yaml new file mode 100644 index 0000000..f5e04ed --- /dev/null +++ b/source/config/test-intercloud.yaml @@ -0,0 +1,21 @@ +# the following config is used when ENV is as follows: +# NODE_ENV=test +# NODE_APP_INSTANCE=intercloud +openstack: + auth: + strategy: keystone + endpoint: http://vhub4.intercloud.net:5000/v2.0 + tenant: + id: 62a2d90992114994977fd6707bac5758 + username: peter + password: # set OS_PASSWORD=xxxx environmental variable + test: + image: ee0fb445-0fc2-4fda-a2dc-175bf3cc3cb1 + flavor: 2312fd98-369e-4361-b967-606373891c11 + +opnfv-promise: + promise: + policy: + reservation: + max-future-start-range: 7 + max-duration: 24 diff --git a/source/forge.yaml b/source/forge.yaml new file mode 100644 index 0000000..24317d1 --- /dev/null +++ b/source/forge.yaml @@ -0,0 +1,42 @@ +# YF 0.12.x forge manifest + +compilers: + yang: yangforge/register + coffee: coffee-script/register + +components: + nfvi: + - yangforge:common + - schema/access-control-models.yang + - schema/nfv-infrastructure.yang + - schema/nfv-mano.yang + + # primary promise service + promise: + - nfvi + - schema/opnfv-promise.yang + - spec/promise.yaml + + # base openstack composition + openstack: + - nfvi + - schema/openstack.yang + - schema/openstack-identity.yang + - schema/openstack-image.yang + - schema/openstack-compute.yang + - schema/openstack-storage.yang + - schema/openstack-network.yang + - spec/openstack.yaml + + # openstack with promise augmentation + os-promise: + - promise + - openstack + - schema/openstack-promise.yang + - spec/openstack-promise.yaml + + # test component for using with 'npm test' + test: + - os-promise + - schema/opnfv-functest.yang + - config/demo.json diff --git a/source/index.yaml b/source/index.yaml new file mode 100644 index 0000000..071d685 --- /dev/null +++ b/source/index.yaml @@ -0,0 +1,4120 @@ +synth: source +name: opnfv-promise +version: ! '' +description: Resource Management for Virtualized Infrastructure +license: Apache-2.0 +schema: + module: + opnfv-promise: + namespace: 'urn:opnfv:promise' + prefix: promise + import: + complex-types: + prefix: ct + ietf-yang-types: + prefix: yang + ietf-inet-types: + prefix: inet + access-control-models: + prefix: acm + nfv-infrastructure: + prefix: nfvi + description: OPNFV Promise Resource Reservation/Allocation controller module + revision: + '2015-10-05': + description: Complete coverage of reservation related intents + '2015-08-06': + description: Updated to incorporate YangForge framework + '2015-04-16': + description: Initial revision. + feature: + reservation-service: + description: 'When enabled, provides resource reservation service' + multi-provider: + description: 'When enabled, provides resource management across multiple providers' + grouping: + resource-utilization: + container: + capacity: + container: + total: + description: Conceptual container that should be extended + reserved: + description: Conceptual container that should be extended + config: false + usage: + description: Conceptual container that should be extended + config: false + available: + description: Conceptual container that should be extended + config: false + temporal-resource-collection: + description: Information model capturing resource-collection with start/end time window + leaf: + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + resource-usage-request: + description: |- + Information model capturing available parameters to make a resource + usage request. + reference: 'OPNFV-PROMISE, Section 3.4.1' + uses: {} + leaf: + zone: + description: Optional identifier to an Availability Zone + type: + instance-identifier: + 'ct:instance-type': 'nfvi:AvailabilityZone' + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + description: |- + Reference to a list of 'pre-existing' resource elements that are + required for fulfillment of the resource-usage-request. + + It can contain any instance derived from ResourceElement, + such as ServerInstances or even other + ResourceReservations. If the resource-usage-request is + accepted, the ResourceElement(s) listed here will be placed + into 'protected' mode as to prevent accidental removal. + + If any of these resource elements become 'unavailable' due to + environmental or administrative activity, a notification will + be issued informing of the issue. + query-start-end-window: + container: + window: + description: Matches entries that are within the specified start/end time window + leaf: + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + scope: + type: + enumeration: + enum: + exclusive: + description: Matches entries that start AND end within the window + value: 0 + inclusive: + description: Matches entries that start OR end within the window + value: 1 + default: inclusive + query-resource-collection: + uses: {} + leaf-list: + without: + description: Excludes specified collection identifiers from the result + type: + instance-identifier: + 'ct:instance-type': ResourceCollection + leaf: + show-utilization: + type: boolean + default: 'true' + container: + elements: + leaf-list: + some: + description: Query for ResourceCollection(s) that contain some or more of these element(s) + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + every: + description: Query for ResourceCollection(s) that contain all of these element(s) + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + window: + description: Matches entries that are within the specified start/end time window + leaf: + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + scope: + type: + enumeration: + enum: + exclusive: + description: Matches entries that start AND end within the window + value: 0 + inclusive: + description: Matches entries that start OR end within the window + value: 1 + default: inclusive + common-intent-output: + leaf: + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + utilization-output: + list: + utilization: + key: timestamp + leaf: + timestamp: + type: 'yang:date-and-time' + count: + type: int16 + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + 'ct:complex-type': + ResourceCollection: + 'ct:extends': 'nfvi:ResourceContainer' + 'ct:abstract': 'true' + description: |- + Describes an abstract ResourceCollection data model, which represents + a grouping of capacity and elements available during a given + window in time which must be extended by other resource + collection related models + leaf: + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + active: + config: false + description: |- + Provides current state of this record whether it is enabled and within + specified start/end time + type: boolean + ResourcePool: + 'ct:extends': ResourceCollection + description: |- + Describes an instance of an active ResourcePool record, which + represents total available capacity and elements from a given + source. + leaf: + source: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceContainer' + require-instance: true + mandatory: true + refine: + elements: + must: + 'boolean(/source/elements/*[@id=id])': + error-message: One or more of the ResourceElement(s) does not exist in the provider to be reserved + ResourceReservation: + 'ct:extends': ResourceCollection + description: |- + Describes an instance of an accepted resource reservation request, + created usually as a result of 'create-reservation' request. + + A ResourceReservation is a derived instance of a generic + ResourceCollection which has additional parameters to map the + pool(s) that were referenced to accept this reservation as well + as to track allocations made referencing this reservation. + + Contains the capacities of various resource attributes being + reserved along with any resource elements that are needed to be + available at the time of allocation(s). + reference: 'OPNFV-PROMISE, Section 3.4.1' + leaf: + created-on: + type: 'yang:date-and-time' + config: false + modified-on: + type: 'yang:date-and-time' + config: false + leaf-list: + pools: + config: false + description: |- + Provides list of one or more pools that were referenced for providing + the requested resources for this reservation. This is an + important parameter for informing how/where allocation + requests can be issued using this reservation since it is + likely that the total reserved resource capacity/elements are + made availble from multiple sources. + type: + instance-identifier: + 'ct:instance-type': ResourcePool + require-instance: true + allocations: + config: false + description: |- + Reference to a collection of consumed allocations referencing + this reservation. + type: + instance-identifier: + 'ct:instance-type': ResourceAllocation + require-instance: true + container: + remaining: + config: false + description: |- + Provides visibility into total remaining capacity for this + reservation based on allocations that took effect utilizing + this reservation ID as a reference. + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + ResourceAllocation: + 'ct:extends': ResourceCollection + description: |- + A ResourceAllocation record denotes consumption of resources from a + referenced ResourcePool. + + It does not reflect an accepted request but is created to + represent the actual state about the ResourcePool. It is + created once the allocation(s) have successfully taken effect + on the 'source' of the ResourcePool. + + The 'priority' state indicates the classification for dealing + with resource starvation scenarios. Lower priority allocations + will be forcefully terminated to allow for higher priority + allocations to be fulfilled. + + Allocations without reference to an existing reservation will + receive the lowest priority. + reference: 'OPNFV-PROMISE, Section 3.4.3' + leaf: + reservation: + description: Reference to an existing reservation identifier (optional) + type: + instance-identifier: + 'ct:instance-type': ResourceReservation + require-instance: true + pool: + description: Reference to an existing resource pool from which allocation is drawn + type: + instance-identifier: + 'ct:instance-type': ResourcePool + require-instance: true + priority: + config: false + description: Reflects current priority level of the allocation according to classification rules + type: + enumeration: + enum: + high: + value: 1 + normal: + value: 2 + low: + value: 3 + default: normal + container: + instance-ref: + config: false + description: Reference to actual instance identifier of the provider/server for this allocation + leaf: + provider: + type: + instance-identifier: + 'ct:instance-type': ResourceProvider + server: + type: 'yang:uuid' + ResourceFlavor: + description: currently NOT an extension of ResourceElement. + key: id + leaf: + id: + type: string + name: + type: string + disk: + type: uint32 + units: GB + default: '0' + ram: + type: uint32 + units: MB + default: '0' + vcpus: + type: uint16 + default: '0' + ResourceProvider: + 'ct:extends': 'nfvi:ResourceContainer' + leaf: + token: + type: string + mandatory: true + container: + services: + config: false + container: + compute: + leaf: + endpoint: + type: 'inet:uri' + 'ct:instance-list': + flavors: + 'ct:instance-type': ResourceFlavor + leaf-list: + pools: + config: false + description: Provides list of one or more pools that are referencing this provider. + type: + instance-identifier: + 'ct:instance-type': ResourcePool + require-instance: true + container: + promise: + uses: {} + 'ct:instance-list': + providers: + if-feature: multi-provider + description: Aggregate collection of all registered ResourceProvider instances for Promise resource management service + 'ct:instance-type': ResourceProvider + status: unavailable + pools: + if-feature: reservation-service + description: Aggregate collection of all ResourcePool instances + 'ct:instance-type': ResourcePool + status: unavailable + reservations: + if-feature: reservation-service + description: Aggregate collection of all ResourceReservation instances + 'ct:instance-type': ResourceReservation + status: unavailable + allocations: + description: Aggregate collection of all ResourceAllocation instances + 'ct:instance-type': ResourceAllocation + container: + policy: + container: + reservation: + leaf: + max-future-start-range: + description: "Enforce reservation request 'start' time is within allowed range from now" + type: + uint16: + range: 0..365 + units: days + max-future-end-range: + description: "Enforce reservation request 'end' time is within allowed range from now" + type: + uint16: + range: 0..365 + units: days + max-duration: + description: Enforce reservation duration (end-start) does not exceed specified threshold + type: uint16 + units: hours + default: '8760' + expiry: + description: |- + Duration in minutes from start when unallocated reserved resources + will be released back into the pool + type: uint32 + units: minutes + capacity: + container: + total: + description: Conceptual container that should be extended + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + reserved: + description: Conceptual container that should be extended + config: false + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + usage: + description: Conceptual container that should be extended + config: false + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + available: + description: Conceptual container that should be extended + config: false + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + rpc: + create-reservation: + if-feature: reservation-service + description: Make a request to the reservation system to reserve resources + input: + uses: {} + leaf: + zone: + description: Optional identifier to an Availability Zone + type: + instance-identifier: + 'ct:instance-type': 'nfvi:AvailabilityZone' + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + description: |- + Reference to a list of 'pre-existing' resource elements that are + required for fulfillment of the resource-usage-request. + + It can contain any instance derived from ResourceElement, + such as ServerInstances or even other + ResourceReservations. If the resource-usage-request is + accepted, the ResourceElement(s) listed here will be placed + into 'protected' mode as to prevent accidental removal. + + If any of these resource elements become 'unavailable' due to + environmental or administrative activity, a notification will + be issued informing of the issue. + output: + leaf: + reservation-id: + type: + instance-identifier: + 'ct:instance-type': ResourceReservation + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + status: unavailable + update-reservation: + description: Update reservation details for an existing reservation + input: + leaf: + reservation-id: + type: + instance-identifier: + 'ct:instance-type': ResourceReservation + require-instance: true + mandatory: true + zone: + description: Optional identifier to an Availability Zone + type: + instance-identifier: + 'ct:instance-type': 'nfvi:AvailabilityZone' + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + uses: {} + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + description: |- + Reference to a list of 'pre-existing' resource elements that are + required for fulfillment of the resource-usage-request. + + It can contain any instance derived from ResourceElement, + such as ServerInstances or even other + ResourceReservations. If the resource-usage-request is + accepted, the ResourceElement(s) listed here will be placed + into 'protected' mode as to prevent accidental removal. + + If any of these resource elements become 'unavailable' due to + environmental or administrative activity, a notification will + be issued informing of the issue. + output: + leaf: + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + cancel-reservation: + description: Cancel the reservation and be a good steward + input: + leaf: + reservation-id: + type: + instance-identifier: + 'ct:instance-type': ResourceReservation + mandatory: true + output: + leaf: + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + query-reservation: + if-feature: reservation-service + description: Query the reservation system to return matching reservation(s) + input: + leaf: + zone: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:AvailabilityZone' + show-utilization: + type: boolean + default: 'true' + uses: {} + leaf-list: + without: + description: Excludes specified collection identifiers from the result + type: + instance-identifier: + 'ct:instance-type': ResourceCollection + container: + elements: + leaf-list: + some: + description: Query for ResourceCollection(s) that contain some or more of these element(s) + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + every: + description: Query for ResourceCollection(s) that contain all of these element(s) + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + window: + description: Matches entries that are within the specified start/end time window + leaf: + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + scope: + type: + enumeration: + enum: + exclusive: + description: Matches entries that start AND end within the window + value: 0 + inclusive: + description: Matches entries that start OR end within the window + value: 1 + default: inclusive + output: + leaf-list: + reservations: + type: + instance-identifier: + 'ct:instance-type': ResourceReservation + list: + utilization: + key: timestamp + leaf: + timestamp: + type: 'yang:date-and-time' + count: + type: int16 + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + status: unavailable + increase-capacity: + description: Increase total capacity for the reservation system between a window in time + input: + leaf: + source: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceContainer' + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + output: + leaf: + pool-id: + type: + instance-identifier: + 'ct:instance-type': ResourcePool + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + decrease-capacity: + description: Decrease total capacity for the reservation system between a window in time + input: + leaf: + source: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceContainer' + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + output: + leaf: + pool-id: + type: + instance-identifier: + 'ct:instance-type': ResourcePool + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + query-capacity: + description: Check available capacity information about a specified resource collection + input: + leaf: + capacity: + type: + enumeration: + enum: + total: + value: 0 + reserved: + value: 1 + usage: + value: 2 + available: + value: 3 + default: available + zone: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:AvailabilityZone' + show-utilization: + type: boolean + default: 'true' + uses: {} + leaf-list: + without: + description: Excludes specified collection identifiers from the result + type: + instance-identifier: + 'ct:instance-type': ResourceCollection + container: + elements: + leaf-list: + some: + description: Query for ResourceCollection(s) that contain some or more of these element(s) + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + every: + description: Query for ResourceCollection(s) that contain all of these element(s) + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + window: + description: Matches entries that are within the specified start/end time window + leaf: + start: + type: 'yang:date-and-time' + end: + type: 'yang:date-and-time' + scope: + type: + enumeration: + enum: + exclusive: + description: Matches entries that start AND end within the window + value: 0 + inclusive: + description: Matches entries that start OR end within the window + value: 1 + default: inclusive + output: + leaf-list: + collections: + type: + instance-identifier: + 'ct:instance-type': ResourceCollection + list: + utilization: + key: timestamp + leaf: + timestamp: + type: 'yang:date-and-time' + count: + type: int16 + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + create-instance: + description: Create an instance of specified resource(s) utilizing capacity from the pool + input: + leaf: + provider-id: + if-feature: multi-provider + type: + instance-identifier: + 'ct:instance-type': ResourceProvider + require-instance: true + status: unavailable + name: + type: string + mandatory: true + image: + type: + union: + type: + 'yang:uuid': null + 'inet:uri': null + mandatory: true + flavor: + type: + union: + type: + 'yang:uuid': null + 'inet:uri': null + mandatory: true + reservation-id: + type: + instance-identifier: + 'ct:instance-type': ResourceReservation + require-instance: true + output: + leaf: + instance-id: + type: + instance-identifier: + 'ct:instance-type': ResourceAllocation + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + destroy-instance: + description: Destroy an instance of resource utilization and release it back to the pool + input: + leaf: + instance-id: + type: + instance-identifier: + 'ct:instance-type': ResourceAllocation + require-instance: true + output: + leaf: + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + add-provider: + description: Register a new resource provider into reservation system + input: + leaf: + provider-type: + description: Select a specific resource provider type + mandatory: true + type: + enumeration: + enum: + openstack: + value: 0 + hp: + value: 1 + rackspace: + value: 2 + amazon: + status: planned + value: 3 + joyent: + status: planned + value: 4 + azure: + status: planned + value: 5 + default: openstack + strategy: + type: + enumeration: + enum: + oauth: + value: 0 + keystone: + value: 1 + default: keystone + endpoint: + type: 'inet:uri' + description: The target endpoint for authentication + mandatory: true + default: 'http://localhost:5000/v2.0' + username: + type: string + mandatory: true + password: + type: 'acm:password' + mandatory: true + uses: {} + container: + tenant: + leaf: + id: + type: string + name: + type: string + output: + leaf: + provider-id: + type: + instance-identifier: + 'ct:instance-type': ResourceProvider + result: + type: + enumeration: + enum: + ok: + value: 0 + conflict: + value: 1 + error: + value: 2 + message: + type: string + notification: + reservation-event: null + capacity-event: null + allocation-event: null +dependencies: + access-control-models: + module: + access-control-models: + prefix: acm + namespace: 'urn:opnfv:promise:acm' + import: + complex-types: + prefix: ct + ietf-yang-types: + prefix: yang + ietf-inet-types: + prefix: inet + typedef: + password: + type: + string: + length: 1..255 + grouping: + access-credentials: + leaf: + strategy: + type: + enumeration: + enum: + oauth: + value: 0 + keystone: + value: 1 + default: oauth + endpoint: + type: 'inet:uri' + description: The target endpoint for authentication + mandatory: true + username: + type: string + mandatory: true + password: + type: 'acm:password' + mandatory: true + 'ct:complex-type': + Identity: + 'ct:abstract': 'true' + description: Identity represents an administrative access model entity + key: id + leaf: + id: + type: 'yang:uuid' + mandatory: true + name: + type: string + mandatory: true + description: + type: string + enabled: + type: boolean + default: 'true' + User: + 'ct:extends': Identity + leaf: + credential: + type: string + mandatory: true + domain: + type: + instance-identifier: + 'ct:instance-type': Domain + container: + contact: + leaf: + fullName: + type: string + email: + type: string + leaf-list: + groups: + type: + instance-identifer: + 'ct:instance-type': Group + Group: + 'ct:extends': Identity + leaf-list: + users: + type: + instance-identifier: + 'ct:instance-type': User + leaf: + domain: + type: + instance-identifier: + 'ct:instance-type': Domain + Domain: + 'ct:extends': Identity + description: |- + Domain represent a distinct administrative domain across + collection of users and groups. + 'ct:instance-list': + users: + 'ct:instance-type': User + groups: + 'ct:instance-type': Group + rpc: + create-user: null + remove-user: null + create-group: null + remove-group: null + nfv-infrastructure: + module: + nfv-infrastructure: + namespace: 'urn:opnfv:promise:nfv:infrastructure' + prefix: nfvi + import: + access-control-models: + prefix: acm + ietf-inet-types: + prefix: inet + ietf-yang-types: + prefix: yang + complex-types: + prefix: ct + description: |- + NFV Infrastructure Data Models with complex types and typed instance + identifiers representing the various ResourceElements available + in the infrastructure across compute, network, and storage. + revision: + '2015-10-13': + description: Introduce capacity and element collection into NFVI models + '2015-08-07': + description: |- + This YANG module is modeled using 'yangforge' which natively provides + complex types and typed instance identifiers. This module + provides various collections of resource management data models + for instance based management + identity: + manager: + description: used by specific modules implementing manager role for NFVI + grouping: + compute-capacity: + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + network-capacity: + leaf: + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + storage-capacity: + leaf: + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + resource-capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + resource-collection: + description: |- + Information model capturing parameters for describing a collection of + resource capacity and resource elements + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + resource-stack: + description: |- + Information model describing a NFVI resource stack comprising of + various resource elements across compute, network, and storage + 'ct:instance-list': + hosts: + 'ct:instance-type': 'nfvi:PhysicalHost' + hypervisors: + 'ct:instance-type': 'nfvi:Hypervisor' + container: + compute: + description: Contains compute related resources + 'ct:instance-list': + servers: + 'ct:instance-type': 'nfvi:ServerInstance' + images: + 'ct:instance-type': 'nfvi:VirtualMachineImage' + flavors: + 'ct:instance-type': 'nfvi:ComputeFlavor' + network: + description: Contains networking related resources + 'ct:instance-list': + networks: + 'ct:instance-type': 'nfvi:Network' + subnets: + 'ct:instance-type': 'nfvi:SubNetwork' + ports: + 'ct:instance-type': 'nfvi:SwitchPort' + 'ct:complex-type': + ResourceElement: + 'ct:abstract': 'true' + key: id + leaf: + id: + type: 'yang:uuid' + mandatory: true + name: + type: string + enabled: + type: boolean + default: 'true' + protected: + type: boolean + default: 'false' + owner: + type: + instance-identifier: + 'ct:instance-type': 'acm:Identity' + visibility: + description: "Specify visibility level available from the perspective of 'owner'" + type: + enumeration: + enum: + public: + value: 0 + domain: + value: 1 + project: + value: 2 + group: + value: 3 + user: + value: 4 + default: user + leaf-list: + tags: + type: string + members: + description: Optionally share with explicit list of members of AccessIdentity complex-type + type: + instance-identifier: + 'ct:instance-type': 'acm:Identity' + ResourceInstance: + 'ct:extends': ResourceElement + 'ct:abstract': 'true' + leaf: + status: + type: + enumeration: + enum: + active: + value: 0 + inactive: + value: 1 + pending: + value: 2 + progress: + type: + uint8: + range: 0..100 + default: '0' + ResourceContainer: + 'ct:extends': ResourceInstance + 'ct:abstract': 'true' + description: |- + An abstract resource instance which contains a collection of capacity + and elements. + container: + capacity: + uses: {} + leaf: + cores: + type: int16 + default: '0' + ram: + type: int32 + default: '0' + units: MB + instances: + type: int16 + default: '0' + networks: + type: int16 + default: '0' + ports: + type: int16 + default: '0' + routers: + type: int16 + default: '0' + subnets: + type: int16 + default: '0' + addresses: + type: int32 + default: '0' + gigabytes: + type: int32 + default: '0' + units: GB + snapshots: + type: int16 + default: '0' + volumes: + type: int16 + default: '0' + leaf-list: + elements: + type: + instance-identifier: + 'ct:instance-type': 'nfvi:ResourceElement' + require-instance: true + AvailabilityZone: + 'ct:extends': ResourceElement + PhysicalHost: + 'ct:extends': ResourceElement + leaf: + type: + type: string + version: + type: string + cpu: + type: uint8 + workload: + type: uint8 + default: '0' + uptime: + type: string + container: + ram: + leaf: + total: + type: uint32 + units: MB + used: + type: uint32 + units: MB + free: + type: uint32 + units: MB + disk: + leaf: + total: + type: uint32 + units: GB + used: + type: uint32 + units: GB + free: + type: uint32 + units: GB + leaf-list: + hypervisors: + type: + instance-identifier: + 'ct:instance-type': Hypervisor + Hypervisor: + 'ct:extends': PhysicalHost + leaf: + host: + type: + instance-identifier: + 'ct:instance-type': PhysicalHost + mandatory: true + container: + vcpu: + leaf: + total: + type: uint16 + used: + type: uint16 + free: + type: uint16 + leaf-list: + servers: + type: + instance-identifier: + 'ct:instance-type': ServerInstance + ComputeElement: + 'ct:extends': ResourceElement + 'ct:abstract': 'true' + container: + constraint: + leaf: + disk: + type: uint32 + units: GB + default: '0' + ram: + type: uint32 + units: MB + default: '0' + vcpu: + type: uint16 + default: '0' + leaf-list: + instances: + description: State info about instances currently using this resource element + type: + instance-identifier: + 'ct:instance-type': ResourceInstance + config: false + VirtualMachineImage: + 'ct:extends': ComputeElement + container: + data: + leaf: + checksum: + type: string + mandatory: true + size: + type: uint32 + units: Bytes + mandatory: true + content: + description: "should be a 'private' property so only direct access retrieves content" + type: binary + container: + format: + leaf: + container: + type: + enumeration: + enum: + ami: + value: 0 + ari: + value: 1 + aki: + value: 2 + bare: + value: 3 + ovf: + value: 4 + default: bare + disk: + type: + enumeration: + enum: + ami: + value: 0 + ari: + value: 1 + aki: + value: 2 + vhd: + value: 3 + vmdk: + value: 4 + raw: + value: 5 + qcow2: + value: 6 + vdi: + value: 7 + iso: + value: 8 + ComputeFlavor: + 'ct:extends': ResourceElement + leaf: + disk: + type: uint32 + units: GB + default: '0' + ram: + type: uint32 + units: MB + default: '0' + vcpus: + type: uint16 + default: '0' + ServerInstance: + 'ct:extends': ResourceInstance + leaf: + flavor: + type: + instance-identifier: + 'ct:instance-type': ComputeFlavor + mandatory: true + image: + type: + instance-identifier: + 'ct:instance-type': VirtualMachineImage + mandatory: true + host: + type: + instance-identifier: + 'ct:instance-type': PhysicalHost + leaf-list: + connections: + description: |- + References to collection of NetworkingElement class objects such as + Network, Subnet, Port, Router that this ServerInstance is + connected with. + type: + instance-identifier: + 'ct:instance-type': NetworkElement + NetworkElement: + 'ct:extends': ResourceElement + 'ct:abstract': 'true' + Network: + 'ct:extends': NetworkElement + leaf-list: + subnets: + type: + instance-identifier: + 'ct:instance-type': SubNetwork + SubNetwork: + 'ct:extends': NetworkElement + leaf: + network: + type: + instance-identifier: + 'ct:instance-type': Network + leaf-list: + nameservers: + type: string + container: + dhcp: + leaf: + enabled: + type: boolean + list: + pools: + leaf: + start: + type: 'inet:ip-address' + end: + type: 'inet:ip-address' + SwitchPort: + 'ct:extends': NetworkElement + leaf: + subnet: + type: + instance-identifier: + 'ct:instance-type': SubNetwork +extension: + module: + argument: name + include: 0..n + prefix: 0..1 + anyxml: 0..n + augment: 0..n + choice: 0..n + contact: 0..1 + container: 0..n + description: 0..1 + deviation: 0..n + extension: 0..n + feature: 0..n + grouping: 0..n + identity: 0..n + import: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + namespace: 0..1 + notification: 0..n + organization: 0..1 + reference: 0..1 + revision: 0..n + rpc: 0..n + typedef: 0..n + uses: 0..n + yang-version: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + var changes, match, ref, synth, target; + synth = this.require('data-synth'); + ref = params.augment; + for (target in ref) { + changes = ref[target]; + match = this.locate(ctx, target); + if (match == null) { + continue; + } + synth.copy(match, changes); + } + return delete this.source[params.prefix]; + } + construct: ! |- + function (arg, params, children, ctx, self) { + return (self.origin.construct.apply(this, arguments)).merge({ + models: this.resolve('complex-type') + }); + } + complex-type: 0..n + instance: 0..n + instance-list: 0..n + origin: + argument: name + include: 0..n + prefix: 0..1 + anyxml: 0..n + augment: 0..n + choice: 0..n + contact: 0..1 + container: 0..n + description: 0..1 + deviation: 0..n + extension: 0..n + feature: 0..n + grouping: 0..n + identity: 0..n + import: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + namespace: 0..1 + notification: 0..n + organization: 0..1 + reference: 0..1 + revision: 0..n + rpc: 0..n + typedef: 0..n + uses: 0..n + yang-version: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + var changes, match, ref, synth, target; + synth = this.require('data-synth'); + ref = params.augment; + for (target in ref) { + changes = ref[target]; + match = this.locate(ctx, target); + if (match == null) { + continue; + } + synth.copy(match, changes); + } + return delete this.source[params.prefix]; + } + construct: ! |- + function (arg, params, children) { + var k, m, modules, ref, synth, v; + synth = this.require('data-synth'); + modules = {}; + ref = params["import"]; + for (k in ref) { + v = ref[k]; + modules[k] = children[k]; + delete children[k]; + } + m = (synth.Store(params, function() { + return this.set({ + name: arg, + modules: modules + }); + })).bind(children); + this.define('module', arg, m); + return m; + } + prefix: + argument: value + preprocess: ! |- + function (arg, params, ctx) { + return this.source[arg] = this.source; + } + include: + argument: module + preprocess: ! |- + function (arg, params, ctx) { + var k, m, ref, ref1, ref2, results, v; + m = this.preprocess(this.resolve('dependencies', arg)); + ref = m.extension; + for (k in ref) { + v = ref[k]; + this.define('extension', k, v); + } + ref1 = m.typedef; + for (k in ref1) { + v = ref1[k]; + this.define('typedef', k, v); + } + ref2 = m.schema; + results = []; + for (k in ref2) { + v = ref2[k]; + results.push(ctx[k] = v); + } + return results; + } + revision-date: 0..1 + augment: + anyxml: 0..n + case: 0..n + choice: 0..n + container: 0..n + description: 0..1 + if-feature: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + reference: 0..1 + status: 0..1 + uses: 0..n + when: 0..1 + argument: target-node + belongs-to: + prefix: 1 + preprocess: ! |- + function (arg, params, ctx) { + return this.source[params.prefix] = this.source; + } + argument: module + bit: + description: 0..1 + reference: 0..1 + status: 0..1 + position: 0..1 + argument: name + case: + anyxml: 0..n + choice: 0..n + container: 0..n + description: 0..1 + if-feature: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + reference: 0..1 + status: 0..1 + uses: 0..n + when: 0..1 + argument: name + choice: + anyxml: 0..n + case: 0..n + config: 0..1 + container: 0..n + default: 0..1 + description: 0..1 + if-feature: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + mandatory: 0..1 + reference: 0..1 + status: 0..1 + when: 0..1 + argument: condition + config: + preprocess: ! |- + function (arg, p, ctx) { + return ctx.config = arg === true || arg === 'true'; + } + argument: value + container: + anyxml: 0..n + choice: 0..n + config: 0..1 + container: 0..n + description: 0..1 + grouping: 0..n + if-feature: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + must: 0..n + presence: 0..1 + reference: 0..1 + status: 0..1 + typedef: 0..n + uses: 0..n + when: 0..1 + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return (synth.Object(params)).bind(children); + } + argument: name + instance: 0..n + instance-list: 0..n + origin: + anyxml: 0..n + choice: 0..n + config: 0..1 + container: 0..n + description: 0..1 + grouping: 0..n + if-feature: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + must: 0..n + presence: 0..1 + reference: 0..1 + status: 0..1 + typedef: 0..n + uses: 0..n + when: 0..1 + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return (synth.Object(params)).bind(children); + } + argument: name + deviate: + config: 0..1 + default: 0..1 + mandatory: 0..1 + max-elements: 0..1 + min-elements: 0..1 + must: 0..n + type: 0..1 + unique: 0..1 + units: 0..1 + argument: value + deviation: + description: 0..1 + deviate: 1..n + reference: 0..1 + argument: target-node + enum: + description: 0..1 + reference: 0..1 + status: 0..1 + value: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + if (params.value == null) { + if (this.enumValue == null) { + this.enumValue = 0; + } + params.value = this.enumValue++; + } else { + params.value = Number(params.value); + this.enumValue = params.value + 1; + } + return ctx["enum"][arg] = params; + } + argument: name + feature: + description: 0..1 + if-feature: 0..n + reference: 0..1 + status: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + if (params.status === 'unavailable') { + console.warn("feature " + arg + " is unavailable"); + if (typeof ctx.feature === 'object') { + return delete ctx.feature[arg]; + } else { + return delete ctx.feature; + } + } + } + construct: ! |- + function (arg, params, children) { + var feature; + feature = this.resolve('feature', arg); + return null; + } + argument: name + grouping: + anyxml: 0..n + choice: 0..n + container: 0..n + description: 0..1 + grouping: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + reference: 0..1 + status: 0..1 + typedef: 0..n + uses: 0..n + preprocess: ! |- + function (arg, params) { + return this.define('grouping', arg, params); + } + argument: name + instance: 0..n + instance-list: 0..n + origin: + anyxml: 0..n + choice: 0..n + container: 0..n + description: 0..1 + grouping: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + reference: 0..1 + status: 0..1 + typedef: 0..n + uses: 0..n + preprocess: ! |- + function (arg, params) { + return this.define('grouping', arg, params); + } + argument: name + identity: + base: 0..1 + description: 0..1 + reference: 0..1 + status: 0..1 + preprocess: ! |- + function (arg, params) { + return this.define('identity', arg, params); + } + argument: name + if-feature: + preprocess: ! |- + function (arg, params, ctx) { + if ((this.resolve('feature', arg)) == null) { + return ctx.status = 'unavailable'; + } + } + argument: name + import: + prefix: 1 + revision-date: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + var copy, k, m, original, ref, ref1, rev, schema, source, synth, v; + synth = this.require('data-synth'); + schema = this.resolve('dependencies', arg, false); + if (schema == null) { + console.warn("no explicit dependency for " + arg + " defined, searching local filesystem"); + schema = this.parse("!yang " + arg + ".yang", this.source); + if (schema != null) { + this.define('dependencies', arg, schema); + source = this.source.parent; + while ((source.parent != null) && source.parent.name !== 'yangforge') { + source = source.parent; + } + if (source.dependencies == null) { + source.dependencies = {}; + } + source.dependencies[arg] = schema; + } + } + m = this.preprocess(schema); + if (m == null) { + throw this.error("unable to resolve '" + arg + "' in dependencies", 'import'); + } + rev = params['revision-date']; + if ((rev != null) && !(rev in m.revision)) { + throw this.error("requested " + rev + " not available in " + arg, 'import'); + } + ref = m.extension; + for (k in ref) { + v = ref[k]; + if (!(v.override === true)) { + continue; + } + original = this.resolve('extension', k); + copy = synth.copy({}, v); + copy.origin = synth.copy({}, (ref1 = original.origin) != null ? ref1 : original); + delete copy.override; + this.define('extension', k, copy); + } + return this.source[params.prefix] = m; + } + construct: ! |- + function (arg, params, children, ctx) { + return this.compile(this.source[params.prefix], this.source); + } + argument: module + input: + anyxml: 0..n + choice: 0..n + container: 0..n + grouping: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + typedef: 0..n + uses: 0..n + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return (synth.Object(params)).bind(children); + } + instance: 0..n + instance-list: 0..n + origin: + anyxml: 0..n + choice: 0..n + container: 0..n + grouping: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + typedef: 0..n + uses: 0..n + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return (synth.Object(params)).bind(children); + } + leaf: + config: 0..1 + default: 0..1 + description: 0..1 + if-feature: 0..n + mandatory: 0..1 + must: 0..n + reference: 0..1 + status: 0..1 + type: 0..1 + units: 0..1 + when: 0..1 + construct: ! |- + function (arg, params, children, ctx, self) { + var synth; + synth = this.require('data-synth'); + if (params.type['instance-identifier'] != null) { + return synth.BelongsTo(params, function() { + return this.set({ + model: children.type + }); + }); + } else { + return self.origin.construct.apply(this, arguments); + } + } + argument: name + origin: + config: 0..1 + default: 0..1 + description: 0..1 + if-feature: 0..n + mandatory: 0..1 + must: 0..n + reference: 0..1 + status: 0..1 + type: 0..1 + units: 0..1 + when: 0..1 + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return synth.Property(params, function() { + if (children.type != null) { + return this.set({ + type: children.type + }); + } + }); + } + argument: name + leaf-list: + config: 0..1 + description: 0..1 + if-feature: 0..n + max-elements: 0..1 + min-elements: 0..1 + must: 0..n + ordered-by: 0..1 + reference: 0..1 + status: 0..1 + type: 0..1 + units: 0..1 + when: 0..1 + construct: ! |- + function (arg, params, children, ctx, self) { + var synth; + synth = this.require('data-synth'); + if (params.type['instance-identifier'] != null) { + return synth.HasMany(params, function() { + return this.set({ + model: children.type + }); + }); + } else { + return self.origin.construct.apply(this, arguments); + } + } + argument: name + origin: + config: 0..1 + description: 0..1 + if-feature: 0..n + max-elements: 0..1 + min-elements: 0..1 + must: 0..n + ordered-by: 0..1 + reference: 0..1 + status: 0..1 + type: 0..1 + units: 0..1 + when: 0..1 + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return synth.List(params, function() { + if (children.type != null) { + return this.set({ + type: children.type + }); + } + }); + } + argument: name + list: + anyxml: 0..n + choice: 0..n + config: 0..1 + container: 0..n + description: 0..1 + grouping: 0..n + if-feature: 0..n + key: 0..1 + leaf: 0..n + leaf-list: 0..n + list: 0..n + max-elements: 0..1 + min-elements: 0..1 + must: 0..n + ordered-by: 0..1 + reference: 0..1 + status: 0..1 + typedef: 0..n + unique: 0..1 + uses: 0..n + when: 0..1 + construct: ! |- + function (arg, params, children) { + var item, synth; + synth = this.require('data-synth'); + item = (synth.Object(null)).bind(children); + return (synth.List(params)).set({ + type: item + }); + } + argument: name + mandatory: + preprocess: ! |- + function (arg, p, ctx) { + return ctx.mandatory = arg === true || arg === 'true'; + } + argument: value + max-elements: + preprocess: ! |- + function (arg, params, ctx) { + if (arg !== 'unbounded') { + return ctx['max-elements'] = Number(arg); + } + } + argument: value + min-elements: + preprocess: ! |- + function (arg, params, ctx) { + return ctx['min-elements'] = Number(arg); + } + argument: value + must: + description: 0..1 + error-app-tag: 0..1 + error-message: 0..1 + reference: 0..1 + argument: condition + notification: + anyxml: 0..n + choice: 0..n + container: 0..n + description: 0..1 + grouping: 0..n + if-feature: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + reference: 0..1 + status: 0..1 + typedef: 0..n + uses: 0..n + preprocess: ! |- + function (arg, params) { + return this.define('notification', arg, params); + } + argument: event + output: + anyxml: 0..n + choice: 0..n + container: 0..n + grouping: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + typedef: 0..n + uses: 0..n + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return (synth.Object(params)).bind(children); + } + instance: 0..n + instance-list: 0..n + origin: + anyxml: 0..n + choice: 0..n + container: 0..n + grouping: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + typedef: 0..n + uses: 0..n + construct: ! |- + function (arg, params, children) { + var synth; + synth = this.require('data-synth'); + return (synth.Object(params)).bind(children); + } + path: + preprocess: ! |- + function (arg, params, ctx) { + return ctx.path = arg.replace(/[_]/g, '.'); + } + argument: value + pattern: + construct: ! |- + function (arg, params, children, ctx) { + if (ctx.patterns == null) { + ctx.patterns = []; + } + return ctx.patterns.push(new RegExp(arg)); + } + argument: value + refine: + default: 0..1 + description: 0..1 + reference: 0..1 + config: 0..1 + mandatory: 0..1 + presence: 0..1 + must: 0..n + min-elements: 0..1 + max-elements: 0..1 + units: 0..1 + argument: target-node + require-instance: + preprocess: ! |- + function (arg, params, ctx) { + return ctx['require-instance'] = arg === true || arg === 'true'; + } + argument: value + revision: + description: 0..1 + reference: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + return this.define('revision', arg, params); + } + argument: date + rpc: + description: 0..1 + grouping: 0..n + if-feature: 0..n + input: 0..1 + output: 0..1 + reference: 0..1 + status: 0..1 + typedef: 0..n + construct: ! |- + function (arg, params, children) { + var func, method, ref, ref1, request, response, synth; + synth = this.require('data-synth'); + func = this.resolve('rpc', arg, false); + if (func == null) { + func = function(input, output, done) { + return done("No control logic found for '" + arg + "' rpc operation"); + }; + } + request = (ref = children.input) != null ? ref : synth.Meta; + response = (ref1 = children.output) != null ? ref1 : synth.Meta; + method = function(data, resolve, reject) { + var e, error, input, output; + if (typeof console.debug === "function") { + console.debug("executing rpc " + arg + "..."); + } + try { + input = new request(data, this); + output = new response(null, this); + } catch (error) { + e = error; + return reject(e); + } + return func.call(this, input, output, function(e) { + if (e == null) { + return resolve(output); + } else { + return reject(e); + } + }); + }; + method.params = params; + method.input = request; + method.output = response; + return method; + } + argument: name + submodule: + argument: name + anyxml: 0..n + augment: 0..n + belongs-to: 0..1 + choice: 0..n + contact: 0..1 + container: 0..n + description: 0..1 + deviation: 0..n + extension: 0..n + feature: 0..n + grouping: 0..n + identity: 0..n + import: 0..n + include: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + notification: 0..n + organization: 0..1 + reference: 0..1 + revision: 0..n + rpc: 0..n + typedef: 0..n + uses: 0..n + yang-version: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + var k, v; + for (k in params) { + v = params[k]; + ctx[k] = v; + } + return delete ctx.submodule; + } + complex-type: 0..n + instance: 0..n + instance-list: 0..n + origin: + argument: name + anyxml: 0..n + augment: 0..n + belongs-to: 0..1 + choice: 0..n + contact: 0..1 + container: 0..n + description: 0..1 + deviation: 0..n + extension: 0..n + feature: 0..n + grouping: 0..n + identity: 0..n + import: 0..n + include: 0..n + leaf: 0..n + leaf-list: 0..n + list: 0..n + notification: 0..n + organization: 0..1 + reference: 0..1 + revision: 0..n + rpc: 0..n + typedef: 0..n + uses: 0..n + yang-version: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + var k, v; + for (k in params) { + v = params[k]; + ctx[k] = v; + } + return delete ctx.submodule; + } + status: + preprocess: ! |- + function (arg, params, ctx) { + return ctx.status != null ? ctx.status : ctx.status = arg; + } + argument: value + type: + base: 0..1 + bit: 0..n + enum: 0..n + fraction-digits: 0..1 + length: 0..1 + path: 0..1 + pattern: 0..n + range: 0..1 + require-instance: 0..1 + type: 0..n + preprocess: ! |- + function (arg, params, ctx) { + return delete this.enumValue; + } + construct: ! |- + function (arg, params, children, ctx, self) { + if (children.ctype != null) { + ctx.type = children.ctype; + return null; + } else { + return self.origin.construct.apply(this, arguments); + } + } + argument: name + instance-type: 0..1 + origin: + base: 0..1 + bit: 0..n + enum: 0..n + fraction-digits: 0..1 + length: 0..1 + path: 0..1 + pattern: 0..n + range: 0..1 + require-instance: 0..1 + type: 0..n + preprocess: ! |- + function (arg, params, ctx) { + return delete this.enumValue; + } + construct: ! |- + function (arg, params, children, ctx) { + var key, mparams, ref, ref1, synth, typedef, value; + synth = this.require('data-synth'); + typedef = this.resolve('typedef', arg); + if (typedef == null) { + throw this.error("unable to resolve typedef for " + arg); + } + switch (false) { + case typedef.construct == null: + ctx.type = typedef.construct(params, this, arguments.callee); + break; + case typeof typedef.type !== 'object': + ref = typedef.type; + for (key in ref) { + value = ref[key]; + mparams = synth.copy({}, value); + synth.copy(mparams, params); + arguments.callee.call(this, key, mparams, children, ctx); + } + break; + case typeof typedef.type !== 'string': + arguments.callee.call(this, typedef.type, params, children, ctx); + } + if ((ref1 = ctx.type) != null) { + ref1.toString = function() { + return arg; + }; + } + return null; + } + argument: name + typedef: + default: 0..1 + description: 0..1 + units: 0..1 + type: 0..1 + reference: 0..1 + preprocess: ! |- + function (arg, params) { + return this.define('typedef', arg, params); + } + argument: name + uses: + augment: 0..n + description: 0..1 + if-feature: 0..n + refine: 0..n + reference: 0..1 + status: 0..1 + when: 0..1 + preprocess: ! |- + function (arg, params, ctx) { + var changes, grouping, k, match, ref, ref1, synth, target, v; + synth = this.require('data-synth'); + grouping = synth.copy({}, this.resolve('grouping', arg)); + delete grouping.description; + delete grouping.reference; + synth.copy(ctx, grouping); + ref = params.refine; + for (target in ref) { + changes = ref[target]; + match = this.locate(ctx, target); + if (match == null) { + continue; + } + for (k in changes) { + v = changes[k]; + match[k] = v; + } + } + ref1 = params.augment; + for (target in ref1) { + changes = ref1[target]; + match = this.locate(ctx, target); + if (match == null) { + continue; + } + synth.copy(match, changes); + } + if (typeof ctx.uses === 'object') { + return delete ctx.uses[arg]; + } else { + return delete ctx.uses; + } + } + argument: name + when: + description: 0..1 + reference: 0..1 + argument: condition + anyxml: {} + base: + argument: name + contact: + argument: + text: + yin-element: 'true' + default: + argument: value + description: + argument: + text: + yin-element: 'true' + error-app-tag: + argument: value + error-message: + argument: + value: + yin-element: 'true' + fraction-digits: + argument: value + key: + argument: value + length: + argument: value + namespace: + argument: uri + ordered-by: + argument: value + organization: + argument: + text: + yin-element: 'true' + position: + argument: value + presence: + argument: value + range: + argument: value + reference: + argument: + text: + yin-element: 'true' + revision-date: + argument: date + unique: + argument: tag + units: + argument: value + value: + argument: value + yang-version: + argument: value + yin-element: + argument: value +feature: ! '' +keywords: + - opnfv + - promise + - vim + - nfvi + - infrastructure + - openstack + - nbi + - yangforge + - resource + - reservation + - capacity + - allocation +rpc: + create-reservation: ! |- + function (input, output, done) { + var reservation, reservations; + reservation = this.create('ResourceReservation'); + reservations = this.access('promise.reservations'); + return reservation.invoke('update', input.get()).then(function(res) { + return res.save().then(function() { + reservations.push(res); + output.set({ + result: 'ok', + message: 'reservation request accepted' + }); + output.set('reservation-id', res.id); + return done(); + })["catch"](function(err) { + output.set({ + result: 'error', + message: err + }); + return done(); + }); + })["catch"](function(err) { + output.set({ + result: 'conflict', + message: err + }); + return done(); + }); + } + query-reservation: ! |- + function (input, output, done) { + var query; + query = input.get(); + query.capacity = 'reserved'; + return this.invoke('query-capacity', query).then(function(res) { + output.set('reservations', res.get('collections')); + output.set('utilization', res.get('utilization')); + return done(); + })["catch"](function(e) { + return done(e); + }); + } + update-reservation: ! |- + function (input, output, done) { + var reservation; + if ((input.get('reservation-id')) == null) { + output.set({ + result: 'error', + message: "must provide 'reservation-id' parameter" + }); + return done(); + } + reservation = this.find('ResourceReservation', input.get('reservation-id')); + if (reservation == null) { + output.set({ + result: 'error', + message: 'no reservation found for specified identifier' + }); + return done(); + } + return reservation.invoke('update', input.get()).then(function(res) { + return res.save().then(function() { + output.set({ + result: 'ok', + message: 'reservation update successful' + }); + return done(); + })["catch"](function(err) { + output.set({ + result: 'error', + message: err + }); + return done(); + }); + })["catch"](function(err) { + output.set({ + result: 'conflict', + message: err + }); + return done(); + }); + } + cancel-reservation: ! |- + function (input, output, done) { + var reservation; + reservation = this.find('ResourceReservation', input.get('reservation-id')); + if (reservation == null) { + output.set({ + result: 'error', + message: 'no reservation found for specified identifier' + }); + return done(); + } + return reservation.destroy().then((function(_this) { + return function() { + (_this.access('promise.reservations')).remove(reservation.id); + output.set('result', 'ok'); + output.set('message', 'reservation canceled'); + return done(); + }; + })(this))["catch"](function(e) { + output.set('result', 'error'); + output.set('message', e); + return done(); + }); + } + query-capacity: ! |- + function (input, output, done) { + var collections, deltas, entry, k, last, matches, metric, timestamp, usages, v, window; + window = input.get('window'); + metric = input.get('capacity'); + collections = (function() { + switch (metric) { + case 'total': + return ['ResourcePool']; + case 'reserved': + return ['ResourceReservation']; + case 'usage': + return ['ResourceAllocation']; + case 'available': + return ['ResourcePool', 'ResourceReservation', 'ResourceAllocation']; + } + })(); + matches = collections.reduce(((function(_this) { + return function(a, name) { + var res; + res = _this.find(name, { + start: function(value) { + return (window.end == null) || (new Date(value)) <= (new Date(window.end)); + }, + end: function(value) { + return (window.start == null) || (new Date(value)) >= (new Date(window.start)); + }, + enabled: true + }); + return a.concat.apply(a, res); + }; + })(this)), []); + if (window.scope === 'exclusive') { + matches = matches.where({ + start: function(value) { + return (window.start == null) || (new Date(value)) >= (new Date(window.start)); + }, + end: function(value) { + return (window.end == null) || (new Date(value)) <= (new Date(window.end)); + } + }); + } + matches = matches.without({ + id: input.get('without') + }); + if (metric === 'available') { + matches = matches.without({ + reservation: function(v) { + return v != null; + } + }); + } + output.set('collections', matches); + if ((input.get('show-utilization')) !== true) { + return done(); + } + deltas = matches.reduce((function(a, entry) { + var b, base, base1, ekey, k, ref1, ref2, skey, v; + b = entry.get(); + if (b.end == null) { + b.end = 'infiniteT'; + } + ref1 = [(b.start.split('T'))[0], (b.end.split('T'))[0]], skey = ref1[0], ekey = ref1[1]; + if (a[skey] == null) { + a[skey] = { + count: 0, + capacity: {} + }; + } + if (a[ekey] == null) { + a[ekey] = { + count: 0, + capacity: {} + }; + } + a[skey].count += 1; + a[ekey].count -= 1; + ref2 = b.capacity; + for (k in ref2) { + v = ref2[k]; + if (!(v != null)) { + continue; + } + if ((base = a[skey].capacity)[k] == null) { + base[k] = 0; + } + if ((base1 = a[ekey].capacity)[k] == null) { + base1[k] = 0; + } + if (entry.name === 'ResourcePool') { + a[skey].capacity[k] += v; + a[ekey].capacity[k] -= v; + } else { + a[skey].capacity[k] -= v; + a[ekey].capacity[k] += v; + } + } + return a; + }), {}); + last = { + count: 0, + capacity: {} + }; + usages = (function() { + var i, len, ref1, ref2, ref3, results; + ref1 = Object.keys(deltas).sort(); + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + timestamp = ref1[i]; + if (!(timestamp !== 'infinite')) { + continue; + } + entry = deltas[timestamp]; + entry.timestamp = (new Date(timestamp)).toJSON(); + entry.count += last.count; + ref2 = entry.capacity; + for (k in ref2) { + v = ref2[k]; + entry.capacity[k] += (ref3 = last.capacity[k]) != null ? ref3 : 0; + } + last = entry; + results.push(entry); + } + return results; + })(); + output.set('utilization', usages); + return done(); + } + increase-capacity: ! |- + function (input, output, done) { + var pool; + pool = this.create('ResourcePool', input.get()); + return pool.save().then((function(_this) { + return function(res) { + (_this.access('promise.pools')).push(res); + output.set({ + result: 'ok', + message: 'capacity increase successful' + }); + output.set('pool-id', res.id); + return done(); + }; + })(this))["catch"](function(e) { + output.set({ + result: 'error', + message: e + }); + return done(); + }); + } + decrease-capacity: ! |- + function (input, output, done) { + var k, pool, ref1, request, v; + request = input.get(); + ref1 = request.capacity; + for (k in ref1) { + v = ref1[k]; + request.capacity[k] = -v; + } + pool = this.create('ResourcePool', request); + return pool.save().then((function(_this) { + return function(res) { + (_this.access('promise.pools')).push(res); + output.set({ + result: 'ok', + message: 'capacity decrease successful' + }); + output.set('pool-id', res.id); + return done(); + }; + })(this))["catch"](function(e) { + output.set({ + result: 'error', + message: e + }); + return done(); + }); + } + create-instance: ! |- + function (input, output, done) { + var available, flavor, k, pid, provider, required, reservation, rid, v; + pid = input.get('provider-id'); + if (pid != null) { + provider = this.find('ResourceProvider', pid); + if (provider == null) { + output.set({ + result: 'error', + message: "no matching provider found for specified identifier: " + pid + }); + return done(); + } + } else { + provider = (this.find('ResourceProvider'))[0]; + if (provider == null) { + output.set({ + result: 'error', + message: "no available provider found for create-instance" + }); + return done(); + } + } + flavor = provider.access("services.compute.flavors." + (input.get('flavor'))); + if (flavor == null) { + output.set({ + result: 'error', + message: "no such flavor found for specified identifier: " + pid + }); + return done(); + } + required = { + instances: 1, + cores: flavor.get('vcpus'), + ram: flavor.get('ram'), + gigabytes: flavor.get('disk') + }; + rid = input.get('reservation-id'); + if (rid != null) { + reservation = this.find('ResourceReservation', rid); + if (reservation == null) { + output.set({ + result: 'error', + message: 'no valid reservation found for specified identifier' + }); + return done(); + } + if ((reservation.get('active')) !== true) { + output.set({ + result: 'error', + message: "reservation is currently not active" + }); + return done(); + } + available = reservation.get('remaining'); + } else { + available = this.get('promise.capacity.available'); + } + for (k in required) { + v = required[k]; + if ((v != null) && !!v) { + if (!(available[k] >= v)) { + output.set({ + result: 'conflict', + message: "required " + k + "=" + v + " exceeds available " + available[k] + }); + return done(); + } + } + } + return this.create('ResourceAllocation', { + reservation: rid, + capacity: required + }).save().then((function(_this) { + return function(instance) { + var request, url; + url = provider.get('services.compute.endpoint'); + request = _this.parent.require('superagent'); + request.post(url + "/servers").send({ + server: { + name: input.get('name'), + imageRef: input.get('image'), + flavorRef: input.get('flavor') + } + }).set('X-Auth-Token', provider.get('token')).set('Accept', 'application/json').end(function(err, res) { + if ((err != null) || !res.ok) { + instance.destroy(); + console.error(err); + return done(res.error); + } + instance.set('instance-ref', { + provider: provider, + server: res.body.server.id + }); + (_this.access('promise.allocations')).push(instance); + output.set({ + result: 'ok', + message: 'create-instance request accepted' + }); + output.set('instance-id', instance.id); + return done(); + }); + return instance; + }; + })(this))["catch"](function(err) { + output.set({ + result: 'error', + mesage: err + }); + return done(); + }); + } + destroy-instance: ! |- + function (input, output, done) { + var instance; + instance = this.find('ResourceAllocation', input.get('instance-id')); + if (instance == null) { + output.set({ + result: 'error', + message: 'no allocation found for specified identifier' + }); + return done(); + } + return instance.destroy().then((function(_this) { + return function() { + var provider, ref, request, url; + (_this.access('promise.allocations')).remove(instance.id); + ref = instance.get('instance-ref'); + provider = _this.access("promise.providers." + ref.provider); + url = provider.get('services.compute.endpoint'); + request = _this.parent.require('superagent'); + request["delete"](url + "/servers/" + ref.server).set('X-Auth-Token', provider.get('token')).set('Accept', 'application/json').end(function(err, res) { + if ((err != null) || !res.ok) { + console.error(err); + return done(res.error); + } + output.set('result', 'ok'); + output.set('message', 'instance destroyed and resource released back to pool'); + return done(); + }); + return instance; + }; + })(this))["catch"](function(e) { + output.set('result', 'error'); + output.set('message', e); + return done(); + }); + } + add-provider: ! "function (input, output, done) {\n var app, payload, providers, request, url;\n app = this.parent;\n request = app.require('superagent');\n payload = (function() {\n switch (input.get('provider-type')) {\n case 'openstack':\n return {\n auth: {\n tenantId: input.get('tenant.id'),\n tenantName: input.get('tenant.name'),\n passwordCredentials: input.get('username', 'password')\n }\n };\n }\n })();\n if (payload == null) {\n return done('Sorry, only openstack supported at this time');\n }\n url = input.get('endpoint');\n switch (input.get('strategy')) {\n case 'keystone':\n case 'oauth':\n if (!/\\/tokens$/.test(url)) {\n url += '/tokens';\n }\n }\n providers = this.access('promise.providers');\n return request.post(url).send(payload).set('Accept', 'application/json').end((function(_this) {\n return function(err, res) {\n var access, provider, ref1, ref2, ref3;\n if ((err != null) || !res.ok) {\n return done(res.error);\n }\n access = res.body.access;\n provider = _this.create('ResourceProvider', {\n token: access != null ? (ref1 = access.token) != null ? ref1.id : void 0 : void 0,\n name: access != null ? (ref2 = access.token) != null ? (ref3 = ref2.tenant) != null ? ref3.name : void 0 : void 0 : void 0\n });\n return provider.invoke('update', access.serviceCatalog).then(function(res) {\n return res.save().then(function() {\n providers.push(res);\n output.set('result', 'ok');\n output.set('provider-id', res.id);\n return done();\n })[\"catch\"](function(err) {\n output.set('error', {\n message: err\n });\n return done();\n });\n })[\"catch\"](function(err) {\n output.set('error', {\n message: err\n });\n return done();\n });\n };\n })(this));\n }" +typedef: ! '' +complex-type: + ResourceElement: + id: ! |- + function (prev) { + return prev.set('default', function() { + return this.uuid(); + }); + } + ResourceCollection: + id: + - ! |- + function (prev) { + return prev.set('default', function() { + return this.uuid(); + }); + } + start: + - ! |- + function (prev) { + return prev.set('default', function() { + return (new Date).toJSON(); + }); + } + active: + - ! |- + function (prev) { + return this.computed((function() { + var end, now, start; + now = new Date; + start = new Date(this.get('start')); + end = (function() { + switch (false) { + case (this.get('end')) == null: + return new Date(this.get('end')); + default: + return now; + } + }).call(this); + return (this.get('enabled')) && ((start <= now && now <= end)); + }), { + type: prev + }); + } + ResourceReservation: + id: + - ! |- + function (prev) { + return prev.set('default', function() { + return this.uuid(); + }); + } + start: + - ! |- + function (prev) { + return prev.set('default', function() { + return (new Date).toJSON(); + }); + } + active: + - ! |- + function (prev) { + return this.computed((function() { + var end, now, start; + now = new Date; + start = new Date(this.get('start')); + end = (function() { + switch (false) { + case (this.get('end')) == null: + return new Date(this.get('end')); + default: + return now; + } + }).call(this); + return (this.get('enabled')) && ((start <= now && now <= end)); + }), { + type: prev + }); + } + end: + - ! |- + function (prev) { + return prev.set('default', function() { + var end, max; + end = new Date(this.get('start')); + max = this.parent.get('promise.policy.reservation.max-duration'); + if (max == null) { + return; + } + end.setTime(end.getTime() + (max * 60 * 60 * 1000)); + return end.toJSON(); + }); + } + allocations: + - ! |- + function (prev) { + return this.computed((function() { + var res; + res = this.store.find('ResourceAllocation', { + reservation: this.id + }); + return res.map(function(x) { + return x.get('id'); + }); + }), { + type: 'array' + }); + } + remaining: + - ! |- + function (prev) { + return this.computed((function() { + var entry, i, k, len, records, total, usage, v; + total = this.get('capacity'); + records = this.store.find('ResourceAllocation', { + id: this.get('allocations'), + active: true + }); + for (i = 0, len = records.length; i < len; i++) { + entry = records[i]; + usage = entry.get('capacity'); + for (k in usage) { + v = usage[k]; + total[k] -= v; + } + } + return total; + }), { + type: prev + }); + } + validate: + - ! |- + function (prev) { + return function(value, resolve, reject) { + var end, hasCapacity, k, now, ref, start, v; + if (value == null) { + value = {}; + } + ref = value.capacity; + for (k in ref) { + v = ref[k]; + if ((v != null) && !!v) { + hasCapacity = true; + } + } + if ((!hasCapacity) && value.elements.length === 0) { + return reject("unable to validate reservation record without anything being reserved"); + } + now = new Date; + if (value.start != null) { + start = new Date(value.start); + } + if (value.end != null) { + end = new Date(value.end); + } + if ((end != null) && end < now) { + return reject("requested end time " + value.end + " cannot be in the past"); + } + if ((start != null) && (end != null) && start > end) { + retun(reject("requested start time must be earlier than end time")); + } + return resolve(this); + }; + } + update: + - ! |- + function (prev) { + return function(req, resolve, reject) { + if (req.start == null) { + req.start = this.get('start'); + } + if (req.end == null) { + req.end = this.get('end'); + } + return this.parent.invoke('query-capacity', { + start: req.start, + end: req.end, + capacity: 'available', + without: this.id + }).then((function(_this) { + return function(res) { + var available, collections, end, entries, i, k, pools, ref, ref1, start, t1, t2, v, x; + collections = res.get('collections'); + if (!(collections.length > 0)) { + return reject('no resource capacity available during requested start/end time'); + } + pools = collections.filter(function(e) { + return /^ResourcePool/.test(e); + }); + entries = res.get('utilization'); + start = new Date(req.start); + end = new Date(req.end); + for (x = i = 0, ref = entries.length - 1; 0 <= ref ? i <= ref : i >= ref; x = 0 <= ref ? ++i : --i) { + t1 = new Date(entries[x].timestamp); + if (!(t1 < end)) { + break; + } + if (x < entries.length - 1) { + t2 = new Date(entries[x + 1].timestamp); + if (!(t2 > start)) { + continue; + } + } + available = entries[x].capacity; + ref1 = req.capacity; + for (k in ref1) { + v = ref1[k]; + if ((v != null) && !!v) { + if (!(available[k] >= v)) { + return reject("requested " + k + "=" + v + " exceeds available " + available[k] + " between " + t1 + " and " + t2); + } + } + } + } + _this.set(req); + _this.set('pools', pools); + return resolve(_this); + }; + })(this))["catch"](function(err) { + return reject(err); + }); + }; + } + save: + - ! |- + function (prev) { + return function(resolve, reject) { + return this.invoke('validate', this.get()).then(function(res) { + var now; + now = (new Date).toJSON(); + if ((res.get('created-on')) == null) { + res.set('created-on', now); + } + res.set('modified-on', now); + return resolve(res); + })["catch"](function(e) { + return reject(e); + }); + }; + } + ResourceAllocation: + id: + - ! |- + function (prev) { + return prev.set('default', function() { + return this.uuid(); + }); + } + start: + - ! |- + function (prev) { + return prev.set('default', function() { + return (new Date).toJSON(); + }); + } + active: + - ! |- + function (prev) { + return this.computed((function() { + var end, now, start; + now = new Date; + start = new Date(this.get('start')); + end = (function() { + switch (false) { + case (this.get('end')) == null: + return new Date(this.get('end')); + default: + return now; + } + }).call(this); + return (this.get('enabled')) && ((start <= now && now <= end)); + }), { + type: prev + }); + } + priority: + - ! |- + function (prev) { + return this.computed((function() { + switch (false) { + case !((this.get('reservation')) == null): + return 3; + case !!(this.get('active')): + return 2; + default: + return 1; + } + }), { + type: prev + }); + } + ResourcePool: + id: + - ! |- + function (prev) { + return prev.set('default', function() { + return this.uuid(); + }); + } + start: + - ! |- + function (prev) { + return prev.set('default', function() { + return (new Date).toJSON(); + }); + } + active: + - ! |- + function (prev) { + return this.computed((function() { + var end, now, start; + now = new Date; + start = new Date(this.get('start')); + end = (function() { + switch (false) { + case (this.get('end')) == null: + return new Date(this.get('end')); + default: + return now; + } + }).call(this); + return (this.get('enabled')) && ((start <= now && now <= end)); + }), { + type: prev + }); + } + save: + - ! |- + function (prev) { + return function(resolve, reject) { + var hasCapacity, k, ref, v, value; + value = this.get(); + ref = value.capacity; + for (k in ref) { + v = ref[k]; + if ((v != null) && !!v) { + hasCapacity = true; + } + } + if ((!hasCapacity) && value.elements.length === 0) { + return reject("unable to save pool record without any capacity values"); + } + return resolve(this); + }; + } + ResourceProvider: + id: + - ! |- + function (prev) { + return prev.set('default', function() { + return this.uuid(); + }); + } + token: + - ! |- + function (prev) { + return prev.set('private', true); + } + pools: + - ! |- + function (prev) { + return this.computed((function() { + return (this.store.find('ResourcePool', { + source: this.get('name') + })).map(function(x) { + return x.get('id'); + }); + }), { + type: 'array' + }); + } + update: + - ! |- + function (prev) { + return function(services, resolve, reject) { + var request; + if (services == null) { + services = []; + } + if (!services.length) { + return reject("unable to update provider without list of services"); + } + request = this.store.parent.require('superagent'); + services.forEach((function(_this) { + return function(service) { + var url; + switch (service.type) { + case 'compute': + url = service.endpoints[0].publicURL; + _this.set('services.compute.endpoint', url); + request.get(url + "/limits").set('X-Auth-Token', _this.get('token')).set('Accept', 'application/json').end(function(err, res) { + var capacity, ref; + if ((err != null) || !res.ok) { + console.warn("request to discover capacity limits failed"); + return; + } + capacity = (ref = res.body.limits) != null ? ref.absolute : void 0; + return (_this.access('capacity')).set({ + cores: capacity.maxTotalCores, + ram: capacity.maxTotalRAMSize, + instances: capacity.maxTotalInstances, + addresses: capacity.maxTotalFloatingIps + }); + }); + return request.get(url + "/flavors/detail").set('X-Auth-Token', _this.get('token')).set('Accept', 'application/json').end(function(err, res) { + var er, error, flavors, ref; + if ((err != null) || !res.ok) { + console.warn("request to discover compute flavors failed"); + return; + } + flavors = res.body.flavors; + try { + flavors = flavors.map(function(x) { + return { + ResourceFlavor: x + }; + }); + return (ref = _this.access('services.compute.flavors')).push.apply(ref, flavors); + } catch (error) { + er = error; + return console.warn("failed to update flavors into the provider due to validation errors"); + } + }); + } + }; + })(this)); + return resolve(this); + }; + } + ResourceFlavor: ! '' +pkgdir: /home/plee/hack/opnfv-promise +module: + opnfv-promise: + promise.capacity.total: + - ! |- + function (prev) { + return this.computed((function() { + var combine; + combine = function(a, b) { + var k, ref, v; + ref = b.capacity; + for (k in ref) { + v = ref[k]; + if (!(v != null)) { + continue; + } + if (a[k] == null) { + a[k] = 0; + } + a[k] += v; + } + return a; + }; + return (this.parent.get('pools')).filter(function(entry) { + return entry.active === true; + }).reduce(combine, {}); + }), { + type: prev + }); + } + promise.capacity.reserved: + - ! |- + function (prev) { + return this.computed((function() { + var combine; + combine = function(a, b) { + var k, ref, v; + ref = b.remaining; + for (k in ref) { + v = ref[k]; + if (!(v != null)) { + continue; + } + if (a[k] == null) { + a[k] = 0; + } + a[k] += v; + } + return a; + }; + return (this.parent.get('reservations')).filter(function(entry) { + return entry.active === true; + }).reduce(combine, {}); + }), { + type: prev + }); + } + promise.capacity.usage: + - ! |- + function (prev) { + return this.computed((function() { + var combine; + combine = function(a, b) { + var k, ref, v; + ref = b.capacity; + for (k in ref) { + v = ref[k]; + if (!(v != null)) { + continue; + } + if (a[k] == null) { + a[k] = 0; + } + a[k] += v; + } + return a; + }; + return (this.parent.get('allocations')).filter(function(entry) { + return entry.active === true; + }).reduce(combine, {}); + }), { + type: prev + }); + } + promise.capacity.available: + - ! |- + function (prev) { + return this.computed((function() { + var k, reserved, total, usage, v; + total = this.get('total'); + reserved = this.get('reserved'); + usage = this.get('usage'); + for (k in total) { + v = total[k]; + if (!(v != null)) { + continue; + } + if (reserved[k] != null) { + total[k] -= reserved[k]; + } + if (usage[k] != null) { + total[k] -= usage[k]; + } + } + return total; + }), { + type: prev + }); + } +config: ! '' diff --git a/source/openstack.yaml b/source/openstack.yaml new file mode 100644 index 0000000..6881186 --- /dev/null +++ b/source/openstack.yaml @@ -0,0 +1,13 @@ +name: openstack +description: OpenStack Management Module (WIP) +author: Peter K. Lee +license: Apache-2.0 +yangforge: "0.11.x" + +schema: !yang schema/openstack.yang + +dependencies: + access-control-models: !yang schema/access-control-models.yang + nfv-infrastructure: !yang schema/nfv-infrastructure.yang + +rpc: !require spec/openstack-intents.coffee diff --git a/source/package.json b/source/package.json new file mode 100644 index 0000000..7ae3977 --- /dev/null +++ b/source/package.json @@ -0,0 +1,41 @@ +{ + "name": "@opnfv/promise", + "version": "1.0.0", + "description": "Resource Management for Virtualized Infrastructure", + "author": "Peter K. Lee ", + "license": "Apache-2.0", + "private": true, + "homepage": "http://wiki.opnfv.org/promise", + "repository": "opnfv/promise", + "keywords": [ + "opnfv", + "promise", + "vim", + "nfvi", + "infrastructure", + "openstack", + "nbi", + "yangforge", + "resource", + "reservation", + "capacity", + "allocation" + ], + "engines": { + "yangforge": ">=0.11.0" + }, + "devDependencies": { + "config": "^1.19.0", + "js-yaml": "^3.5.2", + "mocha": "~2.0.1", + "promise": "^7.1.1", + "should": "~3.1.3", + "yangforge": "^0.11.0" + }, + "main": "./lib/index.js", + "scripts": { + "prepublish": "yfc build -o index.yaml promise.yaml", + "test": "mocha", + "start": "yfc run --express 5050 promise.yaml" + } +} diff --git a/source/promise.yaml b/source/promise.yaml new file mode 100644 index 0000000..125c7b7 --- /dev/null +++ b/source/promise.yaml @@ -0,0 +1,290 @@ +name: opnfv-promise +description: Resource Management for Virtualized Infrastructure +author: Peter K. Lee +license: Apache-2.0 +homepage: http://wiki.opnfv.org/promise +repository: git://github.com/opnfv/promise.git +yangforge: "0.11.x" +keywords: + - opnfv + - promise + - vim + - nfvi + - infrastructure + - openstack + - nbi + - yangforge + - resource + - reservation + - capacity + - allocation + +schema: !yang schema/opnfv-promise.yang + +# below config provides default parameters +# NOTE: uncomment locally for testing with pre-existing data +#config: !json config/demo.json + +dependencies: + access-control-models: !yang schema/access-control-models.yang + nfv-infrastructure: !yang schema/nfv-infrastructure.yang + +# MODULE model active bindings +module: + opnfv-promise: + # rebind to be a computed property + promise.capacity.total: !coffee/function | + (prev) -> @computed (-> + combine = (a, b) -> + for k, v of b.capacity when v? + a[k] ?= 0 + a[k] += v + return a + (@parent.get 'pools') + .filter (entry) -> entry.active is true + .reduce combine, {} + ), type: prev + # rebind to be a computed property + promise.capacity.reserved: !coffee/function | + (prev) -> @computed (-> + combine = (a, b) -> + for k, v of b.remaining when v? + a[k] ?= 0 + a[k] += v + return a + (@parent.get 'reservations') + .filter (entry) -> entry.active is true + .reduce combine, {} + ), type: prev + # rebind to be a computed property + promise.capacity.usage: !coffee/function | + (prev) -> @computed (-> + combine = (a, b) -> + for k, v of b.capacity when v? + a[k] ?= 0 + a[k] += v + return a + (@parent.get 'allocations') + .filter (entry) -> entry.active is true + .reduce combine, {} + ), type: prev + # rebind to be a computed property + promise.capacity.available: !coffee/function | + (prev) -> @computed (-> + total = @get 'total' + reserved = @get 'reserved' + usage = @get 'usage' + for k, v of total when v? + total[k] -= reserved[k] if reserved[k]? + total[k] -= usage[k] if usage[k]? + total + ), type: prev + +# RPC definitions (INTENT interfaces) +rpc: !require spec/promise-intents.coffee + +# COMPLEX-TYPE model active bindings (controller logic) +complex-type: + ResourceElement: + #properties + id: !coffee/function | + (prev) -> prev.set 'default', -> @uuid() + + ResourceCollection: + # properties + start: !coffee/function | + (prev) -> prev.set 'default', -> (new Date).toJSON() + + active: !coffee/function | + (prev) -> @computed (-> + now = new Date + start = new Date (@get 'start') + end = switch + when (@get 'end')? then new Date (@get 'end') + else now + (@get 'enabled') and (start <= now <= end) + ), type: prev + + ResourceReservation: + end: !coffee/function | + (prev) -> prev.set 'default', -> + end = (new Date @get 'start') + max = @parent.get 'promise.policy.reservation.max-duration' + return unless max? + end.setTime (end.getTime() + (max*60*60*1000)) + end.toJSON() + + allocations: !coffee/function | + (prev) -> @computed (-> + res = (@store.find 'ResourceAllocation', reservation: @id) + res.map (x) -> x.get 'id' + ), type: 'array' + + remaining: !coffee/function | + (prev) -> @computed (-> + total = @get 'capacity' + records = @store.find 'ResourceAllocation', id: (@get 'allocations'), active: true + for entry in records + usage = entry.get 'capacity' + for k, v of usage + total[k] -= v + total + ), type: prev + + # methods + validate: !coffee/function | + (prev) -> (value={}, resolve, reject) -> + # validate that request contains sufficient data + for k, v of value.capacity when v? and !!v + hasCapacity = true + if (not hasCapacity) and value.elements.length is 0 + return reject "unable to validate reservation record without anything being reserved" + # time range verifications + now = new Date + start = (new Date value.start) if value.start? + end = (new Date value.end) if value.end? + # if start? and start < now + # return reject "requested start time #{value.start} cannot be in the past" + if end? and end < now + return reject "requested end time #{value.end} cannot be in the past" + if start? and end? and start > end + retun reject "requested start time must be earlier than end time" + resolve this + + update: !coffee/function | + (prev) -> (req, resolve, reject) -> + req.start ?= @get 'start' + req.end ?= @get 'end' + + # TODO: should validate here... + @parent.invoke 'query-capacity', + start: req.start + end: req.end + capacity: 'available' + without: @id + .then (res) => + collections = res.get 'collections' + unless collections.length > 0 + return reject 'no resource capacity available during requested start/end time' + + pools = collections.filter (e) -> /^ResourcePool/.test e + # should do some policy or check to see if more than one pool acceptable to reservee + + entries = res.get 'utilization' + start = new Date req.start + end = new Date req.end + + for x in [0..entries.length-1] + t1 = new Date entries[x].timestamp + break unless t1 < end + + if x < entries.length-1 + t2 = new Date entries[x+1].timestamp + continue unless t2 > start + + available = entries[x].capacity + for k, v of req.capacity when v? and !!v + unless available[k] >= v + return reject "requested #{k}=#{v} exceeds available #{available[k]} between #{t1} and #{t2}" + + @set req + @set 'pools', pools + resolve this + .catch (err) -> reject err + + save: !coffee/function | + (prev) -> (resolve, reject) -> + @invoke 'validate', @get() + .then (res) -> + # should do something about this reservation record... + now = (new Date).toJSON() + unless (res.get 'created-on')? + res.set 'created-on', now + res.set 'modified-on', now + resolve res + .catch (e) -> reject e + + ResourceAllocation: + # properties + priority: !coffee/function | + (prev) -> @computed (-> + switch + when not (@get 'reservation')? then 3 + when not (@get 'active') then 2 + else 1 + ), type: prev + + ResourcePool: + save: !coffee/function | + (prev) -> (resolve, reject) -> + # validate that record contains sufficient data + value = @get() + + for k, v of value.capacity when v? and !!v + hasCapacity = true + if (not hasCapacity) and value.elements.length is 0 + return reject "unable to save pool record without any capacity values" + + resolve this + + ResourceProvider: + # properties + token: !coffee/function | + (prev) -> prev.set 'private', true + + pools: !coffee/function | + (prev) -> @computed (-> + (@store.find 'ResourcePool', source: (@get 'name')).map (x) -> x.get 'id' + ), type: 'array' + + # methods + # XXX - this method is OpenStack-specific only, will need to revise later + update: !coffee/function | + (prev) -> (services=[], resolve, reject) -> + return reject "unable to update provider without list of services" unless services.length + request = @store.parent.require 'superagent' + services.forEach (service) => + switch service.type + when 'compute' + url = service.endpoints[0].publicURL + @set 'services.compute.endpoint', url + request + .get "#{url}/limits" + .set 'X-Auth-Token', @get 'token' + .set 'Accept', 'application/json' + .end (err, res) => + if err? or !res.ok + console.warn "request to discover capacity limits failed" + return + + capacity = res.body.limits?.absolute + #console.log "\ndiscovered capacity:" + #console.log capacity + + (@access 'capacity').set { + cores: capacity.maxTotalCores + ram: capacity.maxTotalRAMSize + instances: capacity.maxTotalInstances + addresses: capacity.maxTotalFloatingIps + } + request + .get "#{url}/flavors/detail" + .set 'X-Auth-Token', @get 'token' + .set 'Accept', 'application/json' + .end (err, res) => + if err? or !res.ok + console.warn "request to discover compute flavors failed" + return + + flavors = res.body.flavors + # console.log "\ndiscovered flavors:" + # console.log flavors + try + flavors = flavors.map (x) -> ResourceFlavor: x + (@access 'services.compute.flavors').push flavors... + catch er + console.warn "failed to update flavors into the provider due to validation errors" + + # XXX - update should do promise.all + resolve this + diff --git a/source/schema/access-control-models.yang b/source/schema/access-control-models.yang new file mode 100644 index 0000000..7b4684c --- /dev/null +++ b/source/schema/access-control-models.yang @@ -0,0 +1,92 @@ +module access-control-models { + prefix acm; + namespace "urn:opnfv:promise:acm"; + + import complex-types { prefix ct; } + import ietf-yang-types { prefix yang; } + import ietf-inet-types { prefix inet; } + + typedef password { + type string { + length 1..255; + } + } + + grouping access-credentials { + leaf strategy { + type enumeration { + enum oauth; + enum keystone; + } + default oauth; + } + leaf endpoint { + type inet:uri; + description "The target endpoint for authentication"; + mandatory true; + } + leaf username { + type string; + mandatory true; + } + leaf password { + type acm:password; + mandatory true; + } + } + + /********************************************* + * Identity Models + *********************************************/ + + ct:complex-type Identity { + ct:abstract true; + description "Identity represents an administrative access model entity"; + + key "id"; + leaf id { type yang:uuid; mandatory true; } + leaf name { type string; mandatory true; } + leaf description { type string; } + leaf enabled { type boolean; default true; } + } + + ct:complex-type User { + ct:extends Identity; + + leaf credential { + //type instance-identifier { ct:instance-type IdentityCredential; } + type string; + mandatory true; + } + + container contact { + leaf fullName { type string; } + leaf email { type string; } + } + + leaf-list groups { type instance-identifer { ct:instance-type Group; } } + leaf domain { type instance-identifier { ct:instance-type Domain; } } + } + + ct:complex-type Group { + ct:extends Identity; + + leaf-list users { type instance-identifier { ct:instance-type User; } } + leaf domain { type instance-identifier { ct:instance-type Domain; } } + } + + ct:complex-type Domain { + ct:extends Identity; + description + "Domain represent a distinct administrative domain across + collection of users and groups."; + + ct:instance-list users { ct:instance-type User; } + ct:instance-list groups { ct:instance-type Group; } + } + + rpc create-user; + rpc remove-user; + rpc create-group; + rpc remove-group; +} diff --git a/source/schema/nfv-infrastructure.yang b/source/schema/nfv-infrastructure.yang new file mode 100644 index 0000000..ccad269 --- /dev/null +++ b/source/schema/nfv-infrastructure.yang @@ -0,0 +1,322 @@ +module nfv-infrastructure { + namespace "urn:opnfv:promise:nfv:infrastructure"; + prefix nfvi; + + import access-control-models { prefix acm; } + import ietf-inet-types { prefix inet; } + import ietf-yang-types { prefix yang; } + import complex-types { prefix ct; } + + description + "NFV Infrastructure Data Models with complex types and typed instance + identifiers representing the various ResourceElements available + in the infrastructure across compute, network, and storage."; + + revision 2015-10-13 { + description + "Introduce capacity and element collection into NFVI models"; + } + + revision 2015-08-07 { + description + "This YANG module is modeled using 'yangforge' which natively provides + complex types and typed instance identifiers. This module + provides various collections of resource management data models + for instance based management"; + } + + identity manager { + description "used by specific modules implementing manager role for NFVI"; + } + + grouping compute-capacity { + leaf cores { type int16; default 0; } + leaf ram { type int32; default 0; units 'MB'; } + leaf instances { type int16; default 0; } + } + + grouping network-capacity { + leaf networks { type int16; default 0; } + leaf ports { type int16; default 0; } + leaf routers { type int16; default 0; } + leaf subnets { type int16; default 0; } + leaf addresses { type int32; default 0; } + } + + grouping storage-capacity { + leaf gigabytes { type int32; default 0; units 'GB'; } + leaf snapshots { type int16; default 0; } + leaf volumes { type int16; default 0; } + } + + grouping resource-capacity { + uses compute-capacity; + uses network-capacity; + uses storage-capacity; + } + + grouping resource-collection { + description + "Information model capturing parameters for describing a collection of + resource capacity and resource elements"; + + container capacity { uses resource-capacity; } + leaf-list elements { + type instance-identifier { + ct:instance-type nfvi:ResourceElement; + require-instance true; + } + } + } + + grouping resource-stack { + description + "Information model describing a NFVI resource stack comprising of + various resource elements across compute, network, and storage"; + + ct:instance-list hosts { ct:instance-type nfvi:PhysicalHost; } + ct:instance-list hypervisors { ct:instance-type nfvi:Hypervisor; } + + container compute { + description "Contains compute related resources"; + + ct:instance-list servers { ct:instance-type nfvi:ServerInstance; } + ct:instance-list images { ct:instance-type nfvi:VirtualMachineImage; } + ct:instance-list flavors { ct:instance-type nfvi:ComputeFlavor; } + } + + container network { + description "Contains networking related resources"; + + ct:instance-list networks { ct:instance-type nfvi:Network; } + ct:instance-list subnets { ct:instance-type nfvi:SubNetwork; } + ct:instance-list ports { ct:instance-type nfvi:SwitchPort; } + //ct:instance-list routers { ct:instance-type Router; } + } + } + + /********************************************* + * Abstract Models (top-level) + *********************************************/ + + ct:complex-type ResourceElement { + ct:abstract true; + + key "id"; + leaf id { type yang:uuid; mandatory true; } + leaf name { type string; } + leaf enabled { type boolean; default true; } + leaf protected { type boolean; default false; } + leaf owner { type instance-identifier { ct:instance-type acm:Identity; } } + leaf visibility { + description "Specify visibility level available from the perspective of 'owner'"; + type enumeration { + enum public; + enum domain; + enum project; + enum group; + enum user; + } + default user; + } + leaf-list tags { type string; } + + leaf-list members { + description "Optionally share with explicit list of members of AccessIdentity complex-type"; + type instance-identifier { + ct:instance-type acm:Identity; + } + } + } + + ct:complex-type ResourceInstance { + ct:extends ResourceElement; + ct:abstract true; + + leaf status { + type enumeration { + enum active; + enum inactive; + enum pending; + } + } + leaf progress { + type uint8 { range 0..100; } + default 0; + } + } + + ct:complex-type ResourceContainer { + ct:extends ResourceInstance; + ct:abstract true; + + description + "An abstract resource instance which contains a collection of capacity + and elements."; + + uses resource-collection; + } + + /********************************************* + * Compute Models + *********************************************/ + + ct:complex-type AvailabilityZone { + ct:extends ResourceElement; + } + + ct:complex-type PhysicalHost { + ct:extends ResourceElement; + + leaf type { type string; } + leaf version { type string; } + + leaf cpu { type uint8; } + leaf workload { type uint8; default 0; } + leaf uptime { type string; } + + container ram { + leaf total { type uint32; units 'MB'; } + leaf used { type uint32; units 'MB'; } + leaf free { type uint32; units 'MB'; } + } + container disk { + leaf total { type uint32; units 'GB'; } + leaf used { type uint32; units 'GB'; } + leaf free { type uint32; units 'GB'; } + } + + leaf-list hypervisors { type instance-identifier { ct:instance-type Hypervisor; } } + } + + ct:complex-type Hypervisor { + ct:extends PhysicalHost; + + leaf host { + type instance-identifier { ct:instance-type PhysicalHost; } + mandatory true; + } + container vcpu { + leaf total { type uint16; } + leaf used { type uint16; } + leaf free { type uint16; } + } + leaf-list servers { type instance-identifier { ct:instance-type ServerInstance; } } + } + + ct:complex-type ComputeElement { + ct:extends ResourceElement; + ct:abstract true; + + container constraint { + leaf disk { type uint32; units 'GB'; default 0; } + leaf ram { type uint32; units 'MB'; default 0; } + leaf vcpu { type uint16; default 0; } + } + + leaf-list instances { + description "State info about instances currently using this resource element"; + type instance-identifier { + ct:instance-type ResourceInstance; + } + config false; + } + } + + ct:complex-type VirtualMachineImage { + ct:extends ComputeElement; + + container data { + leaf checksum { type string; mandatory true; } + leaf size { type uint32; units 'Bytes'; mandatory true; } + + container format { + leaf container { + type enumeration { enum ami; enum ari; enum aki; enum bare; enum ovf; } + default bare; + } + leaf disk { + type enumeration { enum ami; enum ari; enum aki; enum vhd; enum vmdk; enum raw; enum qcow2; enum vdi; enum iso; } + } + } + leaf content { + description "should be a 'private' property so only direct access retrieves content"; + type binary; + } + } + } + + ct:complex-type ComputeFlavor { + ct:extends ResourceElement; + + leaf disk { type uint32; units 'GB'; default 0; } + leaf ram { type uint32; units 'MB'; default 0; } + leaf vcpus { type uint16; default 0; } + } + + ct:complex-type ServerInstance { + ct:extends ResourceInstance; + + leaf flavor { + type instance-identifier { ct:instance-type ComputeFlavor; } + mandatory true; + } + leaf image { + type instance-identifier { ct:instance-type VirtualMachineImage; } + mandatory true; + } + + //ct:instance metadata { ct:instance-type MetaData; } + + leaf host { + type instance-identifier { ct:instance-type PhysicalHost; } + } + + leaf-list connections { + description + "References to collection of NetworkingElement class objects such as + Network, Subnet, Port, Router that this ServerInstance is + connected with."; + type instance-identifier { ct:instance-type NetworkElement; } + } + } + + /********************************************* + * Network Models (Work-in-Progress) + *********************************************/ + + ct:complex-type NetworkElement { + ct:extends ResourceElement; + ct:abstract true; + } + + ct:complex-type Network { + ct:extends NetworkElement; + + leaf-list subnets { + type instance-identifier { ct:instance-type SubNetwork; } + } + } + + ct:complex-type SubNetwork { + ct:extends NetworkElement; + + leaf network { type instance-identifier { ct:instance-type Network; } } + + leaf-list nameservers { type string; } + + container dhcp { + leaf enabled { type boolean; } + list pools { + leaf start { type inet:ip-address; } + leaf end { type inet:ip-address; } + } + } + } + + ct:complex-type SwitchPort { + ct:extends NetworkElement; + + leaf subnet { type instance-identifier { ct:instance-type SubNetwork; } } + } +} diff --git a/source/schema/nfv-mano.yang b/source/schema/nfv-mano.yang new file mode 100644 index 0000000..0e3bbbe --- /dev/null +++ b/source/schema/nfv-mano.yang @@ -0,0 +1,149 @@ +module nfv-mano { + namespace "urn:opnfv:promise:nfv:mano"; + prefix mano; + + import access-control-models { prefix acm; } + import nfv-infrastructure { prefix nfvi; } + import complex-types { prefix ct; } + import ietf-inet-types { prefix inet; } + import iana-crypt-hash { prefix ianach; } + + description + "NFV Management and Orchestration Data Models with complex types and typed + instance identifiers representing the NFVO, VNFM, and the VIM."; + + revision 2015-09-03 { + description + "This YANG module is modeled using 'yangforge' which natively provides + complex types and typed instance identifiers. This module + provides various collections of infrastructure management data + models for instance based management"; + } + + grouping provider-credentials { + leaf endpoint { + type inet:uri; + description "The target URL endpoint for the resource provider"; + mandatory true; + } + leaf username { + type string; + mandatory true; + } + leaf password { + type acm:password; + mandatory true; + } + leaf region { + type string; + description "Optional specified region for the provider"; + } + } + + ct:complex-type ServiceOrchestrator { + ct:extends acm:Domain; + ct:abstract true; + // TBD + } + + ct:complex-type ResourceOrchestrator { + ct:extends acm:Domain; + ct:abstract true; + // TBD + } + + ct:complex-type VirtualNetworkFunctionManager { + ct:extends acm:Domain; + ct:abstract true; + // TBD + } + + ct:complex-type VirtualInfrastructureManager { + ct:extends acm:Domain; + ct:abstract true; + + leaf name { type string; mandatory true; } + + container auth { + description 'Conceptual container that will be extended by explicit provider'; + // ct:instance-list credentials { ct:instance-type AccessCredential; } + // ct:instance-list roles { ct:instance-type AccessRole; } + // ct:instance-list policies { ct:instance-type AccessPolicy; } + } + + ct:instance-list hosts { ct:instance-type nfvi:PhysicalHost; } + ct:instance-list hypervisors { ct:instance-type nfvi:Hypervisor; } + + container compute { + if-feature has-compute; + description "Contains compute related resources"; + + ct:instance-list servers { ct:instance-type nfvi:ServerInstance; } + ct:instance-list images { ct:instance-type nfvi:VirtualMachineImage; } + ct:instance-list flavors { ct:instance-type nfvi:ComputeFlavor; } + } + + container network { + if-feature has-networking; + description "Contains networking related resources"; + + ct:instance-list networks { ct:instance-type nfvi:Network; } + ct:instance-list subnets { ct:instance-type nfvi:SubNetwork; } + ct:instance-list ports { ct:instance-type nfvi:SwitchPort; } + //ct:instance-list routers { ct:instance-type Router; } + } + } + + container stack { + container nfvo { + ct:instance service { ct:instance-type ServiceOrchestrator; } + ct:instance resource { ct:instance-type ResourceOrchestrator; } + } + container vnfm { + ct:instance-list managers { ct:instance-type VirtualNetworkFunctionManager; } + } + container vim { + ct:instance-list managers { ct:instance-type VirtualInfrastructureManager; } + } + } + + rpc add-vim { + description "This operation allows you to add a new VirtualInfrastructureManager into the NFVI stack"; + input { + uses provider-credentials; + + leaf provider { + description "Select a specific resource provider"; + mandatory true; + type enumeration { + enum openstack; + enum hp; + enum rackspace; + enum amazon { + status planned; + } + enum joyent { + status planned; + } + enum azure { + status planned; + } + } + } + } + output { + leaf id { + description "Unique identifier for the newly added provider found in /promise/providers"; + type instance-identifier { + ct:instance-type VirtualInfrastructureManager; + } + } + leaf result { + type enumeration { + enum success; + enum error; + } + } + } + } +} diff --git a/source/schema/openstack-compute.yang b/source/schema/openstack-compute.yang new file mode 100644 index 0000000..c3e790c --- /dev/null +++ b/source/schema/openstack-compute.yang @@ -0,0 +1,72 @@ +module openstack-compute { + prefix os-com; + + import nfv-infrastructure { prefix nfvi; } + import complex-types { prefix ct; } + + identity nova { base nvfi:compute; } + + feature availability-zone { + description "Specifies whether availability zone functionality is available."; + } + feature extended-status { + description "Specifies whether extended status functionality is available."; + } + feature security-groups { + description "Specifies whether security groups functionality is available."; + } + + ct:complex-type ServerInstance { + ct:extends nfvi:ServerInstance; + + leaf zone { + if-feature availability-zone; + type string; + } + + leaf project { + type instance-identifier { ct:instance-type nfvi:ResourceProject; } + mandatory true; + } + + container extended-status { + if-feature extended-status; + leaf locked-by; + leaf power; + leaf task; + leaf vm; + } + + leaf-list security-groups { + if-feature security-groups; + type instance-identifier { ct:instance-type SecurityGroup; } + } + + } + + choice version { + case v2.1 { + ct:instance-list servers { ct:instance-type ServerInstance; } + } + } + + // OpenStack Nova specific RPC calls + rpc resize { + input { + leaf server { type instance-type { ct:instance-type ServerInstance; } } + // other params for resize + } + } + rpc backup; + rpc migrate; + rpc restore; + rpc evacuate; + rpc lock; + rpc unlock; + rpc suspend; + rpc resume; + rpc pause; + rpc unpause; + rpc inject-network; + rpc reset-network; +} diff --git a/source/schema/openstack-identity.yang b/source/schema/openstack-identity.yang new file mode 100644 index 0000000..4b92957 --- /dev/null +++ b/source/schema/openstack-identity.yang @@ -0,0 +1,84 @@ +module openstack-identity { + namespace "urn:opnfv:promise:openstack:identity"; + prefix os-id; + + import access-control-models { prefix acm; } + import nfv-infrastructure { prefix nfvi; } + import complex-types { prefix ct; } + import ietf-yang-types { prefix yang; } + + description + "OpenStack Identity Data Models with complex types and typed instance + identifiers represent the various Access Control Models available + within OpenStack."; + + revision 2015-09-03 { + description + "This YANG module is modeled using 'yangforge' which natively provides + complex types and typed instance identifiers. This module + provides various collections of resource management data models + for instance based management"; + } + + /********************************************* + * OpenStack Identity Models + *********************************************/ + + ct:complex-type Project { + ct:extends acm:Group; + description + "OpenStack Project represent a distinct resource consumption space across + collection of users and groups that can reserve and allocate + resources."; + + leaf-list groups { type instance-identifer { ct:instance-type acm:Group; } } + + container resource { + leaf-list images { + if-feature vm-images; + type instance-identifier { ct:instance-type nfvi:VirtualMachineImage; } + } + + leaf-list flavors { + if-feature compute-flavors; + type instance-identifier { ct:instance-type nfvi:VirtualMachineFlavor; } + } + } + } + + ct:complex-type User { + ct:extends acm:User; + + description + "OpenStack User can also belong to multiple projects."; + + leaf-list projects { type instance-identifier { ct:instance-type Project; } } + } + + ct:complex-type Group { + ct:extends acm:Group; + + description + "OpenStack Group can also belong to multiple projects."; + + leaf-list projects { type instance-identifier { ct:instance-type Project; } } + } + + ct:complex-type Domain { + ct:extends acm:Domain; + + description + "OpenStack Domain represent a distinct administrative domain including projects."; + + ct:instance-list projects { ct:instance-type Project; } + } + + ct:complex-type Token { + leaf key { type yang:uuid; } + leaf identity { type instance-identifier { ct:instance-type Identity; } } + } + + + rpc create-project; + rpc remove-project; +} diff --git a/source/schema/openstack.yang b/source/schema/openstack.yang new file mode 100644 index 0000000..6878f7e --- /dev/null +++ b/source/schema/openstack.yang @@ -0,0 +1,74 @@ +module openstack { + prefix os; + + import nfv-infrastructure { prefix nfvi; } + import access-control-models { prefix acm; } + import ietf-yang-types { prefix yang; } + import ietf-inet-types { prefix inet; } + import complex-types { prefix ct; } + + description + "OpenStack controller module"; + + revision 2016-01-19 { + description "Basic coverage of limited intents needed for Promise"; + } + + identity openstack { base nfvi:manager; } + identity release { base openstack; } + identity distro { base openstack; } + + feature os-system-admin { + description "OpenStack system administration capability"; + } + + grouping os-credentials { + uses acm:access-credentials { + refine strategy { + default keystone; + } + refine endpoint { + default "http://localhost:5000/v2.0"; + } + } + container tenant { + leaf id { type string; } + leaf name { type string; } + } + } + + // OpenStack infrastructure platform (PLACEHOLDER) + container platform { + uses nfvi:resource-stack; + + leaf release { type identityref { base release; } } + leaf distro { type identityref { base distro; } } + + //ct:instance-list services { ct:instance-type OpenStackService; } + //ct:instance-list endpoints { ct:instance-type ServiceEndpoint; } + } + + // OpenStack system administrator configuration tree + container admin { + if-feature os-system-admin; + container auth { + uses os-credentials; + leaf token { type yang:uuid; } + } + } + + rpc authenticate { + if-feature os-system-admin; + input { + uses os-credentials; + } + output { + leaf token { type yang:uuid; } + } + } + + rpc create-tenant { + if-feature os-system-admin; + + } +} diff --git a/source/schema/opnfv-promise.yang b/source/schema/opnfv-promise.yang new file mode 100644 index 0000000..b606382 --- /dev/null +++ b/source/schema/opnfv-promise.yang @@ -0,0 +1,640 @@ +module opnfv-promise { + namespace "urn:opnfv:promise"; + prefix promise; + + import complex-types { prefix ct; } + import ietf-yang-types { prefix yang; } + import ietf-inet-types { prefix inet; } + import access-control-models { prefix acm; } + import nfv-infrastructure { prefix nfvi; } + + description + "OPNFV Promise Resource Reservation/Allocation controller module"; + + revision 2015-10-05 { + description "Complete coverage of reservation related intents"; + } + + revision 2015-08-06 { + description "Updated to incorporate YangForge framework"; + } + + revision 2015-04-16 { + description "Initial revision."; + } + + feature reservation-service { + description "When enabled, provides resource reservation service"; + } + + feature multi-provider { + description "When enabled, provides resource management across multiple providers"; + } + + typedef reference-identifier { + description "defines valid formats for external reference id"; + type union { + type yang:uuid; + type inet:uri; + type uint32; + } + } + + grouping resource-utilization { + container capacity { + container total { description 'Conceptual container that should be extended'; } + container reserved { description 'Conceptual container that should be extended'; config false; } + container usage { description 'Conceptual container that should be extended'; config false; } + container available { description 'Conceptual container that should be extended'; config false; } + } + } + + grouping temporal-resource-collection { + description + "Information model capturing resource-collection with start/end time window"; + + leaf start { type yang:date-and-time; } + leaf end { type yang:date-and-time; } + + uses nfvi:resource-collection; + } + + grouping resource-usage-request { + description + "Information model capturing available parameters to make a resource + usage request."; + reference "OPNFV-PROMISE, Section 3.4.1"; + + uses temporal-resource-collection { + refine elements { + description + "Reference to a list of 'pre-existing' resource elements that are + required for fulfillment of the resource-usage-request. + + It can contain any instance derived from ResourceElement, + such as ServerInstances or even other + ResourceReservations. If the resource-usage-request is + accepted, the ResourceElement(s) listed here will be placed + into 'protected' mode as to prevent accidental removal. + + If any of these resource elements become 'unavailable' due to + environmental or administrative activity, a notification will + be issued informing of the issue."; + } + } + + leaf zone { + description "Optional identifier to an Availability Zone"; + type instance-identifier { ct:instance-type nfvi:AvailabilityZone; } + } + } + + grouping query-start-end-window { + container window { + description "Matches entries that are within the specified start/end time window"; + leaf start { type yang:date-and-time; } + leaf end { type yang:date-and-time; } + leaf scope { + type enumeration { + enum "exclusive" { + description "Matches entries that start AND end within the window"; + } + enum "inclusive" { + description "Matches entries that start OR end within the window"; + } + } + default "inclusive"; + } + } + } + + grouping query-resource-collection { + uses query-start-end-window { + description "Match for ResourceCollection(s) that are within the specified start/end time window"; + } + leaf-list without { + description "Excludes specified collection identifiers from the result"; + type instance-identifier { ct:instance-type ResourceCollection; } + } + leaf show-utilization { type boolean; default true; } + container elements { + leaf-list some { + description "Query for ResourceCollection(s) that contain some or more of these element(s)"; + type instance-identifier { ct:instance-type nfvi:ResourceElement; } + } + leaf-list every { + description "Query for ResourceCollection(s) that contain all of these element(s)"; + type instance-identifier { ct:instance-type nfvi:ResourceElement; } + } + } + } + + grouping common-intent-output { + leaf result { + type enumeration { + enum "ok"; + enum "conflict"; + enum "error"; + } + } + leaf message { type string; } + } + + grouping utilization-output { + list utilization { + key 'timestamp'; + leaf timestamp { type yang:date-and-time; } + leaf count { type int16; } + container capacity { uses nfvi:resource-capacity; } + } + } + + ct:complex-type ResourceCollection { + ct:extends nfvi:ResourceContainer; + ct:abstract true; + + description + "Describes an abstract ResourceCollection data model, which represents + a grouping of capacity and elements available during a given + window in time which must be extended by other resource + collection related models"; + + leaf start { type yang:date-and-time; } + leaf end { type yang:date-and-time; } + + leaf active { + config false; + description + "Provides current state of this record whether it is enabled and within + specified start/end time"; + type boolean; + } + } + + ct:complex-type ResourcePool { + ct:extends ResourceCollection; + + description + "Describes an instance of an active ResourcePool record, which + represents total available capacity and elements from a given + source."; + + leaf source { + type instance-identifier { + ct:instance-type nfvi:ResourceContainer; + require-instance true; + } + mandatory true; + } + + refine elements { + // following 'must' statement applies to each element + // NOTE: just a non-working example for now... + must "boolean(/source/elements/*[@id=id])" { + error-message "One or more of the ResourceElement(s) does not exist in the provider to be reserved"; + } + } + } + + ct:complex-type ResourceReservation { + ct:extends ResourceCollection; + + description + "Describes an instance of an accepted resource reservation request, + created usually as a result of 'create-reservation' request. + + A ResourceReservation is a derived instance of a generic + ResourceCollection which has additional parameters to map the + pool(s) that were referenced to accept this reservation as well + as to track allocations made referencing this reservation. + + Contains the capacities of various resource attributes being + reserved along with any resource elements that are needed to be + available at the time of allocation(s)."; + + reference "OPNFV-PROMISE, Section 3.4.1"; + + leaf created-on { type yang:date-and-time; config false; } + leaf modified-on { type yang:date-and-time; config false; } + + leaf-list pools { + config false; + description + "Provides list of one or more pools that were referenced for providing + the requested resources for this reservation. This is an + important parameter for informing how/where allocation + requests can be issued using this reservation since it is + likely that the total reserved resource capacity/elements are + made availble from multiple sources."; + type instance-identifier { + ct:instance-type ResourcePool; + require-instance true; + } + } + + container remaining { + config false; + description + "Provides visibility into total remaining capacity for this + reservation based on allocations that took effect utilizing + this reservation ID as a reference."; + + uses nfvi:resource-capacity; + } + + leaf-list allocations { + config false; + description + "Reference to a collection of consumed allocations referencing + this reservation."; + type instance-identifier { + ct:instance-type ResourceAllocation; + require-instance true; + } + } + } + + ct:complex-type ResourceAllocation { + ct:extends ResourceCollection; + + description + "A ResourceAllocation record denotes consumption of resources from a + referenced ResourcePool. + + It does not reflect an accepted request but is created to + represent the actual state about the ResourcePool. It is + created once the allocation(s) have successfully taken effect + on the 'source' of the ResourcePool. + + The 'priority' state indicates the classification for dealing + with resource starvation scenarios. Lower priority allocations + will be forcefully terminated to allow for higher priority + allocations to be fulfilled. + + Allocations without reference to an existing reservation will + receive the lowest priority."; + + reference "OPNFV-PROMISE, Section 3.4.3"; + + leaf reservation { + description "Reference to an existing reservation identifier (optional)"; + + type instance-identifier { + ct:instance-type ResourceReservation; + require-instance true; + } + } + + leaf pool { + description "Reference to an existing resource pool from which allocation is drawn"; + + type instance-identifier { + ct:instance-type ResourcePool; + require-instance true; + } + } + + container instance-ref { + config false; + description + "Reference to actual instance identifier of the provider/server for this allocation"; + leaf provider { + type instance-identifier { ct:instance-type ResourceProvider; } + } + leaf server { type yang:uuid; } + } + + leaf priority { + config false; + description + "Reflects current priority level of the allocation according to classification rules"; + type enumeration { + enum "high" { value 1; } + enum "normal" { value 2; } + enum "low" { value 3; } + } + default "normal"; + } + } + + ct:complex-type ResourceFlavor { + description "currently NOT an extension of ResourceElement."; + key "id"; + leaf id { type string; } + leaf name { type string; } + leaf disk { type uint32; units 'GB'; default 0; } + leaf ram { type uint32; units 'MB'; default 0; } + leaf vcpus { type uint16; default 0; } + } + + ct:complex-type ResourceProvider { + ct:extends nfvi:ResourceContainer; + + key "name"; + leaf token { type string; mandatory true; } + + container services { // read-only + config false; + container compute { + leaf endpoint { type inet:uri; } + ct:instance-list flavors { ct:instance-type ResourceFlavor; } + } + } + + leaf-list pools { + config false; + description + "Provides list of one or more pools that are referencing this provider."; + + type instance-identifier { + ct:instance-type ResourcePool; + require-instance true; + } + } + } + + // MAIN CONTAINER + container promise { + + uses resource-utilization { + description "Describes current state info about capacity utilization info"; + + augment "capacity/total" { uses nfvi:resource-capacity; } + augment "capacity/reserved" { uses nfvi:resource-capacity; } + augment "capacity/usage" { uses nfvi:resource-capacity; } + augment "capacity/available" { uses nfvi:resource-capacity; } + } + + ct:instance-list providers { + if-feature multi-provider; + description "Aggregate collection of all registered ResourceProvider instances for Promise resource management service"; + ct:instance-type ResourceProvider; + } + + ct:instance-list pools { + if-feature reservation-service; + description "Aggregate collection of all ResourcePool instances"; + ct:instance-type ResourcePool; + } + + ct:instance-list reservations { + if-feature reservation-service; + description "Aggregate collection of all ResourceReservation instances"; + ct:instance-type ResourceReservation; + } + + ct:instance-list allocations { + description "Aggregate collection of all ResourceAllocation instances"; + ct:instance-type ResourceAllocation; + } + + container policy { + container reservation { + leaf max-future-start-range { + description + "Enforce reservation request 'start' time is within allowed range from now"; + type uint16 { range 0..365; } + units "days"; + } + leaf max-future-end-range { + description + "Enforce reservation request 'end' time is within allowed range from now"; + type uint16 { range 0..365; } + units "days"; + } + leaf max-duration { + description + "Enforce reservation duration (end-start) does not exceed specified threshold"; + type uint16; + units "hours"; + default 8760; // for now cap it at max one year as default + } + leaf expiry { + description + "Duration in minutes from start when unallocated reserved resources + will be released back into the pool"; + type uint32; + units "minutes"; + } + } + } + } + + //------------------- + // INTENT INTERFACE + //------------------- + + // RESERVATION INTENTS + rpc create-reservation { + if-feature reservation-service; + description "Make a request to the reservation system to reserve resources"; + input { + uses resource-usage-request; + } + output { + uses common-intent-output; + leaf reservation-id { + type instance-identifier { ct:instance-type ResourceReservation; } + } + } + } + + rpc update-reservation { + description "Update reservation details for an existing reservation"; + input { + leaf reservation-id { + type instance-identifier { + ct:instance-type ResourceReservation; + require-instance true; + } + mandatory true; + } + uses resource-usage-request; + } + output { + uses common-intent-output; + } + } + + rpc cancel-reservation { + description "Cancel the reservation and be a good steward"; + input { + leaf reservation-id { + type instance-identifier { ct:instance-type ResourceReservation; } + mandatory true; + } + } + output { + uses common-intent-output; + } + } + + rpc query-reservation { + if-feature reservation-service; + description "Query the reservation system to return matching reservation(s)"; + input { + leaf zone { type instance-identifier { ct:instance-type nfvi:AvailabilityZone; } } + uses query-resource-collection; + } + output { + leaf-list reservations { type instance-identifier { ct:instance-type ResourceReservation; } } + uses utilization-output; + } + } + + // CAPACITY INTENTS + rpc increase-capacity { + description "Increase total capacity for the reservation system between a window in time"; + input { + uses temporal-resource-collection; + leaf source { + type instance-identifier { + ct:instance-type nfvi:ResourceContainer; + } + } + } + output { + uses common-intent-output; + leaf pool-id { + type instance-identifier { ct:instance-type ResourcePool; } + } + } + } + + rpc decrease-capacity { + description "Decrease total capacity for the reservation system between a window in time"; + input { + uses temporal-resource-collection; + leaf source { + type instance-identifier { + ct:instance-type nfvi:ResourceContainer; + } + } + } + output { + uses common-intent-output; + leaf pool-id { + type instance-identifier { ct:instance-type ResourcePool; } + } + } + } + + rpc query-capacity { + description "Check available capacity information about a specified resource collection"; + input { + leaf capacity { + type enumeration { + enum 'total'; + enum 'reserved'; + enum 'usage'; + enum 'available'; + } + default 'available'; + } + leaf zone { type instance-identifier { ct:instance-type nfvi:AvailabilityZone; } } + uses query-resource-collection; + // TBD: additional parameters for query-capacity + } + output { + leaf-list collections { type instance-identifier { ct:instance-type ResourceCollection; } } + uses utilization-output; + } + } + + // ALLOCATION INTENTS (should go into VIM module in the future) + rpc create-instance { + description "Create an instance of specified resource(s) utilizing capacity from the pool"; + input { + leaf provider-id { + if-feature multi-provider; + type instance-identifier { ct:instance-type ResourceProvider; require-instance true; } + } + leaf name { type string; mandatory true; } + leaf image { + type reference-identifier; + mandatory true; + } + leaf flavor { + type reference-identifier; + mandatory true; + } + leaf-list networks { + type reference-identifier; + description "optional, will assign default network if not provided"; + } + + // TODO: consider supporting a template-id (such as HEAT) for more complex instantiation + + leaf reservation-id { + type instance-identifier { ct:instance-type ResourceReservation; require-instance true; } + } + } + output { + uses common-intent-output; + leaf instance-id { + type instance-identifier { ct:instance-type ResourceAllocation; } + } + } + } + + rpc destroy-instance { + description "Destroy an instance of resource utilization and release it back to the pool"; + input { + leaf instance-id { + type instance-identifier { ct:instance-type ResourceAllocation; require-instance true; } + } + } + output { + uses common-intent-output; + } + } + + // PROVIDER INTENTS (should go into VIM module in the future) + rpc add-provider { + description "Register a new resource provider into reservation system"; + input { + leaf provider-type { + description "Select a specific resource provider type"; + mandatory true; + type enumeration { + enum openstack; + enum hp; + enum rackspace; + enum amazon { + status planned; + } + enum joyent { + status planned; + } + enum azure { + status planned; + } + } + default openstack; + } + uses acm:access-credentials { + refine strategy { + default keystone; + } + refine endpoint { + default "http://localhost:5000/v2.0"; + } + } + container tenant { + leaf id { type string; } + leaf name { type string; } + } + } + output { + uses common-intent-output; + leaf provider-id { + type instance-identifier { ct:instance-type ResourceProvider; } + } + } + } + + // TODO... + notification reservation-event; + notification capacity-event; + notification allocation-event; +} diff --git a/source/spec/openstack-intents.coffee b/source/spec/openstack-intents.coffee new file mode 100644 index 0000000..f1a10d2 --- /dev/null +++ b/source/spec/openstack-intents.coffee @@ -0,0 +1,6 @@ +request = require 'superagent' + +module.exports = + 'create-tenant': + (input, output, done) -> + # TODO - this requires OS-KSADM extension diff --git a/source/spec/promise-intents.coffee b/source/spec/promise-intents.coffee new file mode 100644 index 0000000..6ad3ae7 --- /dev/null +++ b/source/spec/promise-intents.coffee @@ -0,0 +1,360 @@ +module.exports = + 'create-reservation': + (input, output, done) -> + # 1. create the reservation record (empty) + reservation = @create 'ResourceReservation' + reservations = @access 'promise.reservations' + + # 2. update the record with requested input + reservation.invoke 'update', input.get() + .then (res) -> + # 3. save the record and add to list + res.save() + .then -> + reservations.push res + output.set result: 'ok', message: 'reservation request accepted' + output.set 'reservation-id', res.id + done() + .catch (err) -> + output.set result: 'error', message: err + done() + .catch (err) -> + output.set result: 'conflict', message: err + done() + + 'query-reservation': + (input, output, done) -> + query = input.get() + query.capacity = 'reserved' + @invoke 'query-capacity', query + .then (res) -> + output.set 'reservations', res.get 'collections' + output.set 'utilization', res.get 'utilization' + done() + .catch (e) -> done e + + 'update-reservation': + (input, output, done) -> + # TODO: we shouldn't need this... need to check why leaf mandatory: true not being enforced + unless (input.get 'reservation-id')? + output.set result: 'error', message: "must provide 'reservation-id' parameter" + return done() + + # 1. find the reservation + reservation = @find 'ResourceReservation', input.get 'reservation-id' + unless reservation? + output.set result: 'error', message: 'no reservation found for specified identifier' + return done() + + # 2. update the record with requested input + reservation.invoke 'update', input.get() + .then (res) -> + # 3. save the updated record + res.save() + .then -> + output.set result: 'ok', message: 'reservation update successful' + done() + .catch (err) -> + output.set result: 'error', message: err + done() + .catch (err) -> + output.set result: 'conflict', message: err + done() + + 'cancel-reservation': + (input, output, done) -> + # 1. find the reservation + reservation = @find 'ResourceReservation', input.get 'reservation-id' + unless reservation? + output.set result: 'error', message: 'no reservation found for specified identifier' + return done() + + # 2. destroy all traces of this reservation + reservation.destroy() + .then => + (@access 'promise.reservations').remove reservation.id + output.set 'result', 'ok' + output.set 'message', 'reservation canceled' + done() + .catch (e) -> + output.set 'result', 'error' + output.set 'message', e + done() + + 'query-capacity': + (input, output, done) -> + # 1. we gather up all collections that match the specified window + window = input.get 'window' + metric = input.get 'capacity' + + collections = switch metric + when 'total' then [ 'ResourcePool' ] + when 'reserved' then [ 'ResourceReservation' ] + when 'usage' then [ 'ResourceAllocation' ] + when 'available' then [ 'ResourcePool', 'ResourceReservation', 'ResourceAllocation' ] + + matches = collections.reduce ((a, name) => + res = @find name, + start: (value) -> (not window.end?) or (new Date value) <= (new Date window.end) + end: (value) -> (not window.start?) or (new Date value) >= (new Date window.start) + enabled: true + a.concat res... + ), [] + + if window.scope is 'exclusive' + # yes, we CAN query filter in one shot above but this makes logic cleaner... + matches = matches.where + start: (value) -> (not window.start?) or (new Date value) >= (new Date window.start) + end: (value) -> (not window.end?) or (new Date value) <= (new Date window.end) + + # exclude any identifiers specified + matches = matches.without id: (input.get 'without') + + if metric is 'available' + # excludes allocations with reservation property set (to prevent double count) + matches = matches.without reservation: (v) -> v? + + output.set 'collections', matches + unless (input.get 'show-utilization') is true + return done() + + # 2. we calculate the deltas based on start/end times of each match item + deltas = matches.reduce ((a, entry) -> + b = entry.get() + b.end ?= 'infiniteT' + [ skey, ekey ] = [ (b.start.split 'T')[0], (b.end.split 'T')[0] ] + a[skey] ?= count: 0, capacity: {} + a[ekey] ?= count: 0, capacity: {} + a[skey].count += 1 + a[ekey].count -= 1 + + for k, v of b.capacity when v? + a[skey].capacity[k] ?= 0 + a[ekey].capacity[k] ?= 0 + if entry.name is 'ResourcePool' + a[skey].capacity[k] += v + a[ekey].capacity[k] -= v + else + a[skey].capacity[k] -= v + a[ekey].capacity[k] += v + return a + ), {} + + # 3. we then sort the timestamps and aggregate the deltas + last = count: 0, capacity: {} + usages = for timestamp in Object.keys(deltas).sort() when timestamp isnt 'infinite' + entry = deltas[timestamp] + entry.timestamp = (new Date timestamp).toJSON() + entry.count += last.count + for k, v of entry.capacity + entry.capacity[k] += (last.capacity[k] ? 0) + last = entry + entry + + output.set 'utilization', usages + done() + + 'increase-capacity': + (input, output, done) -> + pool = @create 'ResourcePool', input.get() + pool.save() + .then (res) => + (@access 'promise.pools').push res + output.set result: 'ok', message: 'capacity increase successful' + output.set 'pool-id', res.id + done() + .catch (e) -> + output.set result: 'error', message: e + done() + + 'decrease-capacity': + (input, output, done) -> + request = input.get() + for k, v of request.capacity + request.capacity[k] = -v + pool = @create 'ResourcePool', request + pool.save() + .then (res) => + (@access 'promise.pools').push res + output.set result: 'ok', message: 'capacity decrease successful' + output.set 'pool-id', res.id + done() + .catch (e) -> + output.set result: 'error', message: e + done() + + # TEMPORARY (should go into VIM-specific module) + 'create-instance': + (input, output, done) -> + pid = input.get 'provider-id' + if pid? + provider = @find 'ResourceProvider', pid + unless provider? + output.set result: 'error', message: "no matching provider found for specified identifier: #{pid}" + return done() + else + provider = (@find 'ResourceProvider')[0] + unless provider? + output.set result: 'error', message: "no available provider found for create-instance" + return done() + + # calculate required capacity based on 'flavor' and other params + flavor = provider.access "services.compute.flavors.#{input.get 'flavor'}" + unless flavor? + output.set result: 'error', message: "no such flavor found for specified identifier: #{pid}" + return done() + + required = + instances: 1 + cores: flavor.get 'vcpus' + ram: flavor.get 'ram' + gigabytes: flavor.get 'disk' + + rid = input.get 'reservation-id' + if rid? + reservation = @find 'ResourceReservation', rid + unless reservation? + output.set result: 'error', message: 'no valid reservation found for specified identifier' + return done() + unless (reservation.get 'active') is true + output.set result: 'error', message: "reservation is currently not active" + return done() + available = reservation.get 'remaining' + else + available = @get 'promise.capacity.available' + + # TODO: need to verify whether 'provider' associated with this 'reservation' + + for k, v of required when v? and !!v + unless available[k] >= v + output.set result: 'conflict', message: "required #{k}=#{v} exceeds available #{available[k]}" + return done() + + @create 'ResourceAllocation', + reservation: rid + capacity: required + .save() + .then (instance) => + url = provider.get 'services.compute.endpoint' + payload = + server: + name: input.get 'name' + imageRef: input.get 'image' + flavorRef: input.get 'flavor' + networks = (input.get 'networks').filter (x) -> x? and !!x + if networks.length > 0 + payload.server.networks = networks.map (x) -> uuid: x + + request = @parent.require 'superagent' + request + .post "#{url}/servers" + .send payload + .set 'X-Auth-Token', provider.get 'token' + .set 'Accept', 'application/json' + .end (err, res) => + if err? or !res.ok + instance.destroy() + #console.error err + return done res.error + #console.log JSON.stringify res.body, null, 2 + instance.set 'instance-ref', + provider: provider + server: res.body.server.id + (@access 'promise.allocations').push instance + output.set result: 'ok', message: 'create-instance request accepted' + output.set 'instance-id', instance.id + done() + return instance + .catch (err) -> + output.set result: 'error', mesage: err + done() + + 'destroy-instance': + (input, output, done) -> + # 1. find the instance + instance = @find 'ResourceAllocation', input.get 'instance-id' + unless instance? + output.set result: 'error', message: 'no allocation found for specified identifier' + return done() + + # 2. destroy all traces of this instance + instance.destroy() + .then => + # always remove internally + (@access 'promise.allocations').remove instance.id + ref = instance.get 'instance-ref' + provider = (@access "promise.providers.#{ref.provider}") + url = provider.get 'services.compute.endpoint' + request = @parent.require 'superagent' + request + .delete "#{url}/servers/#{ref.server}" + .set 'X-Auth-Token', provider.get 'token' + .set 'Accept', 'application/json' + .end (err, res) => + if err? or !res.ok + console.error err + return done res.error + output.set 'result', 'ok' + output.set 'message', 'instance destroyed and resource released back to pool' + done() + return instance + .catch (e) -> + output.set 'result', 'error' + output.set 'message', e + done() + + # TEMPORARY (should go into VIM-specific module) + 'add-provider': + (input, output, done) -> + app = @parent + request = app.require 'superagent' + + payload = switch input.get 'provider-type' + when 'openstack' + auth: + tenantId: input.get 'tenant.id' + tenantName: input.get 'tenant.name' + passwordCredentials: input.get 'username', 'password' + + unless payload? + return done 'Sorry, only openstack supported at this time' + + url = input.get 'endpoint' + switch input.get 'strategy' + when 'keystone', 'oauth' + url += '/tokens' unless /\/tokens$/.test url + + providers = @access 'promise.providers' + request + .post url + .send payload + .set 'Accept', 'application/json' + .end (err, res) => + if err? or !res.ok then return done res.error + #console.log JSON.stringify res.body, null, 2 + access = res.body.access + provider = @create 'ResourceProvider', + token: access?.token?.id + name: access?.token?.tenant?.name + provider.invoke 'update', access.serviceCatalog + .then (res) -> + res.save() + .then -> + providers.push res + output.set 'result', 'ok' + output.set 'provider-id', res.id + done() + .catch (err) -> + output.set 'error', message: err + done() + .catch (err) -> + output.set 'error', message: err + done() + + # @using 'mano', -> + # @invoke 'add-provider', (input.get 'endpoint', 'region', 'username', 'password') + # .then (res) => + # (@access 'promise.providers').push res + # output.set 'result', 'ok' + # output.set 'provider-id', res.id + # done() diff --git a/source/spec/promise-module.coffee b/source/spec/promise-module.coffee new file mode 100644 index 0000000..3eea482 --- /dev/null +++ b/source/spec/promise-module.coffee @@ -0,0 +1,72 @@ +module.exports = + '/opnfv-promise/promise/capacity/total': (prev) -> + @computed (-> + combine = (a, b) -> + for k, v of b.capacity when v? + a[k] ?= 0 + a[k] += v + return a + (@parent.get 'pools') + .filter (entry) -> entry.active is true + .reduce combine, {} + ), type: prev + + '/opnfv-promise/promise/capacity/reserved', (prev) -> + @computed (-> + combine = (a, b) -> + for k, v of b.capacity when v? + a[k] ?= 0 + a[k] += v + return a + (@parent.get 'reservations') + .filter (entry) -> entry.active is true + .reduce combine, {} + ), type: prev + + # rebind to be a computed property + '/opnfv-promise/promise/capacity/usage': (prev) -> + @computed (-> + combine = (a, b) -> + for k, v of b.capacity when v? + a[k] ?= 0 + a[k] += v + return a + (@parent.get 'allocations') + .filter (entry) -> entry.active is true + .reduce combine, {} + ), type: prev + + # rebind to be a computed property + '/opnfv-promise/promise/capacity/available': (prev) -> + @computed (-> + total = @get 'total' + reserved = @get 'reserved' + usage = @get 'usage' + for k, v of total when v? + total[k] -= reserved[k] if reserved[k]? + total[k] -= usage[k] if usage[k]? + total + ), type: prev + + '/opnfv-promise/create-reservation': + (input, output, done) -> + # 1. create the reservation record (empty) + reservation = @create 'ResourceReservation' + reservations = @access 'promise.reservations' + + # 2. update the record with requested input + reservation.invoke 'update', input.get() + .then (res) -> + # 3. save the record and add to list + res.save() + .then -> + reservations.push res + output.set result: 'ok', message: 'reservation request accepted' + output.set 'reservation-id', res.id + done() + .catch (err) -> + output.set result: 'error', message: err + done() + .catch (err) -> + output.set result: 'conflict', message: err + done() diff --git a/source/test/mocha.opts b/source/test/mocha.opts new file mode 100644 index 0000000..1e154ee --- /dev/null +++ b/source/test/mocha.opts @@ -0,0 +1,2 @@ +--require should +--compilers coffee:coffee-script/register diff --git a/source/test/promise-intents.coffee b/source/test/promise-intents.coffee new file mode 100644 index 0000000..8e6286b --- /dev/null +++ b/source/test/promise-intents.coffee @@ -0,0 +1,437 @@ +config = require 'config' +assert = require 'assert' +forge = require 'yangforge' +app = forge.load '!yaml ../promise.yaml', async: false, pkgdir: __dirname + +# this is javascript promise framework and not related to opnfv-promise +promise = require 'promise' + +if process.env.DEBUG + debug = console.log +else + debug = -> + +# in the future with YF 0.12.x +# app = forge.load('..').build('test') +# app.set config +# app.use 'proxy', target: x.x.x.x:5050, interface: 'restjson' + +describe "promise", -> + before -> + # ensure we have valid OpenStack environment to test against + try + config.get 'openstack.auth.endpoint' + catch e + throw new Error "missing OpenStack environmental variables" + + + # below 'provider' is used across test suites + provider = undefined + + # Test Scenario 00 (FUTURE) + # describe "prepare OpenStack for testing", -> + # before (done) -> + # # ensure we have valid OpenStack environment to test against + # try + # config.get 'openstack.auth.url' + # catch e + # throw new Error "missing OpenStack environmental variables" + + # os = forge.load '!yaml ../openstack.yaml', async: false, pkgdir: __dirname + # app.attach 'openstack', os.access 'openstack' + # app.set config + + # describe "authenticate", -> + # it "should retrieve available service catalog", (done) -> + # app.access('openstack').invoke 'authenticate' + # .then (res) -> + + # done() + # .catch (err) -> done err + + # describe "create-tenant", -> + # # create a new tenant for testing purposes + + # describe "upload-image", -> + # # upload a new test image + + + + # Test Scenario 01 + describe "register OpenStack into resource pool", -> + pool = undefined + + # TC-01 + describe "add-provider", -> + it "should add a new OpenStack provider without error", (done) -> + @timeout 5000 + + auth = config.get 'openstack.auth' + auth['provider-type'] = 'openstack' + + app.access('opnfv-promise').invoke 'add-provider', auth + .then (res) -> + res.get('result').should.equal 'ok' + provider = id: res.get('provider-id') + # HACK - we delay by a second to allow time for discovering capacity and flavors + setTimeout done, 1000 + .catch (err) -> done err + + it "should update promise.providers with a new entry", -> + app.get('opnfv-promise.promise.providers').should.have.length(1) + + it "should contain a new ResourceProvider record in the store", -> + assert provider?.id?, "unable to check without ID" + provider = app.access('opnfv-promise').find('ResourceProvider', provider.id) + assert provider? + + # TC-02 + describe "increase-capacity", -> + it "should add more capacity to the reservation service without error", (done) -> + app.access('opnfv-promise').invoke 'increase-capacity', + source: provider + capacity: + cores: 20 + ram: 51200 + instances: 10 + addresses: 10 + .then (res) -> + res.get('result').should.equal 'ok' + pool = id: res.get('pool-id') + done() + .catch (err) -> done err + + it "should update promise.pools with a new entry", -> + app.get('opnfv-promise.promise.pools').should.have.length(1) + + it "should contain a ResourcePool record in the store", -> + assert pool?.id?, "unable to check without ID" + pool = app.access('opnfv-promise').find('ResourcePool', pool.id) + assert pool? + + # TC-03 + describe "query-capacity", -> + it "should report total collections and utilizations", (done) -> + app.access('opnfv-promise').invoke 'query-capacity', + capacity: 'total' + .then (res) -> + res.get('collections').should.be.Array + res.get('collections').length.should.be.above(0) + res.get('utilization').should.be.Array + res.get('utilization').length.should.be.above(0) + done() + .catch (err) -> done err + + it "should contain newly added capacity pool", (done) -> + app.access('opnfv-promise').invoke 'query-capacity', + capacity: 'total' + .then (res) -> + res.get('collections').should.containEql "ResourcePool:#{pool.id}" + done() + .catch (err) -> done err + + # Test Scenario 02 + describe "allocation without reservation", -> + + # TC-04 + describe "create-instance", -> + allocation = undefined + instance_id = undefined + + before -> + # XXX - need to determine image and flavor to use in the given provider for this test + assert provider?, + "unable to execute without registered 'provider'" + + it "should create a new server in target provider without error", (done) -> + @timeout 5000 + test = config.get 'openstack.test' + app.access('opnfv-promise').invoke 'create-instance', + 'provider-id': provider.id + name: 'promise-test-no-reservation' + image: test.image + flavor: test.flavor + networks: [ test.network ] + .then (res) -> + debug res.get() + res.get('result').should.equal 'ok' + instance_id = res.get('instance-id') + done() + .catch (err) -> done err + + it "should update promise.allocations with a new entry", -> + app.get('opnfv-promise.promise.allocations').length.should.be.above(0) + + it "should contain a new ResourceAllocation record in the store", -> + assert instance_id?, "unable to check without ID" + allocation = app.access('opnfv-promise').find('ResourceAllocation', instance_id) + assert allocation? + + it "should reference the created server ID from the provider", -> + assert allocation?, "unable to check without record" + allocation.get('instance-ref').should.have.property('provider') + allocation.get('instance-ref').should.have.property('server') + + it "should have low priority state", -> + assert allocation?, "unable to check without record" + allocation.get('priority').should.equal 'low' + + # Test Scenario 03 + describe "allocation using reservation for immediate use", -> + reservation = undefined + + # TC-05 + describe "create-reservation", -> + it "should create reservation record (no start/end) without error", (done) -> + app.access('opnfv-promise').invoke 'create-reservation', + capacity: + cores: 5 + ram: 25600 + addresses: 3 + instances: 3 + .then (res) -> + res.get('result').should.equal 'ok' + reservation = id: res.get('reservation-id') + done() + .catch (err) -> done err + + it "should update promise.reservations with a new entry", -> + app.get('opnfv-promise.promise.reservations').length.should.be.above(0) + + it "should contain a new ResourceReservation record in the store", -> + assert reservation?.id?, "unable to check without ID" + reservation = app.access('opnfv-promise').find('ResourceReservation', reservation.id) + assert reservation? + + # TC-06 + describe "create-instance", -> + allocation = undefined + + before -> + assert provider?, + "unable to execute without registered 'provider'" + assert reservation?, + "unable to execute without valid reservation record" + + it "should create a new server in target provider (with reservation) without error", (done) -> + @timeout 5000 + test = config.get 'openstack.test' + app.access('opnfv-promise').invoke 'create-instance', + 'provider-id': provider.id + name: 'promise-test-reservation' + image: test.image + flavor: test.flavor + networks: [ test.network ] + 'reservation-id': reservation.id + .then (res) -> + debug res.get() + res.get('result').should.equal 'ok' + allocation = id: res.get('instance-id') + done() + .catch (err) -> done err + + it "should contain a new ResourceAllocation record in the store", -> + assert allocation?.id?, "unable to check without ID" + allocation = app.access('opnfv-promise').find('ResourceAllocation', allocation.id) + assert allocation? + + it "should be referenced in the reservation record", -> + assert reservation? and allocation?, "unable to check without records" + reservation.get('allocations').should.containEql allocation.id + + it "should have high priority state", -> + assert allocation?, "unable to check without record" + allocation.get('priority').should.equal 'high' + + # Test Scenario 04 + describe "reservation for future use", -> + reservation = undefined + start = new Date + end = new Date + # 7 days in the future + start.setTime (start.getTime() + 7*60*60*1000) + # 8 days in the future + end.setTime (end.getTime() + 8*60*60*1000) + + # TC-07 + describe "create-reservation", -> + it "should create reservation record (for future) without error", (done) -> + app.access('opnfv-promise').invoke 'create-reservation', + start: start.toJSON() + end: end.toJSON() + capacity: + cores: 1 + ram: 12800 + addresses: 1 + instances: 1 + .then (res) -> + res.get('result').should.equal 'ok' + reservation = id: res.get('reservation-id') + done() + .catch (err) -> done err + + it "should update promise.reservations with a new entry", -> + app.get('opnfv-promise.promise.reservations').length.should.be.above(0) + + it "should contain a new ResourceReservation record in the store", -> + assert reservation?.id?, "unable to check without ID" + reservation = app.access('opnfv-promise').find('ResourceReservation', reservation.id) + assert reservation? + + # TC-08 + describe "query-reservation", -> + it "should contain newly created future reservation", (done) -> + app.access('opnfv-promise').invoke 'query-reservation', + window: + start: start.toJSON() + end: end.toJSON() + .then (res) -> + res.get('reservations').should.containEql reservation.id + done() + .catch (err) -> done err + + # TC-09 + describe "update-reservation", -> + it "should modify existing reservation without error", (done) -> + app.access('opnfv-promise').invoke 'update-reservation', + 'reservation-id': reservation.id + capacity: + cores: 3 + ram: 12800 + addresses: 2 + instances: 2 + .then (res) -> + res.get('result').should.equal 'ok' + done() + .catch (err) -> done err + + # TC-10 + describe "cancel-reservation", -> + it "should modify existing reservation without error", (done) -> + app.access('opnfv-promise').invoke 'cancel-reservation', + 'reservation-id': reservation.id + .then (res) -> + res.get('result').should.equal 'ok' + done() + .catch (err) -> done err + + it "should no longer contain record of the deleted reservation", -> + assert reservation?.id?, "unable to check without ID" + reservation = app.access('opnfv-promise').find('ResourceReservation', reservation.id) + assert not reservation? + + # Test Scenario 05 + describe "capacity planning", -> + + # TC-11 + describe "decrease-capacity", -> + start = new Date + end = new Date + # 30 days in the future + start.setTime (start.getTime() + 30*60*60*1000) + # 45 days in the future + end.setTime (end.getTime() + 45*60*60*1000) + + it "should decrease available capacity from a provider in the future", (done) -> + app.access('opnfv-promise').invoke 'decrease-capacity', + source: provider + capacity: + cores: 5 + ram: 17920 + instances: 5 + start: start.toJSON() + end: end.toJSON() + .then (res) -> + res.get('result').should.equal 'ok' + done() + .catch (err) -> done err + + # TC-12 + describe "increase-capacity", -> + start = new Date + end = new Date + # 14 days in the future + start.setTime (start.getTime() + 14*60*60*1000) + # 21 days in the future + end.setTime (end.getTime() + 21*60*60*1000) + + it "should increase available capacity from a provider in the future", (done) -> + app.access('opnfv-promise').invoke 'decrease-capacity', + source: provider + capacity: + cores: 1 + ram: 3584 + instances: 1 + start: start.toJSON() + end: end.toJSON() + .then (res) -> + res.get('result').should.equal 'ok' + done() + .catch (err) -> done err + + # TC-13 (Should improve this TC) + describe "query-capacity", -> + it "should report available collections and utilizations", (done) -> + app.access('opnfv-promise').invoke 'query-capacity', + capacity: 'available' + .then (res) -> + res.get('collections').should.be.Array + res.get('collections').length.should.be.above(0) + res.get('utilization').should.be.Array + res.get('utilization').length.should.be.above(0) + done() + .catch (err) -> done err + + # Test Scenario 06 + describe "reservation with conflict", -> + # TC-14 + describe "create-reservation", -> + it "should fail to create immediate reservation record with proper error", (done) -> + app.access('opnfv-promise').invoke 'create-reservation', + capacity: + cores: 5 + ram: 17920 + instances: 10 + .then (res) -> + res.get('result').should.equal 'conflict' + done() + .catch (err) -> done err + + it "should fail to create future reservation record with proper error", (done) -> + start = new Date + # 30 days in the future + start.setTime (start.getTime() + 30*60*60*1000) + + app.access('opnfv-promise').invoke 'create-reservation', + capacity: + cores: 5 + ram: 17920 + instances: 10 + start: start.toJSON() + .then (res) -> + res.get('result').should.equal 'conflict' + done() + .catch (err) -> done err + + # Test Scenario 07 + describe "cleanup test allocations", -> + allocations = undefined + before -> + allocations = app.get('opnfv-promise.promise.allocations') + debug provider.get() + debug allocations + allocations.length.should.be.above(0) + + describe "destroy-instance", -> + it "should successfully destroy all allocations", (done) -> + @timeout 5000 + promises = allocations.map (x) -> + app.access('opnfv-promise').invoke 'destroy-instance', + 'instance-id': x.id + promise.all promises + .then (res) -> + res.forEach (x) -> + debug x.get() + x.get('result').should.equal 'ok' + done() + .catch (err) -> done err -- cgit 1.2.3-korg