--- /dev/null
+.idea/
+*.pyc
+.DS_Store
+
+.coverage
+htmlcov
+coverage.xml
+nose2-junit.xml
+
--- /dev/null
+[gerrit]
+host=gerrit.opencord.org
+port=29418
+project=olt-service.git
+defaultremote=origin
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# docker build -t xosproject/volt-synchronizer:candidate -f Dockerfile.synchronizer .
+
+# xosproject/volt-synchronizer
+
+FROM cachengo/xos-synchronizer-base:2.1.25
+
+COPY xos/synchronizer /opt/xos/synchronizers/volt
+COPY VERSION /opt/xos/synchronizers/volt/
+COPY samples/onu_activate_event.py /opt/xos/synchronizers/volt/
+
+WORKDIR "/opt/xos/synchronizers/volt"
+
+# Label image
+ARG org_label_schema_schema_version=1.0
+ARG org_label_schema_name=volt-synchronizer
+ARG org_label_schema_version=unknown
+ARG org_label_schema_vcs_url=unknown
+ARG org_label_schema_vcs_ref=unknown
+ARG org_label_schema_build_date=unknown
+ARG org_opencord_vcs_commit_date=unknown
+ARG org_opencord_component_chameleon_version=unknown
+ARG org_opencord_component_chameleon_vcs_url=unknown
+ARG org_opencord_component_chameleon_vcs_ref=unknown
+ARG org_opencord_component_xos_version=unknown
+ARG org_opencord_component_xos_vcs_url=unknown
+ARG org_opencord_component_xos_vcs_ref=unknown
+
+LABEL org.label-schema.schema-version=$org_label_schema_schema_version \
+ org.label-schema.name=$org_label_schema_name \
+ org.label-schema.version=$org_label_schema_version \
+ org.label-schema.vcs-url=$org_label_schema_vcs_url \
+ org.label-schema.vcs-ref=$org_label_schema_vcs_ref \
+ org.label-schema.build-date=$org_label_schema_build_date \
+ org.opencord.vcs-commit-date=$org_opencord_vcs_commit_date \
+ org.opencord.component.chameleon.version=$org_opencord_component_chameleon_version \
+ org.opencord.component.chameleon.vcs-url=$org_opencord_component_chameleon_vcs_url \
+ org.opencord.component.chameleon.vcs-ref=$org_opencord_component_chameleon_vcs_ref \
+ org.opencord.component.xos.version=$org_opencord_component_xos_version \
+ org.opencord.component.xos.vcs-url=$org_opencord_component_xos_vcs_url \
+ org.opencord.component.xos.vcs-ref=$org_opencord_component_xos_vcs_ref
+
+CMD ["/usr/bin/python", "/opt/xos/synchronizers/volt/volt-synchronizer.py"]
--- /dev/null
+pipeline {
+ agent any
+ stages {
+ stage('Build') {
+ parallel {
+ stage('Build aarch64') {
+ agent {
+ node {
+ label 'aarch64'
+ }
+
+ }
+ steps {
+ withDockerRegistry([ credentialsId: "fcf9c294-b8a9-4f7e-87d6-d0446f712411", url: "https://index.docker.io/v1/" ]) {
+ sh 'ci_scripts/push_containers.sh'
+ }
+ }
+ }
+ stage('Build x86') {
+ agent {
+ node {
+ label 'x86_64'
+ }
+
+ }
+ steps {
+ withDockerRegistry([ credentialsId: "fcf9c294-b8a9-4f7e-87d6-d0446f712411", url: "https://index.docker.io/v1/" ]) {
+ sh 'ci_scripts/push_containers.sh'
+ }
+ }
+ }
+ }
+ }
+ stage('Push Manifest') {
+ steps {
+ withDockerRegistry([ credentialsId: "fcf9c294-b8a9-4f7e-87d6-d0446f712411", url: "https://index.docker.io/v1/" ]) {
+ sh 'ci_scripts/push_manifest.sh'
+ }
+ }
+ }
+ }
+}
--- /dev/null
+
+ 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 2016 Open Networking Foundation
+
+ 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.
--- /dev/null
+
+ISVENV := $(shell python -c 'import sys; print ("1" if hasattr(sys, "real_prefix") else "0")')
+
+build:
+ docker build -t xosproject/volt-synchronizer:candidate -f xos/synchronizer/Dockerfile.synchronizer ./xos/synchronizer/
+
+test:
+ifeq ($(ISVENV), 1)
+ pip install requests-mock
+ pushd xos/synchronizer/steps/; nosetests test_sync_olt_device.py; popd;
+else
+ @echo "Please activate the virtualenv and the required libraries, you can do that using the 'scripts/setup_venv.sh' tool in the 'xos' repo"
+endif
--- /dev/null
+# vOLT
+
+This repositoritory contains the XOS service that is responsible for the integration with VOLTHA.
+
+At the moment the `RCORDService` assume that this service (or a service exposing the same APIs) is sitting after it in the service chain.
+
+## ONU activation discovery
+
+This service will listen for events on the kafka bus, in the topic `onu.events`
+
+The events this service will react to are:
+- onu activated
+
+### ONU Activation policy
+
+We assume that:
+- the `vOLTService` service is a subscriber of `MyOssService` via `ServiceDependency`
+- `MyOssService` has `type = oss`
+- `MyOssService` expose and API named `validate_onu`
+ - the `validate_onu` API will be responsible to create a subscriber in XOS
+
+If no OSS Service is found in between the providers of `vOLTService` no action is taken as a consequence of an event.
--- /dev/null
+export IMAGE_TAG=$(cat VERSION)
+export AARCH=`uname -m`
+export IMAGE_NAME=volt-synchronizer
+
+docker build -t cachengo/$IMAGE_NAME-$AARCH:$IMAGE_TAG -f Dockerfile.synchronizer .
+docker push cachengo/$IMAGE_NAME-$AARCH:$IMAGE_TAG
--- /dev/null
+export IMAGE_TAG=$(cat VERSION)
+export AARCH=`uname -m`
+export IMAGE_NAME=volt-synchronizer
+export DOCKER_CLI_EXPERIMENTAL=enabled
+
+docker manifest create --amend cachengo/$IMAGE_NAME:$IMAGE_TAG cachengo/$IMAGE_NAME-x86_64:$IMAGE_TAG cachengo/$IMAGE_NAME-aarch64:$IMAGE_TAG
+docker manifest push cachengo/$IMAGE_NAME:$IMAGE_TAG
--- /dev/null
+# vOLT Service
+
+The `vOLTService` is responsible to configure the access network in RCORD and it
+does that leveraging `VOLTHA`.
+
+## vOLT Modeling
+
+Here is an image describing the models that compose this service,
+followed by a brief description of any of them.
+For a full reference on these models, please take a look at their
+[xProto](https://github.com/opencord/olt-service/blob/master/xos/synchronizer/models/volt.xproto)
+representation.
+
+
+
+**vOLTService**
+
+Contains the information that the synchronizer needs to access `VOLTHA` and
+`ONOS-VOLTHA`
+
+**vOLTServiceInstance**
+
+Represent a subscriber in the service chain
+
+**OLTDevice**
+
+Contains the information needed to pre-provision and activate an OLT device in
+VOLTHA
+
+**PONPort, NNIPort, ONUDevice**
+These models contain informations about the respective components of the PON
+network. In an RCORD deployment these models are pulled from VOLTHA to keep an
+inventory of the system.
+
+## Synchronizations steps
+
+### Push steps
+
+There are two top-down steps in this service:
+
+- `SyncOLTDevice` to pre-provision and enable OLT devices
+- `SyncVOLTServiceInstance` to add the subscriber in `ONOS-VOLTHA`
+
+### Pull steps
+
+The vOLT synchronizer is currently pulling `OLTDevice`, `PONPort`, `NNIPort` and
+`ONUDevices` from `VOLTHA`.
+
+### Event steps
+
+The vOLT synchronizer is listening over the kafka bus for events in the `onu.events`
+topic.
+
+#### ONU Activate event
+
+Event format:
+
+```json
+{
+ "status": "activated",
+ "serial_number": "BRCM1234",
+ "uni_port_id": 16,
+ "of_dpid": "of:109299321"
+}
+```
+
+When an event is received:
+
+- the `vOLTService` checks if in between is provider services there is one with `kind = oss`
+- It calls the `validate_onu` method exposed by that service
+- the OSS Service will be responsible to create a subscriber in XOS
+
+If no OSS Service is found in between the providers of `vOLTService`
+no action is taken as a consequence of an event.
+
+For more informations about the OSS Service, please look
+[here](../hippie-oss/README.md) and in the [R-CORD Configuration guide](../profiles/rcord/configuration.md)
+
+## Test configuration
+
+If you are testing the R-CORD Service chain (meaning that you don't have a
+running VOLTHA), you will need to manually create a `PONPort` and an `ONUDevice`.
+
+To do that, please check the example TOSCA in the repository
+[samples](https://github.com/opencord/olt-service/tree/master/samples) folder.
\ No newline at end of file
--- /dev/null
+# Setup the vOLT Service to work with VOLTHA
+
+The TOSCA recipes in this directory are intended to support development,
+but they can be used as a starting point to configure a real deployment.
\ No newline at end of file
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @olt_device_host_and_port.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/oltdevice.yaml
+ - custom_types/voltservice.yaml
+description: Create a simulated OLT Device in VOLTHA
+topology_template:
+ node_templates:
+
+ service#volt:
+ type: tosca.nodes.VOLTService
+ properties:
+ name: volt
+ must-exist: true
+
+ device#olt:
+ type: tosca.nodes.OLTDevice
+ properties:
+ name: test_olt
+ device_type: ponsim
+ host: 172.17.0.1
+ port: 50060
+ switch_datapath_id: of:0000000000000001
+ switch_port: "1"
+ outer_tpid: "0x8100"
+ dp_id: of:0000000ce2314000
+ uplink: "129"
+ requirements:
+ - volt_service:
+ node: service#volt
+ relationship: tosca.relationships.BelongsToOne
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @olt_device_mac_address.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/oltdevice.yaml
+ - custom_types/voltservice.yaml
+description: Create a simulated OLT Device in VOLTHA
+topology_template:
+ node_templates:
+
+ service#volt:
+ type: tosca.nodes.VOLTService
+ properties:
+ name: volt
+ must-exist: true
+
+ device#olt:
+ type: tosca.nodes.OLTDevice
+ properties:
+ name: test_olt
+ device_type: simulated_olt
+ mac_address: 00:0c:e2:31:40:00
+ switch_datapath_id: of:0000000000000001
+ switch_port: "1"
+ outer_tpid: "0x8100"
+ requirements:
+ - volt_service:
+ node: service#volt
+ relationship: tosca.relationships.BelongsToOne
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# Manually send the event
+
+import json
+from kafka import KafkaProducer
+
+event = json.dumps({
+ 'status': 'activated',
+ 'serial_number': 'BRCM1234',
+ 'uni_port_id': 16,
+ 'of_dpid': 'of:109299321'
+})
+producer = KafkaProducer(bootstrap_servers="cord-kafka")
+producer.send("onu.events", event)
+producer.flush()
\ No newline at end of file
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @pon_port.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/oltdevice.yaml
+ - custom_types/onudevice.yaml
+ - custom_types/ponport.yaml
+ - custom_types/voltservice.yaml
+ - custom_types/uniport.yaml
+description: Create a simulated OLT Device in VOLTHA
+topology_template:
+ node_templates:
+
+ device#olt:
+ type: tosca.nodes.OLTDevice
+ properties:
+ name: test_olt
+ must-exist: true
+
+ pon_port:
+ type: tosca.nodes.PONPort
+ properties:
+ name: test_pon_port_1
+ port_no: 2
+ requirements:
+ - olt_device:
+ node: device#olt
+ relationship: tosca.relationships.BelongsToOne
+
+ device#onu:
+ type: tosca.nodes.ONUDevice
+ properties:
+ serial_number: BRCM1234
+ vendor: Broadcom
+ requirements:
+ - pon_port:
+ node: pon_port
+ relationship: tosca.relationships.BelongsToOne
+
+ uni_port:
+ type: tosca.nodes.UNIPort
+ properties:
+ name: test_uni_port_1
+ port_no: 2
+ requirements:
+ - onu_device:
+ node: device#onu
+ relationship: tosca.relationships.BelongsToOne
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @pon_port.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/oltdevice.yaml
+ - custom_types/onudevice.yaml
+ - custom_types/ponport.yaml
+ - custom_types/voltservice.yaml
+ - custom_types/uniport.yaml
+description: Create a simulated OLT Device in VOLTHA
+topology_template:
+ node_templates:
+
+ device#olt:
+ type: tosca.nodes.OLTDevice
+ properties:
+ name: test_olt
+ must-exist: true
+
+ pon_port2:
+ type: tosca.nodes.PONPort
+ properties:
+ name: test_pon_port_2
+ port_no: 3
+ requirements:
+ - olt_device:
+ node: device#olt
+ relationship: tosca.relationships.BelongsToOne
+
+ device#onu2:
+ type: tosca.nodes.ONUDevice
+ properties:
+ serial_number: BRCM1235
+ vendor: Broadcom
+ requirements:
+ - pon_port:
+ node: pon_port2
+ relationship: tosca.relationships.BelongsToOne
+
+ uni_port2:
+ type: tosca.nodes.UNIPort
+ properties:
+ name: test_uni_port_2
+ port_no: 3
+ requirements:
+ - onu_device:
+ node: device#onu2
+ relationship: tosca.relationships.BelongsToOne
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @subscriber.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/rcordsubscriber.yaml
+description: Create a test subscriber
+topology_template:
+ node_templates:
+ # A subscriber
+ my_house2:
+ type: tosca.nodes.RCORDSubscriber
+ properties:
+ name: My Second House
+ c_tag: 112
+ s_tag: 222
+ onu_device: BRCM1234
+ ip_address: "1.2.3.5"
+ mac_address: "11:22:33:44:55:77"
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @subscriber.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/rcordsubscriber.yaml
+description: Create a test subscriber
+topology_template:
+ node_templates:
+ # A subscriber
+ my_house:
+ type: tosca.nodes.RCORDSubscriber
+ properties:
+ name: My House
+ c_tag: 111
+ s_tag: 222
+ onu_device: BRCM1234
+ ip_address: "1.2.3.4"
+ mac_address: "11:22:33:44:55:66"
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @volt_service.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+ - custom_types/voltservice.yaml
+description: Updates the existing vOLT service with deployment specific data
+topology_template:
+ node_templates:
+ service#volt:
+ type: tosca.nodes.VOLTService
+ properties:
+ name: volt
+ must-exist: true
+ voltha_url: 10.128.22.3
+ voltha_port: 8882
+ onos_voltha_url: 10.128.22.3
+ onos_voltha_port: 8181
+ onos_voltha_user: karaf
+ onos_voltha_pass: karaf
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+name: volt
+required_models:
+ - VOLTService
+ - VOLTServiceInstance
+ - ServiceInstanceLink
+ - OLTDevice
+dependency_graph: "/opt/xos/synchronizers/volt/model-deps"
+model_policies_dir: "/opt/xos/synchronizers/volt/model_policies"
+models_dir: "/opt/xos/synchronizers/volt/models"
+steps_dir: "/opt/xos/synchronizers/volt/steps"
+pull_steps_dir: "/opt/xos/synchronizers/volt/pull_steps"
+event_steps_dir: "/opt/xos/synchronizers/volt/event_steps"
+logging:
+ version: 1
+ handlers:
+ console:
+ class: logging.StreamHandler
+ loggers:
+ 'multistructlog':
+ handlers:
+ - console
+ level: DEBUG
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+import json
+import os
+import sys
+from synchronizers.new_base.eventstep import EventStep
+from synchronizers.new_base.modelaccessor import VOLTService, VOLTServiceInstance, Service
+from xosconfig import Config
+from multistructlog import create_logger
+
+log = create_logger(Config().get('logging'))
+
+class KubernetesPodDetailsEventStep(EventStep):
+ topics = ["xos.kubernetes.pod-details"]
+ technology = "kafka"
+
+ def __init__(self, *args, **kwargs):
+ super(KubernetesPodDetailsEventStep, self).__init__(*args, **kwargs)
+
+ @staticmethod
+ def get_onos(service):
+ service = Service.objects.get(id=service.id)
+
+ # get the onos_fabric service
+ onos = [s.leaf_model for s in service.subscriber_services if "onos" in s.name.lower()]
+
+ if len(onos) == 0:
+ raise Exception('Cannot find ONOS service in provider_services of Fabric-Crossconnect')
+
+ return onos[0]
+
+ def process_event(self, event):
+ value = json.loads(event.value)
+
+ if (value.get("status") != "created"):
+ return
+
+ if "labels" not in value:
+ return
+
+ xos_service = value["labels"].get("xos_service")
+ if not xos_service:
+ return
+
+ for service in VOLTService.objects.all():
+ onos = KubernetesPodDetailsEventStep.get_onos(service)
+ if (onos.name.lower() != xos_service.lower()):
+ continue
+
+ for service_instance in service.service_instances.all():
+ log.info("Dirtying VOLTServiceInstance", service_instance=service_instance)
+ service_instance.backend_code=0
+ service_instance.backend_status="resynchronize due to kubernetes event"
+ service_instance.save(update_fields=["updated", "backend_code", "backend_status"], always_update_timestamp=True)
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import unittest
+import json
+import functools
+from mock import patch, call, Mock, PropertyMock
+import requests_mock
+
+import os, sys
+
+# Hack to load synchronizer framework
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir = os.path.join(xos_dir, "../../xos_services")
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+class TestKubernetesEvent(unittest.TestCase):
+
+ def setUp(self):
+ global DeferredException
+
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ config = os.path.join(test_path, "../test_config.yaml")
+ from xosconfig import Config
+ Config.clear()
+ Config.init(config, 'synchronizer-config-schema.yaml')
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ build_mock_modelaccessor(xos_dir, services_dir, [
+ get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto"),
+ get_models_fn("onos-service", "onos.xproto"),
+ ])
+
+ import synchronizers.new_base.mock_modelaccessor
+ reload(synchronizers.new_base.mock_modelaccessor) # in case nose2 loaded it in a previous test
+
+ import synchronizers.new_base.modelaccessor
+ reload(synchronizers.new_base.modelaccessor) # in case nose2 loaded it in a previous test
+
+ from synchronizers.new_base.modelaccessor import model_accessor
+ from mock_modelaccessor import MockObjectList
+
+ from kubernetes_event import KubernetesPodDetailsEventStep
+
+ # import all class names to globals
+ for (k, v) in model_accessor.all_model_classes.items():
+ globals()[k] = v
+
+ self.event_step = KubernetesPodDetailsEventStep
+
+ self.onos = ONOSService(name="myonos",
+ id=1111,
+ rest_hostname = "onos-url",
+ rest_port = "8181",
+ rest_username = "karaf",
+ rest_password = "karaf",
+ backend_code=1,
+ backend_status="succeeded")
+
+ self.fcservice = VOLTService(name="myoltservice",
+ id=1112,
+ backend_code=1,
+ backend_status="succeeded",
+ subscriber_services=[self.onos])
+
+ self.fcsi1 = VOLTServiceInstance(name="myfcsi1",
+ owner=self.fcservice,
+ backend_code=1,
+ backend_status="succeeded")
+
+ self.fcsi2 = VOLTServiceInstance(name="myfcsi2",
+ owner=self.fcservice,
+ backend_code=1,
+ backend_status="succeeded")
+
+ self.fcservice.service_instances = MockObjectList([self.fcsi1, self.fcsi2])
+
+ self.log = Mock()
+
+ def tearDown(self):
+ sys.path = self.sys_path_save
+
+ def test_process_event(self):
+ with patch.object(VOLTService.objects, "get_items") as fcservice_objects, \
+ patch.object(Service.objects, "get_items") as service_objects, \
+ patch.object(VOLTServiceInstance, "save", autospec=True) as fcsi_save:
+ fcservice_objects.return_value = [self.fcservice]
+ service_objects.return_value = [self.onos, self.fcservice]
+
+ event_dict = {"status": "created",
+ "labels": {"xos_service": "myonos"}}
+ event = Mock()
+ event.value = json.dumps(event_dict)
+
+ step = self.event_step(log=self.log)
+ step.process_event(event)
+
+ self.assertEqual(self.fcsi1.backend_code, 0)
+ self.assertEqual(self.fcsi1.backend_status, "resynchronize due to kubernetes event")
+
+ self.assertEqual(self.fcsi2.backend_code, 0)
+ self.assertEqual(self.fcsi2.backend_status, "resynchronize due to kubernetes event")
+
+ fcsi_save.assert_has_calls([call(self.fcsi1, update_fields=["updated", "backend_code", "backend_status"],
+ always_update_timestamp=True),
+ call(self.fcsi2, update_fields=["updated", "backend_code", "backend_status"],
+ always_update_timestamp=True)])
+
+ def test_process_event_unknownstatus(self):
+ with patch.object(VOLTService.objects, "get_items") as fcservice_objects, \
+ patch.object(Service.objects, "get_items") as service_objects, \
+ patch.object(VOLTServiceInstance, "save") as fcsi_save:
+ fcservice_objects.return_value = [self.fcservice]
+ service_objects.return_value = [self.onos, self.fcservice]
+
+ event_dict = {"status": "something_else",
+ "labels": {"xos_service": "myonos"}}
+ event = Mock()
+ event.value = json.dumps(event_dict)
+
+ step = self.event_step(log=self.log)
+ step.process_event(event)
+
+ self.assertEqual(self.fcsi1.backend_code, 1)
+ self.assertEqual(self.fcsi1.backend_status, "succeeded")
+
+ self.assertEqual(self.fcsi2.backend_code, 1)
+ self.assertEqual(self.fcsi2.backend_status, "succeeded")
+
+ fcsi_save.assert_not_called()
+
+ def test_process_event_unknownservice(self):
+ with patch.object(VOLTService.objects, "get_items") as fcservice_objects, \
+ patch.object(Service.objects, "get_items") as service_objects, \
+ patch.object(VOLTServiceInstance, "save") as fcsi_save:
+ fcservice_objects.return_value = [self.fcservice]
+ service_objects.return_value = [self.onos, self.fcservice]
+
+ event_dict = {"status": "created",
+ "labels": {"xos_service": "something_else"}}
+ event = Mock()
+ event.value = json.dumps(event_dict)
+
+ step = self.event_step(log=self.log)
+ step.process_event(event)
+
+ self.assertEqual(self.fcsi1.backend_code, 1)
+ self.assertEqual(self.fcsi1.backend_status, "succeeded")
+
+ self.assertEqual(self.fcsi2.backend_code, 1)
+ self.assertEqual(self.fcsi2.backend_status, "succeeded")
+
+ fcsi_save.assert_not_called()
+
+ def test_process_event_nolabels(self):
+ with patch.object(VOLTService.objects, "get_items") as fcservice_objects, \
+ patch.object(Service.objects, "get_items") as service_objects, \
+ patch.object(VOLTServiceInstance, "save") as fcsi_save:
+ fcservice_objects.return_value = [self.fcservice]
+ service_objects.return_value = [self.onos, self.fcservice]
+
+ event_dict = {"status": "created"}
+ event = Mock()
+ event.value = json.dumps(event_dict)
+
+ step = self.event_step(log=self.log)
+ step.process_event(event)
+
+ self.assertEqual(self.fcsi1.backend_code, 1)
+ self.assertEqual(self.fcsi1.backend_status, "succeeded")
+
+ self.assertEqual(self.fcsi2.backend_code, 1)
+ self.assertEqual(self.fcsi2.backend_status, "succeeded")
+
+ fcsi_save.assert_not_called()
+
+if __name__ == '__main__':
+ unittest.main()
+
+
+
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+class Helpers():
+ @staticmethod
+ def format_url(url):
+ if 'http' in url:
+ return url
+ else:
+ return 'http://%s' % url
+
+ @staticmethod
+ def get_voltha_info(olt_service):
+ return {
+ 'url': Helpers.format_url(olt_service.voltha_url),
+ 'port': olt_service.voltha_port,
+ 'user': olt_service.voltha_user,
+ 'pass': olt_service.voltha_pass
+ }
+
+ @staticmethod
+ def get_onos_voltha_info(olt_service):
+ return {
+ 'url': Helpers.format_url(olt_service.onos_voltha_url),
+ 'port': olt_service.onos_voltha_port,
+ 'user': olt_service.onos_voltha_user,
+ 'pass': olt_service.onos_voltha_pass
+ }
+
+ @staticmethod
+ def datapath_id_to_hex(id):
+ if isinstance(id, basestring):
+ id = int(id)
+ return "{0:0{1}x}".format(id, 16)
\ No newline at end of file
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+from synchronizers.new_base.modelaccessor import VOLTServiceInstance, ServiceInstanceLink, ONUDevice, ServiceInstance, model_accessor
+from synchronizers.new_base.policy import Policy
+
+class VOLTServiceInstancePolicy(Policy):
+ model_name = "VOLTServiceInstance"
+
+ def handle_create(self, si):
+ return self.handle_update(si)
+
+ def handle_update(self, si):
+
+ if (si.link_deleted_count > 0) and (not si.provided_links.exists()):
+ # If this instance has no links pointing to it, delete
+ self.handle_delete(si)
+ if VOLTServiceInstance.objects.filter(id=si.id).exists():
+ si.delete()
+ return
+
+ self.create_eastbound_instance(si)
+ self.associate_onu_device(si)
+
+ def handle_delete(self, si):
+ pass
+
+ def create_eastbound_instance(self, si):
+ links = si.owner.subscribed_dependencies.all()
+ for link in links:
+ # SEBA-216 prevent any attempt to create an ONOSServiceInstance
+ if "onos" in link.provider_service.name.lower():
+ continue
+
+ provider_service = link.provider_service.leaf_model
+
+ valid_provider_service_instance = provider_service.validate_links(si)
+ if not valid_provider_service_instance:
+ provider_service.acquire_service_instance(si)
+
+ def associate_onu_device(self, si):
+
+ self.logger.debug("MODEL_POLICY: attaching ONUDevice to VOLTServiceInstance %s" % si.id)
+
+ base_si = ServiceInstance.objects.get(id=si.id)
+ try:
+ onu_device_serial_number = base_si.get_westbound_service_instance_properties("onu_device")
+ except Exception as e:
+ raise Exception(
+ "VOLTServiceInstance %s has no westbound ServiceInstance specifying the onu_device, you need to manually specify it" % self.id)
+
+ try:
+ onu = ONUDevice.objects.get(serial_number=onu_device_serial_number)
+ except IndexError:
+ raise Exception("ONUDevice with serial number %s can't be found" % onu_device_serial_number)
+
+ si.onu_device_id = onu.id
+ si.save()
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+import unittest
+from mock import patch, call, Mock, PropertyMock
+
+import os, sys
+
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+service_dir=os.path.join(test_path, "../../../..")
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir=os.path.join(xos_dir, "../../xos_services")
+
+# While transitioning from static to dynamic load, the path to find neighboring xproto files has changed. So check
+# both possible locations...
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+
+class TestModelPolicyVOLTServiceInstance(unittest.TestCase):
+ def setUp(self):
+ global VOLTServiceInstancePolicy, MockObjectList
+
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ config = os.path.join(test_path, "../test_config.yaml")
+ from xosconfig import Config
+ Config.clear()
+ Config.init(config, 'synchronizer-config-schema.yaml')
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto")])
+
+ import synchronizers.new_base.modelaccessor
+ import model_policy_voltserviceinstance
+ from model_policy_voltserviceinstance import VOLTServiceInstancePolicy, model_accessor
+
+ from mock_modelaccessor import MockObjectList
+
+ # import all class names to globals
+ for (k, v) in model_accessor.all_model_classes.items():
+ globals()[k] = v
+
+ # Some of the functions we call have side-effects. For example, creating a VSGServiceInstance may lead to creation of
+ # tags. Ideally, this wouldn't happen, but it does. So make sure we reset the world.
+ model_accessor.reset_all_object_stores()
+
+ self.policy = VOLTServiceInstancePolicy()
+ self.si = Mock()
+
+ def tearDown(self):
+ sys.path = self.sys_path_save
+
+ def test_handle_create(self):
+ with patch.object(VOLTServiceInstancePolicy, "create_eastbound_instance") as create_eastbound_instance, \
+ patch.object(VOLTServiceInstancePolicy, "associate_onu_device") as associate_onu_device:
+
+ self.policy.handle_create(self.si)
+ create_eastbound_instance.assert_called_with(self.si)
+ associate_onu_device.assert_called_with(self.si)
+
+ def test_create_vsg(self):
+ with patch.object(ServiceInstanceLink, "save", autospec=True) as save_link, \
+ patch.object(VSGServiceInstance, "save", autospec=True) as save_vsg:
+
+ subscriber_si = Mock()
+
+ link = Mock()
+ link.provider_service.get_service_instance_class_name.return_value = "VSGServiceInstance"
+ link.provider_service.name = "FabricCrossconnect"
+ link.provider_service.validate_links = Mock(return_value=[])
+ link.provider_service.acquire_service_instance = Mock(return_value=subscriber_si)
+ link.provider_service.leaf_model = link.provider_service
+
+ si = Mock()
+ si.subscribed_links.all.return_value = []
+ si.owner.subscribed_dependencies.all.return_value = [link]
+
+ self.policy.create_eastbound_instance(si)
+
+ link.provider_service.validate_links.assert_called_with(si)
+ link.provider_service.acquire_service_instance.assert_called_with(si)
+
+ def test_create_vsg_already_exists(self):
+ with patch.object(ServiceInstanceLink, "save", autospec=True) as save_link, \
+ patch.object(VSGServiceInstance, "save", autospec=True) as save_vsg:
+
+ subscriber_si = Mock()
+
+ link = Mock()
+ link.provider_service.get_service_instance_class_name.return_value = "VSGServiceInstance"
+ link.provider_service.name = "FabricCrossconnect"
+ link.provider_service.validate_links = Mock(return_value=subscriber_si)
+ link.provider_service.acquire_service_instance = Mock()
+ link.provider_service.leaf_model = link.provider_service
+
+ si = Mock()
+ si.subscribed_links.all.return_value = []
+ si.owner.subscribed_dependencies.all.return_value = [link]
+
+ self.policy.create_eastbound_instance(si)
+
+ link.provider_service.validate_links.assert_called_with(si)
+ link.provider_service.acquire_service_instance.assert_not_called()
+
+ def test_associate_onu(self):
+ with patch.object(ServiceInstance.objects, "get") as get_si, \
+ patch.object(ONUDevice.objects, "get") as get_onu:
+
+ mock_si = Mock()
+ mock_si.get_westbound_service_instance_properties.return_value = "BRCM1234"
+ get_si.return_value = mock_si
+
+ mock_onu = Mock()
+ mock_onu.id = 12
+ get_onu.return_value = mock_onu
+
+ self.policy.associate_onu_device(self.si)
+
+ self.assertEqual(self.si.onu_device_id, mock_onu.id)
+ self.si.save.assert_called()
+
+ def test_handle_delete(self):
+ self.policy.handle_delete(self.si)
+ # handle delete does nothing, and should trivially succeed
+
+if __name__ == '__main__':
+ unittest.main()
+
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+from xosapi.convenience.service import ORMWrapperService
+
+import logging as log
+
+
+class ORMWrapperVOLTService(ORMWrapperService):
+
+ def get_onu_sn_from_openflow(self, dp_id, port_no):
+ """Return the ONU serial number from logical_device informations
+
+ example usage:
+ volt = VOLTService.objects.first()
+ sn = volt.get_onu_from_openflow("of:0000000ce2314000", 2)
+ # BRCM1234
+
+ Arguments:
+ dp_id {string} -- The openflow id of the OLT device
+ port_no {int} -- The openflow port id (UNI Port)
+
+ Returns:
+ string -- ONU Serial Number
+ """
+
+ log.debug("Searching ONUDevice for %s:%s" % (dp_id, port_no))
+ try:
+ olt = self.stub.OLTDevice.objects.get(dp_id=dp_id)
+ uni_ports = self.stub.UNIPort.objects.filter(port_no=port_no)
+ onu = [o.onu_device for o in uni_ports if o.onu_device.pon_port.olt_device.id == olt.id][0]
+ return onu.serial_number
+ except IndexError:
+ log.error("Can't find ONU for %s:%s" % (dp_id, port_no))
+ except Exception:
+ log.exception("Error while finding ONUDevice for %s:%s" % (dp_id, port_no))
+
+
+register_convenience_wrapper("VOLTService", ORMWrapperVOLTService)
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+from xosapi.convenience.serviceinstance import ORMWrapperServiceInstance
+
+import logging as log
+
+class ORMWrapperVOLTServiceInstance(ORMWrapperServiceInstance):
+
+ def get_olt_device_by_subscriber(self):
+ pon_port = self.get_pon_port_by_subscriber()
+ return pon_port.olt_device
+
+ def get_pon_port_by_subscriber(self):
+ si = self.stub.ServiceInstance.objects.get(id=self.id)
+ onu_sn = si.get_westbound_service_instance_properties("onu_device")
+ onu = self.stub.ONUDevice.objects.get(serial_number=onu_sn)
+ return onu.pon_port
+
+ @property
+ def switch_datapath_id(self):
+ try:
+ olt_device = self.get_olt_device_by_subscriber()
+ if olt_device:
+ return olt_device.switch_datapath_id
+ return None
+ except Exception, e:
+ log.exception('Error while reading switch_datapath_id: %s' % e.message)
+ return None
+
+ @property
+ def switch_port(self):
+ try:
+ olt_device = self.get_olt_device_by_subscriber()
+ if olt_device:
+ return olt_device.switch_port
+ return None
+ except Exception, e:
+ log.exception('Error while reading switch_port: %s' % e.message)
+ return None
+
+ @property
+ def outer_tpid(self):
+ try:
+ olt_device = self.get_olt_device_by_subscriber()
+ if olt_device:
+ return olt_device.outer_tpid
+ return None
+ except Exception, e:
+ log.exception('Error while reading outer_tpid: %s' % e.message)
+ return None
+
+
+register_convenience_wrapper("VOLTServiceInstance", ORMWrapperVOLTServiceInstance)
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import random
+
+from xos.exceptions import XOSValidationError
+
+from models_decl import VOLTService_decl
+from models_decl import VOLTServiceInstance_decl
+from models_decl import OLTDevice_decl
+from models_decl import PortBase_decl
+from models_decl import PONPort_decl
+from models_decl import NNIPort_decl
+from models_decl import ONUDevice_decl
+from models_decl import PONONUPort_decl
+from models_decl import UNIPort_decl
+
+class VOLTService(VOLTService_decl):
+ class Meta:
+ proxy = True
+
+ @staticmethod
+ def has_access_device(serial_number):
+ try:
+ ONUDevice.objects.get(serial_number=serial_number)
+ return True
+ except IndexError, e:
+ return False
+
+class VOLTServiceInstance(VOLTServiceInstance_decl):
+ class Meta:
+ proxy = True
+
+class OLTDevice(OLTDevice_decl):
+ class Meta:
+ proxy = True
+
+ def get_volt_si(self):
+ return VOLTServiceInstance.objects.all()
+
+ def save(self, *args, **kwargs):
+
+ if (self.host or self.port) and self.mac_address:
+ raise XOSValidationError("You can't specify both host/port and mac_address for OLTDevice [host=%s, port=%s, mac_address=%s]" % (self.host, self.port, self.mac_address))
+
+ super(OLTDevice, self).save(*args, **kwargs)
+
+ def delete(self, *args, **kwargs):
+
+ onus = []
+ pon_ports = self.pon_ports.all()
+ for port in pon_ports:
+ onus = onus + list(port.onu_devices.all())
+
+
+ if len(onus) > 0:
+ onus = [o.id for o in onus]
+
+ # find the ONUs used by VOLTServiceInstances
+ used_onus = [o.onu_device_id for o in self.get_volt_si()]
+
+ # find the intersection between the onus associated with this OLT and the used one
+ used_onus_to_delete = [o for o in onus if o in used_onus]
+
+ if len(used_onus_to_delete) > 0:
+ if hasattr(self, "device_id") and self.device_id:
+ item = self.device_id
+ elif hasattr(self, "name") and self.name:
+ item = self.name
+ else:
+ item = self.id
+ raise XOSValidationError('OLT "%s" can\'t be deleted as it has subscribers associated with its ONUs' % item)
+
+ super(OLTDevice, self).delete(*args, **kwargs)
+
+class PortBase(PortBase_decl):
+ class Meta:
+ proxy = True
+
+class PONPort(PONPort_decl):
+ class Meta:
+ proxy = True
+
+class NNIPort(NNIPort_decl):
+ class Meta:
+ proxy = True
+
+
+class ONUDevice(ONUDevice_decl):
+ class Meta:
+ proxy = True
+
+ def delete(self, *args, **kwargs):
+
+ if len(self.volt_service_instances.all()) > 0:
+ raise XOSValidationError('ONU "%s" can\'t be deleted as it has subscribers associated with it' % self.serial_number)
+
+ super(ONUDevice, self).delete(*args, **kwargs)
+
+class PONONUPort(PONONUPort_decl):
+ class Meta:
+ proxy = True
+
+class UNIPort(UNIPort_decl):
+ class Meta:
+ proxy = True
+
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import unittest
+from mock import patch, Mock, MagicMock
+
+# mocking XOS exception, as they're based in Django
+class Exceptions:
+ XOSValidationError = Exception
+
+class XOS:
+ exceptions = Exceptions
+
+class TestOLTDeviceModel(unittest.TestCase):
+ def setUp(self):
+ self.xos = XOS
+
+ self.models_decl = Mock()
+ self.models_decl.OLTDevice_decl = MagicMock
+ self.models_decl.OLTDevice_decl.delete = Mock()
+
+ modules = {
+ 'xos.exceptions': self.xos.exceptions,
+ 'models_decl': self.models_decl,
+ }
+
+ self.module_patcher = patch.dict('sys.modules', modules)
+ self.module_patcher.start()
+
+ from models import OLTDevice
+
+ self.olt_device = OLTDevice()
+ self.olt_device.id = None # this is a new model
+ self.olt_device.is_new = True
+ self.olt_device.device_id = 1234
+
+ def tearDown(self):
+ self.module_patcher.stop()
+
+ def test_create_mac_address(self):
+ from models import OLTDevice
+ olt = OLTDevice()
+
+ olt.host = "1.1.1.1"
+ olt.port = "9101"
+ olt.mac_address = "00:0c:d5:00:05:40"
+
+ with self.assertRaises(Exception) as e:
+ olt.save()
+
+ self.assertEqual(e.exception.message,
+ "You can't specify both host/port and mac_address for OLTDevice [host=%s, port=%s, mac_address=%s]" % (olt.host, olt.port, olt.mac_address))
+
+ def test_delete(self):
+ self.olt_device.delete()
+ self.models_decl.OLTDevice_decl.delete.assert_called()
+
+ def test_prevent_delete(self):
+
+ onu1 = Mock()
+ onu1.id = 1
+
+ pon1 = Mock()
+ pon1.onu_devices.all.return_value = [onu1]
+
+ self.olt_device.pon_ports.all.return_value = [pon1]
+
+ volt_si_1 = Mock()
+ volt_si_1.onu_device_id = onu1.id
+
+ with patch.object(self.olt_device, "get_volt_si")as volt_si_get:
+ volt_si_get.return_value = [volt_si_1]
+ with self.assertRaises(Exception) as e:
+ self.olt_device.delete()
+
+ self.assertEqual(e.exception.message, 'OLT "1234" can\'t be deleted as it has subscribers associated with its ONUs')
+ self.models_decl.OLTDevice_decl.delete.assert_not_called()
+
+class TestONUDeviceModel(unittest.TestCase):
+
+ def setUp(self):
+ self.xos = XOS
+
+ self.models_decl = Mock()
+ self.models_decl.ONUDevice_decl = MagicMock
+ self.models_decl.ONUDevice_decl.delete = Mock()
+
+ modules = {
+ 'xos.exceptions': self.xos.exceptions,
+ 'models_decl': self.models_decl,
+ }
+
+ self.module_patcher = patch.dict('sys.modules', modules)
+ self.module_patcher.start()
+
+ from models import ONUDevice
+
+ self.onu_device = ONUDevice()
+ self.onu_device.id = None # this is a new model
+ self.onu_device.is_new = True
+ self.onu_device.serial_number = 1234
+
+ def test_delete(self):
+ self.onu_device.delete()
+ self.models_decl.ONUDevice_decl.delete.assert_called()
+
+ def test_prevent_delete(self):
+ volt_si_1 = Mock()
+ volt_si_1.onu_device_id = self.onu_device.id
+ self.onu_device.volt_service_instances.all.return_value = [volt_si_1]
+
+ with self.assertRaises(Exception) as e:
+ self.onu_device.delete()
+
+ self.assertEqual(e.exception.message,
+ 'ONU "1234" can\'t be deleted as it has subscribers associated with it')
+ self.models_decl.OLTDevice_decl.delete.assert_not_called()
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+option name = "volt";
+option app_label = "volt";
+option legacy="True";
+
+message VOLTService (Service){
+ option verbose_name = "vOLT Service";
+ option kind = "vOLT";
+
+ required string voltha_url = 1 [help_text = "The Voltha API address. By default voltha.voltha.svc.cluster.local", default = "voltha.voltha.svc.cluster.local", max_length = 254, db_index = False];
+ required int32 voltha_port = 2 [help_text = "The Voltha API port. By default 8882", default=8882, db_index = False];
+ required string voltha_user = 3 [help_text = "The Voltha username. By default voltha", default="voltha", max_length = 254, db_index = False];
+ required string voltha_pass = 4 [help_text = "The Voltha password. By default admin", default="admin", max_length = 254, db_index = False];
+ required string onos_voltha_url = 5 [help_text = "The ONOS Voltha address. By default onos-voltha-ui.voltha.svc.cluster.local", default="onos-voltha-ui.voltha.svc.cluster.local", max_length = 254, db_index = False];
+ required int32 onos_voltha_port = 6 [help_text = "The Voltha API port. By default 8181", default=8181, db_index = False];
+ required string onos_voltha_user = 7 [help_text = "The ONOS Voltha username. By default sdn", max_length = 254, default="onos", db_index = False];
+ required string onos_voltha_pass = 8 [help_text = "The ONOS Voltha password. By default rocks", max_length = 254, default="rocks", db_index = False];
+}
+
+message OLTDevice (XOSBase){
+ option verbose_name = "OLT Device";
+ option description="Represents a physical OLT device";
+
+ required manytoone volt_service->VOLTService:volt_devices = 1:1001 [db_index = True];
+ optional string name = 2 [help_text = "name of device", max_length = 254, db_index = False, unique = True];
+ required string device_type = 3 [help_text = "Device Type", default = "openolt", max_length = 254, db_index = False];
+ optional string host = 4 [help_text = "Device IP", max_length = 254, db_index = False];
+ optional int32 port = 5 [help_text = "Device port", db_index = False, unique_with = "host"];
+ optional string mac_address = 6 [help_text = "Device mac address", db_index = False];
+
+ optional string serial_number = 9 [help_text = "Serial Number", db_index = False];
+ optional string device_id = 10 [help_text = "Device ID", db_index = False, feedback_state = True];
+ optional string admin_state = 11 [help_text = "admin_state", db_index = False, feedback_state = True];
+ optional string oper_status = 12 [help_text = "oper_status", db_index = False, feedback_state = True];
+ optional string of_id = 13 [help_text = "openflow id", db_index = False, feedback_state = True];
+ optional string dp_id = 14 [help_text = "datapath id", db_index = False];
+
+ required string uplink = 15 [help_text = "uplink port", db_index = False];
+ required string driver = 16 [default="voltha", help_text = "Olt driver", db_index = False];
+
+ optional string switch_datapath_id = 17 [help_text = "Fabric switch to which the OLT is connected", db_index = False];
+ optional string switch_port = 18 [help_text = "Fabric port to which the OLT is connected", db_index = False];
+ optional string outer_tpid = 19 [help_text = "Outer VLAN id field EtherType", db_index = False];
+
+ optional string nas_id = 20 [help_text = "Authentication ID (propagated to the free-radius server via sadis)", db_index = False];
+}
+
+message PortBase (XOSBase){
+ option gui_hidden = True;
+
+ required string name = 1 [db_index = True];
+ required int32 port_no = 3 [help_text = "Port ID", db_index = False];
+
+ optional string admin_state = 4 [help_text = "admin_state", db_index = False, feedback_state = True];
+ optional string oper_status = 5 [help_text = "oper_status", db_index = False, feedback_state = True];
+}
+
+message PONPort (PortBase){
+ option verbose_name = "PON Port";
+ option description="PON Port";
+
+ required manytoone olt_device->OLTDevice:pon_ports = 1:1001 [db_index = True];
+}
+
+message NNIPort (PortBase) {
+ option verbose_name = "NNI Port";
+ required manytoone olt_device->OLTDevice:nni_ports = 1:1002 [db_index = True];
+}
+
+message ONUDevice (XOSBase){
+ option verbose_name = "ONU Device";
+ option description = "Represents a physical ONU device";
+
+ required manytoone pon_port->PONPort:onu_devices = 1:1001 [db_index = True];
+ required string serial_number = 2 [max_length = 254, db_index = False, tosca_key=True, unique = True];
+ required string vendor = 3 [max_length = 254, db_index = False];
+ required string device_type = 4 [help_text = "Device Type", default = "asfvolt16_olt", max_length = 254, db_index = False];
+
+ optional string device_id = 5 [max_length = 254, db_index = False, feedback_state = True];
+ optional string admin_state = 6 [choices = "(('DISABLED', 'DISABLED'), ('ENABLED', 'ENABLED'))", default="ENABLED", help_text = "admin_state", db_index = False];
+ optional string oper_status = 7 [help_text = "oper_status", db_index = False, feedback_state = True];
+ optional string connect_status = 8 [help_text = "connect_status", db_index = False, feedback_state = True];
+}
+
+message PONONUPort (PortBase) {
+ option verbose_name = "ANI Port";
+ option description="ANI Port";
+ required manytoone onu_device->ONUDevice:pononu_ports = 1:1001 [db_index = True];
+}
+
+message UNIPort (PortBase) {
+ option verbose_name = "UNI Port";
+ required manytoone onu_device->ONUDevice:uni_ports = 1:1002 [db_index = True];
+}
+
+message VOLTServiceInstance (ServiceInstance){
+ option kind = "vOLT";
+ option owner_class_name = "VOLTService";
+ option verbose_name = "vOLT Service Instance";
+
+ optional string description = 1 [max_length = 254, db_index = False];
+ optional manytoone onu_device->ONUDevice:volt_service_instances = 2:1003 [db_index = True];
+}
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from synchronizers.new_base.pullstep import PullStep
+from synchronizers.new_base.modelaccessor import model_accessor, OLTDevice, VOLTService, PONPort, NNIPort
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+import requests
+from requests import ConnectionError
+from requests.models import InvalidURL
+
+import os, sys
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from helpers import Helpers
+
+log = create_logger(Config().get('logging'))
+
+class OLTDevicePullStep(PullStep):
+ def __init__(self):
+ super(OLTDevicePullStep, self).__init__(observed_model=OLTDevice)
+
+ @staticmethod
+ def get_ids_from_logical_device(o):
+ voltha_url = Helpers.get_voltha_info(o.volt_service)['url']
+ voltha_port = Helpers.get_voltha_info(o.volt_service)['port']
+
+ r = requests.get("%s:%s/api/v1/logical_devices" % (voltha_url, voltha_port), timeout=1)
+
+ if r.status_code != 200:
+ raise Exception("Failed to retrieve logical devices from VOLTHA: %s" % r.text)
+
+ res = r.json()
+
+ for ld in res["items"]:
+ if ld["root_device_id"] == o.device_id:
+ o.of_id = ld["id"]
+ o.dp_id = "of:" + Helpers.datapath_id_to_hex(ld["datapath_id"]) # convert to hex
+ return o
+
+ raise Exception("Can't find a logical device for device id: %s" % o.device_id)
+
+ def pull_records(self):
+ log.debug("[OLT pull step] pulling OLT devices from VOLTHA")
+
+ try:
+ self.volt_service = VOLTService.objects.all()[0]
+ except IndexError:
+ log.warn('VOLTService not found')
+ return
+
+ voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
+ voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
+
+ try:
+ r = requests.get("%s:%s/api/v1/devices" % (voltha_url, voltha_port), timeout=1)
+
+ if r.status_code != 200:
+ log.debug("[OLT pull step] It was not possible to fetch devices from VOLTHA")
+
+ # keeping only OLTs
+ devices = [d for d in r.json()["items"] if "olt" in d["type"]]
+
+ log.trace("[OLT pull step] received devices", olts=devices)
+
+ olts_in_voltha = self.create_or_update_olts(devices)
+
+ self.delete_olts(olts_in_voltha)
+
+
+ except ConnectionError, e:
+ log.warn("[OLT pull step] It was not possible to connect to VOLTHA", reason=e)
+ return
+ except InvalidURL, e:
+ log.warn("[OLT pull step] VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
+ return
+
+ def create_or_update_olts(self, olts):
+
+ updated_olts = []
+
+ for olt in olts:
+ if olt["type"] == "simulated_olt":
+ [host, port] = ["172.17.0.1", "50060"]
+ else:
+ [host, port] = olt["host_and_port"].split(":")
+
+ olt_ports = self.fetch_olt_ports(olt["id"])
+
+ try:
+ model = OLTDevice.objects.filter(device_type=olt["type"], host=host, port=port)[0]
+
+ log.trace("[OLT pull step] OLTDevice already exists, updating it", device_type=olt["type"], host=host, port=port)
+
+ if model.enacted < model.updated:
+ log.debug("[OLT pull step] Skipping pull on OLTDevice %s as enacted < updated" % model.name, name=model.name, id=model.id, enacted=model.enacted, updated=model.updated)
+ # if we are not updating the device we still need to pull ports
+ if olt_ports:
+ self.create_or_update_ports(olt_ports, model)
+ updated_olts.append(model)
+ continue
+
+ except IndexError:
+
+ model = OLTDevice()
+ model.device_type = olt["type"]
+
+ if olt["type"] == "simulated_olt":
+ model.host = "172.17.0.1"
+ model.port = 50060
+ else:
+ [host, port] = olt["host_and_port"].split(":")
+ model.host = host
+ model.port = int(port)
+
+ # there's no name in voltha, so make one up based on the id
+ model.name = "OLT-%s" % olt["id"]
+
+ nni_ports = [p for p in olt_ports if "ETHERNET_NNI" in p["type"]]
+ if not nni_ports:
+ log.warning("[OLT pull step] No NNI ports, so no way to determine uplink. Skipping.", device_type=olt["type"], host=host, port=port)
+ continue
+
+ # Exctract uplink from the first NNI port. This decision is arbitrary, we will worry about multiple
+ # NNI ports when that situation arises.
+ model.uplink = str(nni_ports[0]["port_no"])
+
+ log.debug("[OLT pull step] OLTDevice is new, creating it", device_type=olt["type"], host=host, port=port)
+
+ # Adding feedback state to the device
+ model.device_id = olt["id"]
+ model.admin_state = olt["admin_state"]
+ model.oper_status = olt["oper_status"]
+ model.serial_number = olt['serial_number']
+
+ model.volt_service = self.volt_service
+ model.volt_service_id = self.volt_service.id
+
+ # get logical device
+ OLTDevicePullStep.get_ids_from_logical_device(model)
+
+ model.save()
+
+ if olt_ports:
+ self.create_or_update_ports(olt_ports, model)
+
+ updated_olts.append(model)
+
+ return updated_olts
+
+ def fetch_olt_ports(self, olt_device_id):
+ """ Given an olt device_id, query voltha for the set of ports associated with that OLT.
+
+ Returns a list of port dictionaries, or None in case of error.
+ """
+
+ voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
+ voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
+
+ try:
+ r = requests.get("%s:%s/api/v1/devices/%s/ports" % (voltha_url, voltha_port, olt_device_id), timeout=1)
+
+ if r.status_code != 200:
+ log.warn("[OLT pull step] It was not possible to fetch ports from VOLTHA for device %s" % olt_device_id,
+ status_code=r.status_code)
+ return None
+
+ ports = r.json()['items']
+
+ log.trace("[OLT pull step] received ports", ports=ports, olt=olt_device_id)
+
+ return ports
+
+ except ConnectionError, e:
+ log.warn("[OLT pull step] It was not possible to connect to VOLTHA", reason=e)
+ return None
+ except InvalidURL, e:
+ log.warn("[OLT pull step] VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
+ return None
+
+ return None
+
+ def create_or_update_ports(self, ports, olt):
+ nni_ports = [p for p in ports if "ETHERNET_NNI" in p["type"]]
+ pon_ports = [p for p in ports if "PON_OLT" in p["type"]]
+
+ self.create_or_update_nni_port(nni_ports, olt)
+ self.create_or_update_pon_port(pon_ports, olt)
+
+ def create_or_update_pon_port(self, pon_ports, olt):
+
+ update_ports = []
+
+ for port in pon_ports:
+ try:
+ model = PONPort.objects.filter(port_no=port["port_no"], olt_device_id=olt.id)[0]
+ log.trace("[OLT pull step] PONPort already exists, updating it", port_no=port["port_no"], olt_device_id=olt.id)
+ except IndexError:
+ model = PONPort()
+ model.port_no = port["port_no"]
+ model.olt_device_id = olt.id
+ model.name = port["label"]
+ log.debug("[OLT pull step] PONPort is new, creating it", port_no=port["port_no"], olt_device_id=olt.id)
+
+ model.admin_state = port["admin_state"]
+ model.oper_status = port["oper_status"]
+ model.save()
+ update_ports.append(model)
+ return update_ports
+
+ def create_or_update_nni_port(self, nni_ports, olt):
+ update_ports = []
+
+ for port in nni_ports:
+ try:
+ model = NNIPort.objects.filter(port_no=port["port_no"], olt_device_id=olt.id)[0]
+ model.xos_managed = False
+ log.trace("[OLT pull step] NNIPort already exists, updating it", port_no=port["port_no"], olt_device_id=olt.id)
+ except IndexError:
+ model = NNIPort()
+ model.port_no = port["port_no"]
+ model.olt_device_id = olt.id
+ model.name = port["label"]
+ model.xos_managed = False
+ log.debug("[OLT pull step] NNIPort is new, creating it", port_no=port["port_no"], olt_device_id=olt.id)
+
+ model.admin_state = port["admin_state"]
+ model.oper_status = port["oper_status"]
+ model.save()
+ update_ports.append(model)
+ return update_ports
+
+ def delete_olts(self, olts_in_voltha):
+
+ olts_id_in_voltha = [m.device_id for m in olts_in_voltha]
+
+ xos_olts = OLTDevice.objects.all()
+
+ deleted_in_voltha = [o for o in xos_olts if o.device_id not in olts_id_in_voltha]
+
+ for model in deleted_in_voltha:
+
+ if model.enacted < model.updated:
+ # DO NOT delete a model that is being processed
+ log.debug("[OLT pull step] device is not present in VOLTHA, skipping deletion as sync is in progress", device_id=o.device_id,
+ name=o.name)
+ continue
+
+ log.debug("[OLT pull step] deleting device as it's not present in VOLTHA", device_id=o.device_id, name=o.name)
+ model.delete()
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from synchronizers.new_base.pullstep import PullStep
+from synchronizers.new_base.modelaccessor import model_accessor, ONUDevice, VOLTService, OLTDevice, PONPort, PONONUPort, UNIPort
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+import requests
+from requests import ConnectionError
+from requests.models import InvalidURL
+
+import os, sys
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from helpers import Helpers
+
+log = create_logger(Config().get('logging'))
+
+class ONUDevicePullStep(PullStep):
+ def __init__(self):
+ super(ONUDevicePullStep, self).__init__(observed_model=ONUDevice)
+
+ def pull_records(self):
+ log.debug("pulling ONU devices from VOLTHA")
+
+ try:
+ self.volt_service = VOLTService.objects.all()[0]
+ except IndexError:
+ log.warn('VOLTService not found')
+ return
+
+ voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
+ voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
+
+ try:
+ r = requests.get("%s:%s/api/v1/devices" % (voltha_url, voltha_port), timeout=1)
+
+ if r.status_code != 200:
+ log.warn("It was not possible to fetch devices from VOLTHA")
+
+ # keeping only ONUs
+ devices = [d for d in r.json()["items"] if "onu" in d["type"]]
+
+ log.trace("received devices", onus=devices)
+
+ # TODO
+ # [ ] delete ONUS as ONUDevice.objects.all() - updated ONUs
+
+ onus_in_voltha = self.create_or_update_onus(devices)
+
+ except ConnectionError, e:
+ log.warn("It was not possible to connect to VOLTHA", reason=e)
+ return
+ except InvalidURL, e:
+ log.warn("VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
+ return
+
+ def create_or_update_onus(self, onus):
+
+ updated_onus = []
+
+ for onu in onus:
+ try:
+
+ model = ONUDevice.objects.filter(serial_number=onu["serial_number"])[0]
+ log.trace("ONUDevice already exists, updating it", serial_number=onu["serial_number"])
+
+ if model.enacted < model.updated:
+ log.debug("Skipping pull on ONUDevice %s as enacted < updated" % model.serial_number, serial_number=model.serial_number, id=model.id, enacted=model.enacted, updated=model.updated)
+ # if we are not updating the device we still need to pull ports
+ self.fetch_onu_ports(model)
+ continue
+
+ except IndexError:
+ model = ONUDevice()
+ model.serial_number = onu["serial_number"]
+
+ log.debug("ONUDevice is new, creating it", serial_number=onu["serial_number"])
+
+ try:
+ olt = OLTDevice.objects.get(device_id=onu["parent_id"])
+ except IndexError:
+ log.warning("Unable to find olt for ONUDevice", serial_number=onu["serial_number"], olt_device_id=onu["parent_id"])
+ continue
+
+ try:
+ pon_port = PONPort.objects.get(port_no=onu["parent_port_no"], olt_device_id=olt.id)
+ except IndexError:
+ log.warning("Unable to find pon_port for ONUDevice", serial_number=onu["serial_number"], olt_device_id=onu["parent_id"], port_no=onu["parent_port_no"])
+ continue
+
+ # Adding feedback state to the device
+ model.vendor = onu["vendor"]
+ model.device_type = onu["type"]
+ model.device_id = onu["id"]
+
+ model.admin_state = onu["admin_state"]
+ model.oper_status = onu["oper_status"]
+ model.connect_status = onu["connect_status"]
+ model.xos_managed = False
+
+ model.pon_port = pon_port
+ model.pon_port_id = pon_port.id
+
+ model.save()
+
+ self.fetch_onu_ports(model)
+
+ updated_onus.append(model)
+
+ return updated_onus
+
+ def fetch_onu_ports(self, onu):
+ voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
+ voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
+
+ try:
+ r = requests.get("%s:%s/api/v1/devices/%s/ports" % (voltha_url, voltha_port, onu.device_id), timeout=1)
+
+ if r.status_code != 200:
+ log.warn("It was not possible to fetch ports from VOLTHA for ONUDevice %s" % onu.device_id)
+
+ ports = r.json()['items']
+
+ log.trace("received ports", ports=ports, onu=onu.device_id)
+
+ self.create_or_update_ports(ports, onu)
+
+ except ConnectionError, e:
+ log.warn("It was not possible to connect to VOLTHA", reason=e)
+ return
+ except InvalidURL, e:
+ log.warn("VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
+ return
+ return
+
+ def create_or_update_ports(self, ports, onu):
+ uni_ports = [p for p in ports if "ETHERNET_UNI" in p["type"]]
+ pon_onu_ports = [p for p in ports if "PON_ONU" in p["type"]]
+
+ self.create_or_update_uni_port(uni_ports, onu)
+ self.create_or_update_pon_onu_port(pon_onu_ports, onu)
+
+ def get_onu_port_id(self, port, onu):
+ # find the correct port id as represented in the logical_device
+ logical_device_id = onu.pon_port.olt_device.of_id
+
+ voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
+ voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
+
+ try:
+ r = requests.get("%s:%s/api/v1/logical_devices/%s/ports" % (voltha_url, voltha_port, logical_device_id), timeout=1)
+
+ if r.status_code != 200:
+ log.warn("It was not possible to fetch ports from VOLTHA for logical_device %s" % logical_device_id)
+
+ logical_ports = r.json()['items']
+ log.trace("logical device ports for ONUDevice %s" % onu.device_id, logical_ports=logical_ports)
+
+ ports = [p['ofp_port']['port_no'] for p in logical_ports if p['device_id'] == onu.device_id]
+ # log.debug("Port_id for port %s on ONUDevice %s: %s" % (port['label'], onu.device_id, ports), logical_ports=logical_ports)
+ # FIXME if this throws an error ONUs from other OTLs are not sync'ed
+ return int(ports[0])
+
+ except ConnectionError, e:
+ log.warn("It was not possible to connect to VOLTHA", reason=e)
+ return
+ except InvalidURL, e:
+ log.warn("VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
+ return
+
+ def create_or_update_uni_port(self, uni_ports, onu):
+ update_ports = []
+
+ for port in uni_ports:
+ port_no = self.get_onu_port_id(port, onu)
+ try:
+ model = UNIPort.objects.filter(port_no=port_no, onu_device_id=onu.id)[0]
+ log.trace("UNIPort already exists, updating it", port_no=port_no, onu_device_id=onu.id)
+ except IndexError:
+ model = UNIPort()
+ model.port_no = port_no
+ model.onu_device_id = onu.id
+ model.name = port["label"]
+ log.debug("UNIPort is new, creating it", port_no=port["port_no"], onu_device_id=onu.id)
+
+ model.admin_state = port["admin_state"]
+ model.oper_status = port["oper_status"]
+ model.save()
+ update_ports.append(model)
+ return update_ports
+
+ def create_or_update_pon_onu_port(self, pon_onu_ports, onu):
+ update_ports = []
+
+ for port in pon_onu_ports:
+ try:
+ model = PONONUPort.objects.filter(port_no=port["port_no"], onu_device_id=onu.id)[0]
+ model.xos_managed = False
+ log.trace("PONONUPort already exists, updating it", port_no=port["port_no"], onu_device_id=onu.id)
+ except IndexError:
+ model = PONONUPort()
+ model.port_no = port["port_no"]
+ model.onu_device_id = onu.id
+ model.name = port["label"]
+ model.xos_managed = False
+ log.debug("PONONUPort is new, creating it", port_no=port["port_no"], onu_device_id=onu.id)
+
+ model.admin_state = port["admin_state"]
+ model.oper_status = port["oper_status"]
+ model.save()
+ update_ports.append(model)
+ return update_ports
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import unittest
+from mock import patch, call, Mock, PropertyMock
+import requests_mock
+
+import os, sys
+
+# Hack to load synchronizer framework
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir = os.path.join(xos_dir, "../../xos_services")
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+class TestSyncOLTDevice(unittest.TestCase):
+
+ def setUp(self):
+ global DeferredException
+
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ # Setting up the config module
+ from xosconfig import Config
+ config = os.path.join(test_path, "../test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+ # END Setting up the config module
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ # build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto")])
+
+ # FIXME this is to get jenkins to pass the tests, somehow it is running tests in a different order
+ # and apparently it is not overriding the generated model accessor
+ build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto")])
+ import synchronizers.new_base.modelaccessor
+ from pull_olts import OLTDevicePullStep, model_accessor
+
+ # import all class names to globals
+ for (k, v) in model_accessor.all_model_classes.items():
+ globals()[k] = v
+
+ self.sync_step = OLTDevicePullStep
+
+ # mock volt service
+ self.volt_service = Mock()
+ self.volt_service.id = "volt_service_id"
+ self.volt_service.voltha_url = "voltha_url"
+ self.volt_service.voltha_user = "voltha_user"
+ self.volt_service.voltha_pass = "voltha_pass"
+ self.volt_service.voltha_port = 1234
+
+ # mock voltha responses
+ self.devices = {
+ "items": [
+ {
+ "id": "test_id",
+ "type": "simulated_olt",
+ "host_and_port": "172.17.0.1:50060",
+ "admin_state": "ENABLED",
+ "oper_status": "ACTIVE",
+ "serial_number": "serial_number",
+ }
+ ]
+ }
+
+ self.logical_devices = {
+ "items": [
+ {
+ "root_device_id": "test_id",
+ "id": "of_id",
+ "datapath_id": "55334486016"
+ }
+ ]
+ }
+
+ self.ports = {
+ "items": [
+ {
+ "label": "PON port",
+ "port_no": 1,
+ "type": "PON_OLT",
+ "admin_state": "ENABLED",
+ "oper_status": "ACTIVE"
+ },
+ {
+ "label": "NNI facing Ethernet port",
+ "port_no": 2,
+ "type": "ETHERNET_NNI",
+ "admin_state": "ENABLED",
+ "oper_status": "ACTIVE"
+ }
+ ]
+ }
+
+ def tearDown(self):
+ sys.path = self.sys_path_save
+
+ @requests_mock.Mocker()
+ def test_missing_volt_service(self, m):
+ self.assertFalse(m.called)
+
+ @requests_mock.Mocker()
+ def test_pull(self, m):
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice, "save") as mock_olt_save, \
+ patch.object(PONPort, "save") as mock_pon_save, \
+ patch.object(NNIPort, "save") as mock_nni_save:
+ olt_service_mock.return_value = [self.volt_service]
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+ m.get("http://voltha_url:1234/api/v1/devices/test_id/ports", status_code=200, json=self.ports)
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=self.logical_devices)
+
+ self.sync_step().pull_records()
+
+ # TODO how to asster this?
+ # self.assertEqual(existing_olt.admin_state, "ENABLED")
+ # self.assertEqual(existing_olt.oper_status, "ACTIVE")
+ # self.assertEqual(existing_olt.volt_service_id, "volt_service_id")
+ # self.assertEqual(existing_olt.device_id, "test_id")
+ # self.assertEqual(existing_olt.of_id, "of_id")
+ # self.assertEqual(existing_olt.dp_id, "of:0000000ce2314000")
+
+ mock_olt_save.assert_called()
+ mock_pon_save.assert_called()
+ mock_nni_save.assert_called()
+
+ @requests_mock.Mocker()
+ def test_pull_existing(self, m):
+
+ existing_olt = Mock()
+ existing_olt.enacted = 2
+ existing_olt.updated = 1
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice.objects, "filter") as mock_get, \
+ patch.object(PONPort, "save") as mock_pon_save, \
+ patch.object(NNIPort, "save") as mock_nni_save, \
+ patch.object(existing_olt, "save") as mock_olt_save:
+ olt_service_mock.return_value = [self.volt_service]
+ mock_get.return_value = [existing_olt]
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+ m.get("http://voltha_url:1234/api/v1/devices/test_id/ports", status_code=200, json=self.ports)
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=self.logical_devices)
+
+ self.sync_step().pull_records()
+
+ self.assertEqual(existing_olt.admin_state, "ENABLED")
+ self.assertEqual(existing_olt.oper_status, "ACTIVE")
+ self.assertEqual(existing_olt.volt_service_id, "volt_service_id")
+ self.assertEqual(existing_olt.device_id, "test_id")
+ self.assertEqual(existing_olt.of_id, "of_id")
+ self.assertEqual(existing_olt.dp_id, "of:0000000ce2314000")
+
+ mock_olt_save.assert_called()
+ mock_pon_save.assert_called()
+ mock_nni_save.assert_called()
+
+ @requests_mock.Mocker()
+ def test_pull_existing_do_not_sync(self, m):
+ existing_olt = Mock()
+ existing_olt.enacted = 1
+ existing_olt.updated = 2
+ existing_olt.device_id = "test_id"
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice.objects, "filter") as mock_get, \
+ patch.object(PONPort, "save") as mock_pon_save, \
+ patch.object(NNIPort, "save") as mock_nni_save, \
+ patch.object(existing_olt, "save") as mock_olt_save:
+
+ olt_service_mock.return_value = [self.volt_service]
+ mock_get.return_value = [existing_olt]
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+ m.get("http://voltha_url:1234/api/v1/devices/test_id/ports", status_code=200, json=self.ports)
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=self.logical_devices)
+
+ self.sync_step().pull_records()
+
+ mock_olt_save.assert_not_called()
+ mock_pon_save.assert_called()
+ mock_nni_save.assert_called()
+
+ @requests_mock.Mocker()
+ def test_pull_deleted_object(self, m):
+ existing_olt = Mock()
+ existing_olt.enacted = 2
+ existing_olt.updated = 1
+ existing_olt.device_id = "test_id"
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json={"items": []})
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice.objects, "get_items") as mock_get, \
+ patch.object(existing_olt, "delete") as mock_olt_delete:
+
+ olt_service_mock.return_value = [self.volt_service]
+ mock_get.return_value = [existing_olt]
+
+ self.sync_step().pull_records()
+
+ mock_olt_delete.assert_called()
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import functools
+import unittest
+from mock import patch, call, Mock, PropertyMock
+import requests_mock
+
+import os, sys
+
+# Hack to load synchronizer framework
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir = os.path.join(xos_dir, "../../xos_services")
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+class TestPullONUDevice(unittest.TestCase):
+
+ def setUp(self):
+ global DeferredException
+
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ # Setting up the config module
+ from xosconfig import Config
+ config = os.path.join(test_path, "../test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+ # END Setting up the config module
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ # build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto")])
+
+ # FIXME this is to get jenkins to pass the tests, somehow it is running tests in a different order
+ # and apparently it is not overriding the generated model accessor
+ build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto")])
+ import synchronizers.new_base.modelaccessor
+ from pull_onus import ONUDevicePullStep, model_accessor
+
+ # import all class names to globals
+ for (k, v) in model_accessor.all_model_classes.items():
+ globals()[k] = v
+
+ self.sync_step = ONUDevicePullStep
+
+ # mock volt service
+ self.volt_service = Mock()
+ self.volt_service.id = "volt_service_id"
+ self.volt_service.voltha_url = "voltha_url"
+ self.volt_service.voltha_user = "voltha_user"
+ self.volt_service.voltha_pass = "voltha_pass"
+ self.volt_service.voltha_port = 1234
+
+ # mock OLTDevice
+ self.olt = Mock()
+ self.olt.id = 1
+
+ # second mock OLTDevice
+ self.olt2 = Mock()
+ self.olt2.id = 2
+
+ # mock pon port
+ self.pon_port = Mock()
+ self.pon_port.id = 1
+
+ # mock pon port
+ self.pon_port2 = Mock()
+ self.pon_port2.id = 2
+
+ # mock voltha responses
+ self.devices = {
+ "items": [
+ {
+ "id": "0001130158f01b2d",
+ "type": "broadcom_onu",
+ "vendor": "Broadcom",
+ "serial_number": "BRCM22222222",
+ "vendor_id": "BRCM",
+ "adapter": "broadcom_onu",
+ "vlan": 0,
+ "admin_state": "ENABLED",
+ "oper_status": "ACTIVE",
+ "connect_status": "REACHABLE",
+ "parent_id": "00010fc93996afea",
+ "parent_port_no": 1
+ }
+ ]
+ }
+
+ self.two_devices = {
+ "items": [
+ {
+ "id": "0001130158f01b2d",
+ "type": "broadcom_onu",
+ "vendor": "Broadcom",
+ "serial_number": "BRCM22222222",
+ "vendor_id": "BRCM",
+ "adapter": "broadcom_onu",
+ "vlan": 0,
+ "admin_state": "ENABLED",
+ "oper_status": "ACTIVE",
+ "connect_status": "REACHABLE",
+ "parent_id": "00010fc93996afea",
+ "parent_port_no": 1
+ },
+ {
+ "id": "0001130158f01b2e",
+ "type": "broadcom_onu",
+ "vendor": "Broadcom",
+ "serial_number": "BRCM22222223",
+ "vendor_id": "BRCM",
+ "adapter": "broadcom_onu",
+ "vlan": 0,
+ "admin_state": "ENABLED",
+ "oper_status": "ACTIVE",
+ "connect_status": "REACHABLE",
+ "parent_id": "00010fc93996afeb",
+ "parent_port_no": 1
+ }
+ ],
+ }
+
+ # TODO add ports
+ self.ports = {
+ "items": []
+ }
+
+ def tearDown(self):
+ sys.path = self.sys_path_save
+
+ @requests_mock.Mocker()
+ def test_missing_volt_service(self, m):
+ self.assertFalse(m.called)
+
+ @requests_mock.Mocker()
+ def test_pull(self, m):
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice.objects, "get") as mock_olt_device, \
+ patch.object(PONPort.objects, "get") as mock_pon_port, \
+ patch.object(ONUDevice, "save", autospec=True) as mock_save:
+ olt_service_mock.return_value = [self.volt_service]
+ mock_pon_port.return_value = self.pon_port
+ mock_olt_device.return_value = self.olt
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+ m.get("http://voltha_url:1234/api/v1/devices/0001130158f01b2d/ports", status_code=200, json=self.ports)
+
+ self.sync_step().pull_records()
+
+ saved_onu = mock_save.call_args[0][0]
+
+ self.assertEqual(saved_onu.admin_state, "ENABLED")
+ self.assertEqual(saved_onu.oper_status, "ACTIVE")
+ self.assertEqual(saved_onu.connect_status, "REACHABLE")
+ self.assertEqual(saved_onu.device_type, "broadcom_onu")
+ self.assertEqual(saved_onu.vendor, "Broadcom")
+ self.assertEqual(saved_onu.device_id, "0001130158f01b2d")
+
+ self.assertEqual(mock_save.call_count, 1)
+
+ @requests_mock.Mocker()
+ def test_pull_bad_pon(self, m):
+
+ def olt_side_effect(device_id):
+ # fail the first onu device
+ if device_id=="00010fc93996afea":
+ return self.olt
+ else:
+ return self.olt2
+
+ def pon_port_side_effect(mock_pon_port, port_no, olt_device_id):
+ # fail the first onu device
+ if olt_device_id==1:
+ raise IndexError()
+ return self.pon_port2
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice.objects, "get") as mock_olt_device, \
+ patch.object(PONPort.objects, "get") as mock_pon_port, \
+ patch.object(ONUDevice, "save", autospec=True) as mock_save:
+ olt_service_mock.return_value = [self.volt_service]
+ mock_pon_port.side_effect = functools.partial(pon_port_side_effect, self.pon_port)
+ mock_olt_device.side_effect = olt_side_effect
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.two_devices)
+ m.get("http://voltha_url:1234/api/v1/devices/0001130158f01b2d/ports", status_code=200, json=self.ports)
+ m.get("http://voltha_url:1234/api/v1/devices/0001130158f01b2e/ports", status_code=200, json=self.ports)
+
+ self.sync_step().pull_records()
+
+ self.assertEqual(mock_save.call_count, 1)
+ saved_onu = mock_save.call_args[0][0]
+
+ # we should get the second onu in self.two_onus
+
+ self.assertEqual(saved_onu.admin_state, "ENABLED")
+ self.assertEqual(saved_onu.oper_status, "ACTIVE")
+ self.assertEqual(saved_onu.connect_status, "REACHABLE")
+ self.assertEqual(saved_onu.device_type, "broadcom_onu")
+ self.assertEqual(saved_onu.vendor, "Broadcom")
+ self.assertEqual(saved_onu.device_id, "0001130158f01b2e")
+
+ self.assertEqual(mock_save.call_count, 1)
+
+ @requests_mock.Mocker()
+ def test_pull_bad_olt(self, m):
+
+ def olt_side_effect(device_id):
+ # fail the first onu device
+ if device_id=="00010fc93996afea":
+ raise IndexError()
+ else:
+ return self.olt2
+
+ with patch.object(VOLTService.objects, "all") as olt_service_mock, \
+ patch.object(OLTDevice.objects, "get") as mock_olt_device, \
+ patch.object(PONPort.objects, "get") as mock_pon_port, \
+ patch.object(ONUDevice, "save", autospec=True) as mock_save:
+ olt_service_mock.return_value = [self.volt_service]
+ mock_pon_port.return_value = self.pon_port2
+ mock_olt_device.side_effect = olt_side_effect
+
+ m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.two_devices)
+ m.get("http://voltha_url:1234/api/v1/devices/0001130158f01b2d/ports", status_code=200, json=self.ports)
+ m.get("http://voltha_url:1234/api/v1/devices/0001130158f01b2e/ports", status_code=200, json=self.ports)
+
+ self.sync_step().pull_records()
+
+ self.assertEqual(mock_save.call_count, 1)
+ saved_onu = mock_save.call_args[0][0]
+
+ # we should get the second onu in self.two_onus
+
+ self.assertEqual(saved_onu.admin_state, "ENABLED")
+ self.assertEqual(saved_onu.oper_status, "ACTIVE")
+ self.assertEqual(saved_onu.connect_status, "REACHABLE")
+ self.assertEqual(saved_onu.device_type, "broadcom_onu")
+ self.assertEqual(saved_onu.vendor, "Broadcom")
+ self.assertEqual(saved_onu.device_id, "0001130158f01b2e")
+
+ self.assertEqual(mock_save.call_count, 1)
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from time import sleep
+
+import requests
+from multistructlog import create_logger
+from requests.auth import HTTPBasicAuth
+from synchronizers.new_base.syncstep import SyncStep, DeferredException
+from synchronizers.new_base.modelaccessor import OLTDevice, model_accessor
+from xosconfig import Config
+
+import os, sys
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from helpers import Helpers
+
+log = create_logger(Config().get('logging'))
+
+class SyncOLTDevice(SyncStep):
+ provides = [OLTDevice]
+ observes = OLTDevice
+
+ max_attempt = 120 # we give 10 minutes to the OLT to activate
+
+ @staticmethod
+ def get_ids_from_logical_device(o):
+ voltha = Helpers.get_voltha_info(o.volt_service)
+
+ request = requests.get("%s:%d/api/v1/logical_devices" % (voltha['url'], voltha['port']))
+
+ if request.status_code != 200:
+ raise Exception("Failed to retrieve logical devices from VOLTHA: %s" % request.text)
+
+ response = request.json()
+
+ for ld in response["items"]:
+ if ld["root_device_id"] == o.device_id:
+ o.of_id = ld["id"]
+ o.dp_id = "of:%s" % (Helpers.datapath_id_to_hex(ld["datapath_id"])) # Convert to hex
+ return o
+
+ raise Exception("Can't find a logical_device for OLT device id: %s" % o.device_id)
+
+ def pre_provision_olt_device(self, model):
+ log.info("Pre-provisioning OLT device in VOLTHA", object=str(model), **model.tologdict())
+
+ voltha = Helpers.get_voltha_info(model.volt_service)
+
+ data = {
+ "type": model.device_type
+ }
+
+ if hasattr(model, "host") and hasattr(model, "port"):
+ data["host_and_port"] = "%s:%s" % (model.host, model.port)
+ elif hasattr(model, "mac_address"):
+ data["mac_address"] = model.mac_address
+
+ log.info("Pushing OLT to Voltha", data=data)
+
+ request = requests.post("%s:%d/api/v1/devices" % (voltha['url'], voltha['port']), json=data)
+
+ if request.status_code != 200:
+ raise Exception("Failed to add OLT device: %s" % request.text)
+
+ log.info("Add device response", text=request.text)
+
+ res = request.json()
+
+ log.info("Add device json res", res=res)
+
+ if not res['id']:
+ raise Exception(
+ 'VOLTHA Device Id is empty. This probably means that the OLT device is already provisioned in VOLTHA')
+ else:
+ model.device_id = res['id']
+ model.serial_number = res['serial_number']
+
+
+ return model
+
+ def activate_olt(self, model):
+
+ attempted = 0
+
+ voltha = Helpers.get_voltha_info(model.volt_service)
+
+ # Enable device
+ request = requests.post("%s:%d/api/v1/devices/%s/enable" % (voltha['url'], voltha['port'], model.device_id))
+
+ if request.status_code != 200:
+ raise Exception("Failed to enable OLT device: %s" % request.text)
+
+ model.backend_status = "Waiting for device to be activated"
+ model.save(always_update_timestamp=False) # we don't want to kickoff a new loop
+
+ # Read state
+ request = requests.get("%s:%d/api/v1/devices/%s" % (voltha['url'], voltha['port'], model.device_id)).json()
+ while request['oper_status'] == "ACTIVATING" and attempted < self.max_attempt:
+ log.info("Waiting for OLT device %s (%s) to activate" % (model.name, model.device_id))
+ sleep(5)
+ request = requests.get("%s:%d/api/v1/devices/%s" % (voltha['url'], voltha['port'], model.device_id)).json()
+ attempted = attempted + 1
+
+
+ model.admin_state = request['admin_state']
+ model.oper_status = request['oper_status']
+ model.serial_number = request['serial_number']
+
+ if model.oper_status != "ACTIVE":
+ raise Exception("It was not possible to activate OLTDevice with id %s" % model.id)
+
+ # Find the of_id of the device
+ model = self.get_ids_from_logical_device(model)
+ model.save()
+
+ return model
+
+ def configure_onos(self, model):
+
+ log.info("Adding OLT device in onos-voltha", object=str(model), **model.tologdict())
+
+ onos_voltha = Helpers.get_onos_voltha_info(model.volt_service)
+ onos_voltha_basic_auth = HTTPBasicAuth(onos_voltha['user'], onos_voltha['pass'])
+
+ # Add device info to onos-voltha
+ data = {
+ "devices": {
+ model.dp_id: {
+ "basic": {
+ "name": model.name
+ }
+ }
+ }
+ }
+
+ log.info("Calling ONOS", data=data)
+
+ url = "%s:%d/onos/v1/network/configuration/" % (onos_voltha['url'], onos_voltha['port'])
+ request = requests.post(url, json=data, auth=onos_voltha_basic_auth)
+
+ if request.status_code != 200:
+ log.error(request.text)
+ raise Exception("Failed to add OLT device %s into ONOS" % model.name)
+ else:
+ try:
+ print request.json()
+ except Exception:
+ print request.text
+ return model
+
+ def sync_record(self, model):
+ log.info("Synching device", object=str(model), **model.tologdict())
+
+ # If the device has feedback_state is already present in voltha
+ if not model.device_id and not model.admin_state and not model.oper_status and not model.of_id:
+ log.info("Pushing OLT device to VOLTHA", object=str(model), **model.tologdict())
+ model = self.pre_provision_olt_device(model)
+ self.activate_olt(model)
+ elif model.oper_status != "ACTIVE":
+ raise Exception("It was not possible to activate OLTDevice with id %s" % model.id)
+ else:
+ log.info("OLT device already exists in VOLTHA", object=str(model), **model.tologdict())
+
+ self.configure_onos(model)
+
+ def delete_record(self, model):
+ log.info("Deleting OLT device", object=str(model), **model.tologdict())
+
+ voltha = Helpers.get_voltha_info(model.volt_service)
+
+ if not model.device_id or model.backend_code == 2:
+ # NOTE if the device was not synchronized, just remove it from the data model
+ log.warning("OLTDevice %s has no device_id, it was never saved in VOLTHA" % model.name)
+ return
+ else:
+ try:
+ # Disable the OLT device
+ request = requests.post("%s:%d/api/v1/devices/%s/disable" % (voltha['url'], voltha['port'], model.device_id))
+
+ if request.status_code != 200:
+ log.error("Failed to disable OLT device in VOLTHA: %s - %s" % (model.name, model.device_id), rest_response=request.text, rest_status_code=request.status_code)
+ raise Exception("Failed to disable OLT device in VOLTHA")
+
+ # NOTE [teo] wait some time after the disable to let VOLTHA doing its things
+ i = 0
+ for i in list(reversed(range(10))):
+ sleep(1)
+ log.info("Deleting the OLT in %s seconds" % i)
+
+ # Delete the OLT device
+ request = requests.delete("%s:%d/api/v1/devices/%s/delete" % (voltha['url'], voltha['port'], model.device_id))
+
+ if request.status_code != 200:
+ log.error("Failed to delete OLT device from VOLTHA: %s - %s" % (model.name, model.device_id), rest_response=request.text, rest_status_code=request.status_code)
+ raise Exception("Failed to delete OLT device from VOLTHA")
+ except requests.ConnectionError:
+ log.warning("ConnectionError when contacting Voltha in OLT delete step", name=model.name, device_id=model.device_id)
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import os, sys
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from helpers import Helpers
+
+import requests
+from multistructlog import create_logger
+from requests.auth import HTTPBasicAuth
+from synchronizers.new_base.modelaccessor import ONUDevice, model_accessor
+from synchronizers.new_base.syncstep import SyncStep
+from xosconfig import Config
+
+log = create_logger(Config().get("logging"))
+
+class SyncONUDevice(SyncStep):
+ provides = [ONUDevice]
+
+ observes = ONUDevice
+
+ def disable_onu(self, o):
+ volt_service = o.pon_port.olt_device.volt_service
+ voltha = Helpers.get_voltha_info(volt_service)
+
+ log.info("Disabling device %s in voltha" % o.device_id)
+ request = requests.post("%s:%d/api/v1/devices/%s/disable" % (voltha['url'], voltha['port'], o.device_id))
+
+ if request.status_code != 200:
+ raise Exception("Failed to disable ONU device %s: %s" % (o.serial_number, request.text))
+
+ def enable_onu(self, o):
+ volt_service = o.pon_port.olt_device.volt_service
+ voltha = Helpers.get_voltha_info(volt_service)
+
+ log.info("Enabling device %s in voltha" % o.device_id)
+ request = requests.post("%s:%d/api/v1/devices/%s/enable" % (voltha['url'], voltha['port'], o.device_id))
+
+ if request.status_code != 200:
+ raise Exception("Failed to enable ONU device %s: %s" % (o.serial_number, request.text))
+
+ def sync_record(self, o):
+
+ if o.admin_state == "DISABLED":
+ self.disable_onu(o)
+ if o.admin_state == "ENABLED":
+ self.enable_onu(o)
\ No newline at end of file
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import os, sys
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from helpers import Helpers
+
+import requests
+from multistructlog import create_logger
+from requests.auth import HTTPBasicAuth
+from synchronizers.new_base.modelaccessor import VOLTService, VOLTServiceInstance, ServiceInstance, model_accessor
+from synchronizers.new_base.syncstep import SyncStep, DeferredException
+from xosconfig import Config
+
+log = create_logger(Config().get("logging"))
+
+class SyncVOLTServiceInstance(SyncStep):
+ provides = [VOLTServiceInstance]
+
+ observes = VOLTServiceInstance
+
+ def sync_record(self, o):
+
+ if o.policy_code != 1:
+ raise DeferredException("Waiting for ModelPolicy to complete")
+
+ volt_service = VOLTService.objects.get(id=o.owner_id)
+
+ log.info("Synching OLTServiceInstance", object=str(o), **o.tologdict())
+
+ olt_device = o.onu_device.pon_port.olt_device
+
+ try:
+ # NOTE each ONU has only one UNI port!
+ uni_port_id = o.onu_device.uni_ports.first().port_no
+ except AttributeError:
+ # This is because the ONUDevice is set by model_policy
+ raise DeferredException("Waiting for ONUDevice %s " % olt_device.name)
+
+ if not olt_device.dp_id:
+ raise DeferredException("Waiting for OLTDevice %s to be synchronized" % olt_device.name)
+
+ log.debug("Adding subscriber with info",
+ uni_port_id = uni_port_id,
+ dp_id = olt_device.dp_id
+ )
+
+ # Send request to ONOS
+ onos_voltha = Helpers.get_onos_voltha_info(volt_service)
+ onos_voltha_basic_auth = HTTPBasicAuth(onos_voltha['user'], onos_voltha['pass'])
+
+ handle = "%s/%s" % (olt_device.dp_id, uni_port_id)
+
+ full_url = "%s:%d/onos/olt/oltapp/%s" % (onos_voltha['url'], onos_voltha['port'], handle)
+
+ log.info("Sending request to onos-voltha", url=full_url)
+
+ request = requests.post(full_url, auth=onos_voltha_basic_auth)
+
+ if request.status_code != 200:
+ raise Exception("Failed to add subscriber in onos-voltha: %s" % request.text)
+
+ o.backend_handle = handle
+ o.save(update_fields=["backend_handle"])
+
+ log.info("Added Subscriber in onos voltha", response=request.text)
+
+ def delete_record(self, o):
+
+ log.info("Removing OLTServiceInstance", object=str(o), **o.tologdict())
+
+ volt_service = VOLTService.objects.get(id=o.owner_id)
+ onos_voltha = Helpers.get_onos_voltha_info(volt_service)
+ onos_voltha_basic_auth = HTTPBasicAuth(onos_voltha['user'], onos_voltha['pass'])
+
+ full_url = "%s:%d/onos/olt/oltapp/%s" % (onos_voltha['url'], onos_voltha['port'], o.backend_handle)
+
+ request = requests.delete(full_url, auth=onos_voltha_basic_auth)
+
+ if request.status_code != 204:
+ raise Exception("Failed to remove subscriber from onos-voltha: %s" % request.text)
+
+ log.info("Removed Subscriber from onos voltha", response=request.text)
\ No newline at end of file
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from requests import ConnectionError
+import unittest
+import functools
+from mock import patch, call, Mock, PropertyMock
+import requests_mock
+
+import os, sys
+
+# Hack to load synchronizer framework
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir = os.path.join(xos_dir, "../../xos_services")
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END of hack to load synchronizer framework
+
+# Generate model from xproto
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+def match_onos_req(req):
+ request = req.json()['devices']
+ if not request['of:0000000ce2314000']:
+ return False
+ else:
+ if not request['of:0000000ce2314000']['basic']['driver'] == 'voltha':
+ return False
+ if not request['of:0000000ce2314000']['accessDevice']['vlan'] == 1 or not request['of:0000000ce2314000']['accessDevice']['uplink'] == "129":
+ return False
+ return True
+
+def match_json(desired, req):
+ if desired!=req.json():
+ raise Exception("Got request %s, but body is not matching" % req.url)
+ return False
+ return True
+
+class TestSyncOLTDevice(unittest.TestCase):
+ def setUp(self):
+ global DeferredException
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ # Setting up the config module
+ from xosconfig import Config
+ config = os.path.join(test_path, "../test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+ # END setting up the config module
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ # build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto")])
+
+ # FIXME this is to get jenkins to pass the tests, somehow it is running tests in a different order
+ # and apparently it is not overriding the generated model accessor
+ build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto")])
+
+ import synchronizers.new_base.modelaccessor
+ from sync_olt_device import SyncOLTDevice, DeferredException
+ self.sync_step = SyncOLTDevice
+
+ pon_port = Mock()
+ pon_port.port_id = "00ff00"
+
+ # Create a mock OLTDevice
+ o = Mock()
+ o.volt_service.voltha_url = "voltha_url"
+ o.volt_service.voltha_port = 1234
+ o.volt_service.voltha_user = "voltha_user"
+ o.volt_service.voltha_pass = "voltha_pass"
+
+ o.volt_service.onos_voltha_port = 4321
+ o.volt_service.onos_voltha_url = "onos"
+ o.volt_service.onos_voltha_user = "karaf"
+ o.volt_service.onos_voltha_pass = "karaf"
+
+ o.device_type = "ponsim_olt"
+ o.host = "172.17.0.1"
+ o.port = "50060"
+ o.uplink = "129"
+ o.driver = "voltha"
+ o.name = "Test Device"
+
+ # feedback state
+ o.device_id = None
+ o.admin_state = None
+ o.oper_status = None
+ o.of_id = None
+ o.id = 1
+
+ o.tologdict.return_value = {'name': "Mock VOLTServiceInstance"}
+
+ o.save.return_value = "Saved"
+
+ o.pon_ports.all.return_value = [pon_port]
+
+ self.o = o
+
+ self.voltha_devices_response = {"id": "123", "serial_number": "foobar"}
+
+ def tearDown(self):
+ self.o = None
+ sys.path = self.sys_path_save
+
+ @requests_mock.Mocker()
+ def test_get_of_id_from_device(self, m):
+ logical_devices = {
+ "items": [
+ {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
+ {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
+ ]
+ }
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
+ self.o.device_id = "123"
+ self.o = self.sync_step.get_ids_from_logical_device(self.o)
+ self.assertEqual(self.o.of_id, "0001000ce2314000")
+ self.assertEqual(self.o.dp_id, "of:0000000ce2314000")
+
+ with self.assertRaises(Exception) as e:
+ self.o.device_id = "idonotexist"
+ self.sync_step.get_ids_from_logical_device(self.o)
+ self.assertEqual(e.exception.message, "Can't find a logical_device for OLT device id: idonotexist")
+
+ @requests_mock.Mocker()
+ def test_sync_record_fail_add(self, m):
+ """
+ Should print an error if we can't add the device in VOLTHA
+ """
+ m.post("http://voltha_url:1234/api/v1/devices", status_code=500, text="MockError")
+
+ with self.assertRaises(Exception) as e:
+ self.sync_step().sync_record(self.o)
+ self.assertEqual(e.exception.message, "Failed to add OLT device: MockError")
+
+ @requests_mock.Mocker()
+ def test_sync_record_fail_no_id(self, m):
+ """
+ Should print an error if VOLTHA does not return the device id
+ """
+ m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json={"id": ""})
+
+ with self.assertRaises(Exception) as e:
+ self.sync_step().sync_record(self.o)
+ self.assertEqual(e.exception.message, "VOLTHA Device Id is empty. This probably means that the OLT device is already provisioned in VOLTHA")
+
+ @requests_mock.Mocker()
+ def test_sync_record_fail_enable(self, m):
+ """
+ Should print an error if device.enable fails
+ """
+ m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response)
+ m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=500, text="EnableError")
+
+ with self.assertRaises(Exception) as e:
+ self.sync_step().sync_record(self.o)
+
+ self.assertEqual(e.exception.message, "Failed to enable OLT device: EnableError")
+
+ @requests_mock.Mocker()
+ def test_sync_record_success(self, m):
+ """
+ If device.enable succed should fetch the state, retrieve the of_id and push it to ONOS
+ """
+
+ expected_conf = {
+ "type": self.o.device_type,
+ "host_and_port": "%s:%s" % (self.o.host, self.o.port)
+ }
+
+ m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response, additional_matcher=functools.partial(match_json, expected_conf))
+ m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
+ m.get("http://voltha_url:1234/api/v1/devices/123", json={"oper_status": "ACTIVE", "admin_state": "ENABLED", "serial_number": "foobar"})
+ logical_devices = {
+ "items": [
+ {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
+ {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
+ ]
+ }
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
+
+ onos_expected_conf = {
+ "devices": {
+ "of:0000000ce2314000": {
+ "basic": {
+ "name": self.o.name
+ }
+ }
+ }
+ }
+ m.post("http://onos:4321/onos/v1/network/configuration/", status_code=200, json=onos_expected_conf,
+ additional_matcher=functools.partial(match_json, onos_expected_conf))
+
+ self.sync_step().sync_record(self.o)
+ self.assertEqual(self.o.admin_state, "ENABLED")
+ self.assertEqual(self.o.oper_status, "ACTIVE")
+ self.assertEqual(self.o.serial_number, "foobar")
+ self.assertEqual(self.o.of_id, "0001000ce2314000")
+ self.assertEqual(self.o.save.call_count, 2) # we're updating the backend_status when activating and then adding logical device ids
+
+ @requests_mock.Mocker()
+ def test_sync_record_success_mac_address(self, m):
+ """
+ A device should be pre-provisioned via mac_address, the the process is the same
+ """
+
+ del self.o.host
+ del self.o.port
+ self.o.mac_address = "00:0c:e2:31:40:00"
+
+ expected_conf = {
+ "type": self.o.device_type,
+ "mac_address": self.o.mac_address
+ }
+
+ onos_expected_conf = {
+ "devices": {
+ "of:0000000ce2314000": {
+ "basic": {
+ "name": self.o.name
+ }
+ }
+ }
+ }
+ m.post("http://onos:4321/onos/v1/network/configuration/", status_code=200, json=onos_expected_conf,
+ additional_matcher=functools.partial(match_json, onos_expected_conf))
+
+ m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response,
+ additional_matcher=functools.partial(match_json, expected_conf))
+ m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
+ m.get("http://voltha_url:1234/api/v1/devices/123", json={"oper_status": "ACTIVE", "admin_state": "ENABLED", "serial_number": "foobar"})
+ logical_devices = {
+ "items": [
+ {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
+ {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
+ ]
+ }
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
+
+ self.sync_step().sync_record(self.o)
+ self.assertEqual(self.o.admin_state, "ENABLED")
+ self.assertEqual(self.o.oper_status, "ACTIVE")
+ self.assertEqual(self.o.of_id, "0001000ce2314000")
+ self.assertEqual(self.o.save.call_count, 2)
+
+ @requests_mock.Mocker()
+ def test_sync_record_enable_timeout(self, m):
+ """
+ If device.enable fails we need to tell the suer
+ """
+
+ expected_conf = {
+ "type": self.o.device_type,
+ "host_and_port": "%s:%s" % (self.o.host, self.o.port)
+ }
+
+ m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response,
+ additional_matcher=functools.partial(match_json, expected_conf))
+ m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
+ m.get("http://voltha_url:1234/api/v1/devices/123", [
+ {"json": {"oper_status": "ACTIVATING", "admin_state": "ENABLED", "serial_number": "foobar"}, "status_code": 200},
+ {"json": {"oper_status": "ERROR", "admin_state": "FAILED", "serial_number": "foobar"}, "status_code": 200}
+ ])
+
+ logical_devices = {
+ "items": [
+ {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
+ {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
+ ]
+ }
+ m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
+
+ with self.assertRaises(Exception) as e:
+ self.sync_step().sync_record(self.o)
+
+ self.assertEqual(e.exception.message, "It was not possible to activate OLTDevice with id 1")
+ self.assertEqual(self.o.oper_status, "ERROR")
+ self.assertEqual(self.o.admin_state, "FAILED")
+ self.assertEqual(self.o.save.call_count, 1)
+
+ @requests_mock.Mocker()
+ def test_sync_record_already_existing_in_voltha(self, m):
+ # mock device feedback state
+ self.o.device_id = "123"
+ self.o.admin_state = "ENABLED"
+ self.o.oper_status = "ACTIVE"
+ self.o.dp_id = "of:0000000ce2314000"
+ self.o.of_id = "0001000ce2314000"
+
+ expected_conf = {
+ "devices": {
+ self.o.dp_id: {
+ "basic": {
+ "name": self.o.name
+ }
+ }
+ }
+ }
+ m.post("http://onos:4321/onos/v1/network/configuration/", status_code=200, json=expected_conf,
+ additional_matcher=functools.partial(match_json, expected_conf))
+
+ self.sync_step().sync_record(self.o)
+ self.o.save.assert_not_called()
+
+ @requests_mock.Mocker()
+ def test_delete_record(self, m):
+ self.o.of_id = "0001000ce2314000"
+ self.o.device_id = "123"
+
+ m.post("http://voltha_url:1234/api/v1/devices/123/disable", status_code=200)
+ m.delete("http://voltha_url:1234/api/v1/devices/123/delete", status_code=200)
+
+ self.sync_step().delete_record(self.o)
+
+ self.assertEqual(m.call_count, 2)
+
+ @patch('requests.post')
+ def test_delete_record_connectionerror(self, m):
+ self.o.of_id = "0001000ce2314000"
+ self.o.device_id = "123"
+
+ m.side_effect = ConnectionError()
+
+ self.sync_step().delete_record(self.o)
+
+ # No exception thrown, as ConnectionError will be caught
+
+
+ @requests_mock.Mocker()
+ def test_delete_unsynced_record(self, m):
+
+ self.sync_step().delete_record(self.o)
+
+ self.assertEqual(m.call_count, 0)
+
+if __name__ == "__main__":
+ unittest.main()
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import unittest
+from mock import patch, call, Mock, PropertyMock
+import requests_mock
+
+import os, sys
+
+# Hack to load synchronizer framework
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir = os.path.join(xos_dir, "../../xos_services")
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+class TestSyncVOLTServiceInstance(unittest.TestCase):
+ def setUp(self):
+
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ # Setting up the config module
+ from xosconfig import Config
+ config = os.path.join(test_path, "../test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+ # END Setting up the config module
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ # build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto")])
+
+ # FIXME this is to get jenkins to pass the tests, somehow it is running tests in a different order
+ # and apparently it is not overriding the generated model accessor
+ build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto")])
+ import synchronizers.new_base.modelaccessor
+ from synchronizers.new_base.syncstep import DeferredException
+ from sync_onu_device import SyncONUDevice, model_accessor
+
+ # import all class names to globals
+ for (k, v) in model_accessor.all_model_classes.items():
+ globals()[k] = v
+
+ self.sync_step = SyncONUDevice
+
+ volt_service = Mock()
+ volt_service.voltha_url = "voltha_url"
+ volt_service.voltha_port = 1234
+ volt_service.voltha_user = "voltha_user"
+ volt_service.voltha_pass = "voltha_pass"
+
+ self.o = Mock()
+ self.o.device_id = "test_id"
+ self.o.pon_port.olt_device.volt_service = volt_service
+
+ def tearDown(self):
+ self.o = None
+ sys.path = self.sys_path_save
+
+ @requests_mock.Mocker()
+ def test_enable(self, m):
+ m.post("http://voltha_url:1234/api/v1/devices/test_id/enable")
+
+ self.o.admin_state = "ENABLED"
+ self.sync_step().sync_record(self.o)
+ self.assertTrue(m.called)
+
+ @requests_mock.Mocker()
+ def test_disable(self, m):
+ m.post("http://voltha_url:1234/api/v1/devices/test_id/disable")
+
+ self.o.admin_state = "DISABLED"
+ self.sync_step().sync_record(self.o)
+ self.assertTrue(m.called)
+
+ @requests_mock.Mocker()
+ def test_disable_fail(self, m):
+ m.post("http://voltha_url:1234/api/v1/devices/test_id/disable", status_code=500, text="Mock Error")
+
+ self.o.admin_state = "DISABLED"
+
+ with self.assertRaises(Exception) as e:
+ self.sync_step().sync_record(self.o)
+ self.assertTrue(m.called)
+ self.assertEqual(e.exception.message, "Failed to disable ONU device: Mock Error")
+
+if __name__ == "__main__":
+ unittest.main()
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import unittest
+import functools
+from mock import patch, call, Mock, PropertyMock
+import requests_mock
+
+import os, sys
+
+# Hack to load synchronizer framework
+test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+xos_dir=os.path.join(test_path, "../../..")
+if not os.path.exists(os.path.join(test_path, "new_base")):
+ xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+ services_dir = os.path.join(xos_dir, "../../xos_services")
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+ name = os.path.join(service_name, "xos", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ else:
+ name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+ if os.path.exists(os.path.join(services_dir, name)):
+ return name
+ raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+class TestSyncVOLTServiceInstance(unittest.TestCase):
+ def setUp(self):
+ global DeferredException
+
+ self.sys_path_save = sys.path
+ sys.path.append(xos_dir)
+ sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+ # Setting up the config module
+ from xosconfig import Config
+ config = os.path.join(test_path, "../test_config.yaml")
+ Config.clear()
+ Config.init(config, "synchronizer-config-schema.yaml")
+ # END Setting up the config module
+
+ from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+ # build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto")])
+
+ # FIXME this is to get jenkins to pass the tests, somehow it is running tests in a different order
+ # and apparently it is not overriding the generated model accessor
+ build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
+ get_models_fn("vsg", "vsg.xproto"),
+ get_models_fn("../profiles/rcord", "rcord.xproto")])
+ import synchronizers.new_base.modelaccessor
+ from synchronizers.new_base.syncstep import DeferredException
+ from sync_volt_service_instance import SyncVOLTServiceInstance, model_accessor
+
+ # import all class names to globals
+ for (k, v) in model_accessor.all_model_classes.items():
+ globals()[k] = v
+
+ self.sync_step = SyncVOLTServiceInstance
+
+ volt_service = Mock()
+ volt_service.onos_voltha_url = "onos_voltha_url"
+ volt_service.onos_voltha_port = 4321
+ volt_service.onos_voltha_user = "onos_voltha_user"
+ volt_service.onos_voltha_pass = "onos_voltha_pass"
+
+ uni_port = Mock()
+ uni_port.port_no = "uni_port_id"
+
+ onu_device = Mock()
+ onu_device.name = "BRCM1234"
+ onu_device.pon_port.olt_device.dp_id = None
+ onu_device.pon_port.olt_device.name = "Test OLT Device"
+ onu_device.uni_ports.first.return_value = uni_port
+
+ # create a mock service instance
+ o = Mock()
+ o.policy_code = 1
+ o.id = 1
+ o.owner_id = "volt_service"
+ o.onu_device = onu_device
+ o.tologdict.return_value = {}
+
+ self.o = o
+ self.onu_device = onu_device
+ self.volt_service = volt_service
+
+ def tearDown(self):
+ self.o = None
+ sys.path = self.sys_path_save
+
+ @requests_mock.Mocker()
+ def test_do_not_sync(self, m):
+ self.onu_device.pon_port.olt_device.dp_id = None
+
+ with patch.object(VOLTService.objects, "get") as olt_service_mock:
+ olt_service_mock.return_value = self.volt_service
+
+ with self.assertRaises(DeferredException) as e:
+ self.sync_step().sync_record(self.o)
+
+ self.assertFalse(m.called)
+ self.assertEqual(e.exception.message, "Waiting for OLTDevice Test OLT Device to be synchronized")
+
+ @requests_mock.Mocker()
+ def test_do_sync(self, m):
+
+ self.onu_device.pon_port.olt_device.dp_id = "of:dp_id"
+
+ m.post("http://onos_voltha_url:4321/onos/olt/oltapp/of:dp_id/uni_port_id", status_code=200, json={})
+
+ with patch.object(VOLTService.objects, "get") as olt_service_mock:
+ olt_service_mock.return_value = self.volt_service
+
+ self.sync_step().sync_record(self.o)
+ self.assertTrue(m.called)
+ self.assertEqual(self.o.backend_handle, "of:dp_id/uni_port_id")
+
+ @requests_mock.Mocker()
+ def test_do_sync_fail(self, m):
+
+ m.post("http://onos_voltha_url:4321/onos/olt/oltapp/of:dp_id/uni_port_id", status_code=500, text="Mock Error")
+
+ self.onu_device.pon_port.olt_device.dp_id = "of:dp_id"
+
+ with patch.object(VOLTService.objects, "get") as olt_service_mock:
+ olt_service_mock.return_value = self.volt_service
+
+ with self.assertRaises(Exception) as e:
+ self.sync_step().sync_record(self.o)
+ self.assertTrue(m.called)
+ self.assertEqual(e.exception.message, "Failed to add subscriber in onos voltha: Mock Error")
+
+ @requests_mock.Mocker()
+ def test_delete(self, m):
+ m.delete("http://onos_voltha_url:4321/onos/olt/oltapp/of:dp_id/uni_port_id", status_code=204)
+
+ self.onu_device.pon_port.olt_device.dp_id = "of:dp_id"
+ self.o.backend_handle = "of:dp_id/uni_port_id"
+
+ with patch.object(VOLTService.objects, "get") as olt_service_mock:
+ olt_service_mock.return_value = self.volt_service
+
+ self.sync_step().delete_record(self.o)
+ self.assertTrue(m.called)
+ self.assertEqual(m.call_count, 1)
+
+if __name__ == "__main__":
+ unittest.main()
--- /dev/null
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+name: test-model-policies
+accessor:
+ username: xosadmin@opencord.org
+ password: "sample"
+ kind: "testframework"
+logging:
+ version: 1
+ handlers:
+ console:
+ class: logging.StreamHandler
+ loggers:
+ 'multistructlog':
+ handlers:
+ - console
+# level: DEBUG
--- /dev/null
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import unittest
+
+from mock import Mock
+
+from helpers import Helpers
+
+
+class TestHelpers(unittest.TestCase):
+
+ def setUp(self):
+ # Create a mock service instance
+ o = Mock()
+ o.voltha_url = "voltha_url"
+ o.voltha_port = 1234
+ o.voltha_user = "voltha_user"
+ o.voltha_pass = "voltha_pass"
+ o.onos_voltha_url = "onos_voltha_url"
+ o.onos_voltha_port = 4321
+ o.onos_voltha_user = "onos_voltha_user"
+ o.onos_voltha_pass = "onos_voltha_pass"
+
+ self.o = o
+
+ def test_format_url(self):
+ url = Helpers.format_url("onf.com")
+ self.assertEqual(url, "http://onf.com")
+ url = Helpers.format_url("http://onf.com")
+ self.assertEqual(url, "http://onf.com")
+
+ def test_get_voltha_info(self):
+ voltha_dict = Helpers.get_voltha_info(self.o)
+
+ self.assertEqual(voltha_dict["url"], "http://voltha_url")
+ self.assertEqual(voltha_dict["port"], 1234)
+ self.assertEqual(voltha_dict["user"], "voltha_user")
+ self.assertEqual(voltha_dict["pass"], "voltha_pass")
+
+ def test_get_onos_info(self):
+ onos_voltha_dict = Helpers.get_onos_voltha_info(self.o)
+
+ self.assertEqual(onos_voltha_dict["url"], "http://onos_voltha_url")
+ self.assertEqual(onos_voltha_dict["port"], 4321)
+ self.assertEqual(onos_voltha_dict["user"], "onos_voltha_user")
+ self.assertEqual(onos_voltha_dict["pass"], "onos_voltha_pass")
+
+ def test_datapath_id_to_hex(self):
+ hex = Helpers.datapath_id_to_hex(55334486016)
+ self.assertEqual(hex, "0000000ce2314000")
+
+ hex = Helpers.datapath_id_to_hex("55334486016")
+ self.assertEqual(hex, "0000000ce2314000")
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+# This imports and runs ../../xos-observer.py
+
+import importlib
+import os
+import sys
+from xosconfig import Config
+
+base_config_file = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/config.yaml')
+mounted_config_file = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/mounted_config.yaml')
+
+if os.path.isfile(mounted_config_file):
+ Config.init(base_config_file, 'synchronizer-config-schema.yaml', mounted_config_file)
+else:
+ Config.init(base_config_file, 'synchronizer-config-schema.yaml')
+
+observer_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"../../synchronizers/new_base")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
--- /dev/null
+[unittest]
+plugins=nose2.plugins.junitxml
+code-directories=synchronizer
+ model_policies
+ steps
+ pull_steps
+ event_steps
+ models
+
+[coverage]
+always-on = True
+coverage = synchronizer
+coverage-report = term
+coverage-report = html
+coverage-report = xml