├── .github ├── dependabot.yml └── workflows │ ├── build-test.yml │ ├── code-scan.yml │ └── release.yml ├── .gitignore ├── .idea ├── modules.xml ├── rimedo-ts.iml └── vcs.xml ├── .reuse └── dep5 ├── CODE_OF_CONDUCT.md ├── LICENSES └── Apache-2.0.txt ├── Makefile ├── README.md ├── VERSION ├── build ├── bin │ └── version_check.sh └── rimedo-ts │ └── Dockerfile ├── cmd ├── rimedo-ts-test │ └── rimedo-ts-test.go └── rimedo-ts │ └── rimedo-ts.go ├── go.mod ├── go.sum ├── images ├── blog.png ├── demo.gif ├── demo_slice_5qi.gif ├── install.gif ├── linkedin.png ├── rimedo_ts.png └── web.png ├── pkg ├── manager │ └── manager.go ├── mho │ ├── data.go │ ├── mho.go │ └── reader.go ├── monitoring │ └── monitor.go ├── northbound │ └── a1 │ │ ├── a1ei-service.go │ │ ├── a1p-service.go │ │ └── manager.go ├── policy │ └── manager.go ├── rnib │ └── rnib.go ├── sdran │ └── manager.go └── southbound │ └── e2 │ └── manager.go └── test ├── ts ├── suite.go └── ts.go └── utils ├── a1.go ├── defs.go ├── file_io.go ├── rnib.go ├── sdran.go └── ts.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2024 Intel Corporation 3 | 4 | version: 2 5 | updates: 6 | 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | 12 | - package-ecosystem: "docker" 13 | directory: "build" 14 | schedule: 15 | interval: "weekly" 16 | 17 | - package-ecosystem: "gomod" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2024 Intel Corporation 3 | 4 | name: Build and test workflow 5 | on: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version-file: 'go.mod' 21 | - name: build 22 | run: make build 23 | test: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-go@v5 28 | with: 29 | go-version-file: 'go.mod' 30 | - name: Unit tests 31 | run: make test 32 | -------------------------------------------------------------------------------- /.github/workflows/code-scan.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2024 Intel Corporation 3 | 4 | name: Code scan workflow 5 | 6 | on: 7 | pull_request: 8 | branches: 9 | - master 10 | push: 11 | branches: 12 | - master 13 | 14 | jobs: 15 | version-check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: check version 22 | run: make check-version 23 | lint: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-go@v5 28 | with: 29 | go-version-file: 'go.mod' 30 | - name: golang-lint 31 | run: make lint 32 | license: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: check license 37 | run: make license 38 | fossa-check: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | - name: FOSSA scan 43 | uses: fossa-contrib/fossa-action@v3 44 | with: 45 | fossa-api-key: 6d304c09a3ec097ba4517724e4a4d17d 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2024 Intel Corporation 3 | # Copyright 2024 Kyunghee University 4 | name: Publish image and tag/release code 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | version-check: 13 | if: (github.repository_owner == 'onosproject') 14 | runs-on: ubuntu-latest 15 | outputs: 16 | valid_version: ${{ steps.version-check-step.outputs.valid_version }} 17 | dev_version: ${{ steps.dev-version-check-step.outputs.dev_version }} 18 | target_version: ${{ steps.get-target-version-step.outputs.target_version }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: check version 25 | id: version-check-step 26 | run: | 27 | make check-version; if [[ $? == 0 ]]; then echo "valid_version=true" >> $GITHUB_OUTPUT; else echo "valid_version=false" >> $GITHUB_OUTPUT; fi 28 | cat $GITHUB_OUTPUT 29 | 30 | - name: check dev version 31 | id: dev-version-check-step 32 | run: | 33 | f_dev=$(./build/bin/version_check.sh is_dev) 34 | if [[ $f_dev == "true" ]]; then echo "dev_version=true" >> $GITHUB_OUTPUT; else echo "dev_version=false" >> $GITHUB_OUTPUT; fi 35 | cat $GITHUB_OUTPUT 36 | 37 | - name: get target version 38 | id: get-target-version-step 39 | run: | 40 | echo "target_version=$(cat VERSION)" >> $GITHUB_OUTPUT 41 | cat $GITHUB_OUTPUT 42 | 43 | tag_versions: 44 | runs-on: ubuntu-latest 45 | needs: version-check 46 | if: (github.repository_owner == 'onosproject') && (needs.version-check.outputs.valid_version == 'true') && (needs.version-check.outputs.dev_version == 'false') 47 | steps: 48 | - uses: actions/checkout@v4 49 | with: 50 | fetch-depth: 0 51 | - name: create release using REST API 52 | run: | 53 | curl -L \ 54 | -X POST \ 55 | -H "Accept: application/vnd.github+json" \ 56 | -H "Authorization: Bearer ${{ secrets.GH_ONOS_PAT }}" \ 57 | -H "X-GitHub-Api-Version: 2022-11-28" \ 58 | https://api.github.com/repos/${{ github.repository }}/releases \ 59 | -d '{ 60 | "tag_name": "v${{ needs.version-check.outputs.target_version }}", 61 | "target_commitish": "${{ github.event.repository.default_branch }}", 62 | "name": "v${{ needs.version-check.outputs.target_version }}", 63 | "draft": false, 64 | "prerelease": false, 65 | "generate_release_notes": true 66 | }' 67 | 68 | publish-images: 69 | runs-on: ubuntu-latest 70 | needs: version-check 71 | if: (github.repository_owner == 'onosproject') && (needs.version-check.outputs.valid_version == 'true') 72 | env: 73 | REGISTRY: docker.io 74 | DOCKER_REPOSITORY: onosproject/ 75 | steps: 76 | - uses: actions/checkout@v4 77 | with: 78 | fetch-depth: 0 79 | - uses: actions/setup-go@v5 80 | with: 81 | go-version-file: 'go.mod' 82 | - uses: docker/login-action@v3.2.0 83 | with: 84 | registry: ${{ env.REGISTRY }} 85 | username: ${{ secrets.DOCKER_USERNAME }} 86 | password: ${{ secrets.DOCKER_PASSWORD }} 87 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 88 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 89 | - name: Build and push Docker image with tag latest 90 | env: 91 | DOCKER_TAG: latest 92 | run: | 93 | RIMEDO_TS_VERSION=${{ env.DOCKER_TAG }} make docker-build 94 | RIMEDO_TS_VERSION=${{ env.DOCKER_TAG }} make docker-push 95 | - name: Build and push Docker image with tag 96 | if: needs.version-check.outputs.dev_version == 'false' 97 | env: 98 | DOCKER_TAG: v${{ needs.version-check.outputs.target_version }} 99 | run: | 100 | RIMEDO_TS_VERSION=${{ env.DOCKER_TAG }} make docker-build 101 | RIMEDO_TS_VERSION=${{ env.DOCKER_TAG }} make docker-push 102 | 103 | bump-up-version: 104 | runs-on: ubuntu-latest 105 | needs: version-check 106 | if: (github.repository_owner == 'onosproject') && (needs.version-check.outputs.valid_version == 'true') && (needs.version-check.outputs.dev_version == 'false') 107 | steps: 108 | - uses: actions/checkout@v4 109 | with: 110 | fetch-depth: 0 111 | - name: increment version 112 | run: | 113 | IFS='.' read -r major minor patch <<< ${{ needs.version-check.outputs.target_version }} 114 | patch_update=$((patch+1)) 115 | NEW_VERSION="$major.$minor.$patch_update-dev" 116 | echo $NEW_VERSION > VERSION 117 | echo "Updated version: $NEW_VERSION" 118 | 119 | - name: Create Pull Request 120 | uses: peter-evans/create-pull-request@v6 121 | with: 122 | token: ${{ secrets.GH_ONOS_PAT }} 123 | commit-message: Update version 124 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 125 | author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> 126 | signoff: true 127 | branch: version-update 128 | delete-branch: true 129 | title: Update version 130 | body: | 131 | Update VERSION file 132 | add-paths: | 133 | VERSION -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | # SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | build/_output/* 7 | build/build-tools 8 | **/_output 9 | *.DS_Store 10 | coverage.txt 11 | *.coverprofile 12 | .idea 13 | .vscode 14 | *.o 15 | *.a 16 | .project 17 | .cproject 18 | .eclipse 19 | .settings/ 20 | 21 | # Jenkins report files 22 | *report*.xml 23 | *coverage*.xml 24 | *-output.out 25 | vendor 26 | venv -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/rimedo-ts.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: VERSION .gitreview go.mod go.sum *.png *.gif *.jpg .idea/* 4 | Copyright: 2021 Open Networking Foundation 5 | License: Apache-2.0 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | We expect all ONF employees, member companies, and participants to abide by our [Code of Conduct](https://www.opennetworking.org/wp-content/themes/onf/img/onf-code-of-conduct.pdf). 8 | 9 | If you are being harassed, notice that someone else is being harassed, or have any other concerns involving someone’s welfare, please notify a member of the ONF team or email [conduct@opennetworking.org](conduct@opennetworking.org). 10 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution 10 | as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct 18 | or indirect, to cause the direction or management of such entity, whether 19 | by contract or otherwise, or (ii) ownership of fifty percent (50%) or more 20 | of the outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions 23 | granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation 30 | or translation of a Source form, including but not limited to compiled object 31 | code, generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, 34 | made available under the License, as indicated by a copyright notice that 35 | is included in or attached to the work (an example is provided in the Appendix 36 | below). 37 | 38 | "Derivative Works" shall mean any work, whether in Source or Object form, 39 | that is based on (or derived from) the Work and for which the editorial revisions, 40 | annotations, elaborations, or other modifications represent, as a whole, an 41 | original work of authorship. For the purposes of this License, Derivative 42 | Works shall not include works that remain separable from, or merely link (or 43 | bind by name) to the interfaces of, the Work and Derivative Works thereof. 44 | 45 | "Contribution" shall mean any work of authorship, including the original version 46 | of the Work and any modifications or additions to that Work or Derivative 47 | Works thereof, that is intentionally submitted to Licensor for inclusion in 48 | the Work by the copyright owner or by an individual or Legal Entity authorized 49 | to submit on behalf of the copyright owner. For the purposes of this definition, 50 | "submitted" means any form of electronic, verbal, or written communication 51 | sent to the Licensor or its representatives, including but not limited to 52 | communication on electronic mailing lists, source code control systems, and 53 | issue tracking systems that are managed by, or on behalf of, the Licensor 54 | for the purpose of discussing and improving the Work, but excluding communication 55 | that is conspicuously marked or otherwise designated in writing by the copyright 56 | owner as "Not a Contribution." 57 | 58 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 59 | of whom a Contribution has been received by Licensor and subsequently incorporated 60 | within the Work. 61 | 62 | 2. Grant of Copyright License. Subject to the terms and conditions of this 63 | License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 64 | no-charge, royalty-free, irrevocable copyright license to reproduce, prepare 65 | Derivative Works of, publicly display, publicly perform, sublicense, and distribute 66 | the Work and such Derivative Works in Source or Object form. 67 | 68 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 69 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 70 | no-charge, royalty-free, irrevocable (except as stated in this section) patent 71 | license to make, have made, use, offer to sell, sell, import, and otherwise 72 | transfer the Work, where such license applies only to those patent claims 73 | licensable by such Contributor that are necessarily infringed by their Contribution(s) 74 | alone or by combination of their Contribution(s) with the Work to which such 75 | Contribution(s) was submitted. If You institute patent litigation against 76 | any entity (including a cross-claim or counterclaim in a lawsuit) alleging 77 | that the Work or a Contribution incorporated within the Work constitutes direct 78 | or contributory patent infringement, then any patent licenses granted to You 79 | under this License for that Work shall terminate as of the date such litigation 80 | is filed. 81 | 82 | 4. Redistribution. You may reproduce and distribute copies of the Work or 83 | Derivative Works thereof in any medium, with or without modifications, and 84 | in Source or Object form, provided that You meet the following conditions: 85 | 86 | (a) You must give any other recipients of the Work or Derivative Works a copy 87 | of this License; and 88 | 89 | (b) You must cause any modified files to carry prominent notices stating that 90 | You changed the files; and 91 | 92 | (c) You must retain, in the Source form of any Derivative Works that You distribute, 93 | all copyright, patent, trademark, and attribution notices from the Source 94 | form of the Work, excluding those notices that do not pertain to any part 95 | of the Derivative Works; and 96 | 97 | (d) If the Work includes a "NOTICE" text file as part of its distribution, 98 | then any Derivative Works that You distribute must include a readable copy 99 | of the attribution notices contained within such NOTICE file, excluding those 100 | notices that do not pertain to any part of the Derivative Works, in at least 101 | one of the following places: within a NOTICE text file distributed as part 102 | of the Derivative Works; within the Source form or documentation, if provided 103 | along with the Derivative Works; or, within a display generated by the Derivative 104 | Works, if and wherever such third-party notices normally appear. The contents 105 | of the NOTICE file are for informational purposes only and do not modify the 106 | License. You may add Your own attribution notices within Derivative Works 107 | that You distribute, alongside or as an addendum to the NOTICE text from the 108 | Work, provided that such additional attribution notices cannot be construed 109 | as modifying the License. 110 | 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, 113 | or distribution of Your modifications, or for any such Derivative Works as 114 | a whole, provided Your use, reproduction, and distribution of the Work otherwise 115 | complies with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 118 | Contribution intentionally submitted for inclusion in the Work by You to the 119 | Licensor shall be under the terms and conditions of this License, without 120 | any additional terms or conditions. Notwithstanding the above, nothing herein 121 | shall supersede or modify the terms of any separate license agreement you 122 | may have executed with Licensor regarding such Contributions. 123 | 124 | 6. Trademarks. This License does not grant permission to use the trade names, 125 | trademarks, service marks, or product names of the Licensor, except as required 126 | for reasonable and customary use in describing the origin of the Work and 127 | reproducing the content of the NOTICE file. 128 | 129 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to 130 | in writing, Licensor provides the Work (and each Contributor provides its 131 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 132 | KIND, either express or implied, including, without limitation, any warranties 133 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR 134 | A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness 135 | of using or redistributing the Work and assume any risks associated with Your 136 | exercise of permissions under this License. 137 | 138 | 8. Limitation of Liability. In no event and under no legal theory, whether 139 | in tort (including negligence), contract, or otherwise, unless required by 140 | applicable law (such as deliberate and grossly negligent acts) or agreed to 141 | in writing, shall any Contributor be liable to You for damages, including 142 | any direct, indirect, special, incidental, or consequential damages of any 143 | character arising as a result of this License or out of the use or inability 144 | to use the Work (including but not limited to damages for loss of goodwill, 145 | work stoppage, computer failure or malfunction, or any and all other commercial 146 | damages or losses), even if such Contributor has been advised of the possibility 147 | of such damages. 148 | 149 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 150 | or Derivative Works thereof, You may choose to offer, and charge a fee for, 151 | acceptance of support, warranty, indemnity, or other liability obligations 152 | and/or rights consistent with this License. However, in accepting such obligations, 153 | You may act only on Your own behalf and on Your sole responsibility, not on 154 | behalf of any other Contributor, and only if You agree to indemnify, defend, 155 | and hold each Contributor harmless for any liability incurred by, or claims 156 | asserted against, such Contributor by reason of your accepting any such warranty 157 | or additional liability. 158 | 159 | END OF TERMS AND CONDITIONS 160 | 161 | APPENDIX: How to apply the Apache License to your work. 162 | 163 | To apply the Apache License to your work, attach the following boilerplate 164 | notice, with the fields enclosed by brackets "[]" replaced with your own identifying 165 | information. (Don't include the brackets!) The text should be enclosed in 166 | the appropriate comment syntax for the file format. We also recommend that 167 | a file or class name and description of purpose be included on the same "printed 168 | page" as the copyright notice for easier identification within third-party 169 | archives. 170 | 171 | Copyright [yyyy] [name of copyright owner] 172 | 173 | Licensed under the Apache License, Version 2.0 (the "License"); 174 | you may not use this file except in compliance with the License. 175 | You may obtain a copy of the License at 176 | 177 | http://www.apache.org/licenses/LICENSE-2.0 178 | 179 | Unless required by applicable law or agreed to in writing, software 180 | distributed under the License is distributed on an "AS IS" BASIS, 181 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 182 | See the License for the specific language governing permissions and 183 | limitations under the License. 184 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2019 Open Networking Foundation 3 | # Copyright 2019 Rimedo Labs 4 | # Copyright 2024 Intel Corporation 5 | 6 | .PHONY: build 7 | export CGO_ENABLED=1 8 | export GO111MODULE=on 9 | 10 | RIMEDO_TS_VERSION ?= latest 11 | 12 | GOLANG_CI_VERSION := v1.52.2 13 | 14 | all: build docker-build 15 | 16 | build: # @HELP build the Go binaries and run all validations (default) 17 | GOPRIVATE="github.com/onosproject/*" go build -o build/_output/rimedo-ts ./cmd/rimedo-ts 18 | 19 | test: # @HELP run the unit tests and source code validation 20 | test: build lint license 21 | go test -race github.com/onosproject/rimedo-ts/pkg/... 22 | go test -race github.com/onosproject/rimedo-ts/cmd/... 23 | 24 | docker-build-rimedo-ts: # @HELP build Docker image 25 | @go mod vendor 26 | docker build --network host . -f build/rimedo-ts/Dockerfile \ 27 | -t onosproject/rimedo-ts:${RIMEDO_TS_VERSION} 28 | @rm -rf vendor 29 | 30 | docker-build: # @HELP build all Docker images 31 | docker-build: build docker-build-rimedo-ts 32 | 33 | docker-push-rimedo-ts: # @HELP push Docker image 34 | docker push onosproject/rimedo-ts:${RIMEDO_TS_VERSION} 35 | 36 | docker-push: # @HELP push docker images 37 | docker-push: docker-push-rimedo-ts 38 | 39 | lint: # @HELP examines Go source code and reports coding problems 40 | golangci-lint --version | grep $(GOLANG_CI_VERSION) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b `go env GOPATH`/bin $(GOLANG_CI_VERSION) 41 | golangci-lint run --timeout 15m 42 | 43 | license: # @HELP run license checks 44 | rm -rf venv 45 | python3 -m venv venv 46 | . ./venv/bin/activate;\ 47 | python3 -m pip install --upgrade pip;\ 48 | python3 -m pip install reuse;\ 49 | reuse lint 50 | 51 | check-version: # @HELP check version is duplicated 52 | ./build/bin/version_check.sh all 53 | 54 | clean: # @HELP remove all the build artifacts 55 | rm -rf ./build/_output ./vendor ./cmd/rimedo-ts/rimedo-ts ./cmd/onos/onos venv 56 | go clean github.com/onosproject/rimedo-ts/... 57 | 58 | help: 59 | @grep -E '^.*: *# *@HELP' $(MAKEFILE_LIST) \ 60 | | sort \ 61 | | awk ' \ 62 | BEGIN {FS = ": *# *@HELP"}; \ 63 | {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}; \ 64 | ' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | 8 |

9 | 10 |

11 |   12 |   13 | 14 |

15 | 16 |

17 | 18 |

19 | 20 | ## Overview 21 | 22 | Traffic Steering (TS) xApplication (xApp) for µONOS RIC - associates the users with the eNB/gNB taking into account user radio conditions, cell types, and service type/QoS profile. It can be controlled by policies from non-RT RIC (A1 interface). 23 | 24 | ### Key features: 25 | - Suitable for heterogeneous scenarios 26 | - Per-user association decision taking into account radio conditions (e.g. RSRP), service type (e.g. 5QI), cell type 27 | - Optimizes user-throughput and cell-outage 28 | 29 | ## An example of xApp operation 30 | Example of an policy influence on single UE. The traffic sterring is realized by direct indication of user as the policy scope contains `ueId`. It is worth paying attention to UE association at different point of time. 31 | ![Demo](images/demo.gif) 32 | 33 | #### List of events during the demo: 34 | 35 |
12:58:32 - start of xApp 36 | 37 | ```sh 38 | there is no TS policy applied 39 | ``` 40 | 41 |
42 | 43 |
13:00:00 - put policy with ID 1 to A1T endpoint 44 | 45 | ```json 46 | { 47 | "scope":{ 48 | "ueId":"0000000003064635" 49 | }, 50 | "tspResources":[ 51 | { 52 | "cellIdList":[ 53 | { 54 | "plmnId":{ 55 | "mcc":"314", 56 | "mnc":"628" 57 | }, 58 | "cId":{ 59 | "ncI":470106432 60 | } 61 | } 62 | ], 63 | "preference":"PREFER" 64 | } 65 | ] 66 | } 67 | ``` 68 | 69 |
70 | 71 |
13:01:47 - delete policy with ID 1 at A1T endpoint 72 | 73 | ```sh 74 | there is no TS policy applied 75 | ``` 76 | 77 |
78 | 79 |
13:02:47 - put policy with ID 2 at A1T endpoint 80 | 81 | ```json 82 | { 83 | "scope":{ 84 | "ueId":"0000000003064635" 85 | }, 86 | "tspResources":[ 87 | { 88 | "cellIdList":[ 89 | { 90 | "plmnId":{ 91 | "mcc":"314", 92 | "mnc":"628" 93 | }, 94 | "cId":{ 95 | "ncI":470106432 96 | } 97 | } 98 | ], 99 | "preference":"AVOID" 100 | } 101 | ] 102 | } 103 | ``` 104 | 105 |
106 | 107 |
13:04:07 - put policy with ID 2 at A1T endpoint (update) 108 | 109 | ```json 110 | { 111 | "scope":{ 112 | "ueId":"0000000003064635" 113 | }, 114 | "tspResources":[ 115 | { 116 | "cellIdList":[ 117 | { 118 | "plmnId":{ 119 | "mcc":"314", 120 | "mnc":"628" 121 | }, 122 | "cId":{ 123 | "ncI":470106432 124 | } 125 | } 126 | ], 127 | "preference":"FORBID" 128 | } 129 | ] 130 | } 131 | ``` 132 | 133 |
134 | 135 |
13:05:07 - delete policy with ID 2 at A1T endpoint 136 | 137 | ```sh 138 | there is no TS policy applied 139 | ``` 140 | 141 |
142 | 143 | ## Extra example of xApp operation 144 |
Click to show demo
145 | 146 | Example of an policy influence on single UE or group of UEs. The traffic sterring is realized by indication of user's service type as the policy scope contains `qosId` - in this case `5qI` value is used. It is assumed that only one slice exists. The values of 5QI are changing randomly according to the data generated by the simulator. It is worth paying attention to UE association at different point of time. 147 | 148 | ![Demo](images/demo_slice_5qi.gif) 149 | 150 | #### List of events during the demo: 151 | 152 |
13:55:00 - start of xApp 153 | 154 | ```sh 155 | there is no TS policy applied 156 | ``` 157 |
158 | 159 |
13:56:23 - put policy with ID 1 to A1T endpoint 160 | 161 | ```json 162 | { 163 | "scope":{ 164 | "sliceId":{ 165 | "sst":1, 166 | "sd":"456DEF", 167 | "plmnId":{ 168 | "mcc":"314", 169 | "mnc":"628" 170 | } 171 | }, 172 | "qosId":{ 173 | "5qI":1 174 | } 175 | }, 176 | "tspResources":[ 177 | { 178 | "cellIdList":[ 179 | { 180 | "plmnId":{ 181 | "mcc":"314", 182 | "mnc":"628" 183 | }, 184 | "cId":{ 185 | "ncI":470106432 186 | } 187 | } 188 | ], 189 | "preference":"SHALL" 190 | } 191 | ] 192 | } 193 | ``` 194 | 195 |
196 |
197 | 198 | ## Installation 199 | To use TS xApp there should be kubernetes, docker, helm and sdran (by ONOS) installed and deployed. You can use [SDRAN-in-a-Box](https://github.com/onosproject/sdran-in-a-box) project to install everything at once, or install requirements manually and deploy *sdran* from [SDRAN Helm charts](https://github.com/onosproject/sdran-helm-charts) repository. 200 | 201 | ### Requirements 202 | TS xApp was tested under this version of SD-RAN modules. Tested under ran-simulator model `two-cell-two-node-model.yaml`. 203 | 204 | | Module | Version | 205 | | ------------- |--------------:| 206 | | onos-a1t | v0.1.11 | 207 | | onos-cli | v0.9.10 | 208 | | onos-config | v0.10.27 | 209 | | onos-e2t | v0.10.11 | 210 | | onos-topo | v0.9.2 | 211 | | ran-simulator | v0.9.6 | 212 | | e2sm_mho_go | v0.8.7 | 213 | 214 | ### Deployment 215 | 216 | To deploy TS xApp firstly build docker image, and then use helm to install xApp. 217 | 218 | ```bash 219 | make docker 220 | make install-xapp 221 | ``` 222 | 223 | ![Installation](images/install.gif) 224 | 225 | ### Useful tips 226 | 227 | - `:31963/policytypes/ORAN_TrafficSteeringPreference_2.0.0/policies/` - the policies are send to `A1` interface on address 228 | - `kubectl logs -n riab rimedo-ts- rimedo-ts` - observe logs of TS xApp 229 | - `watch onos uenib get ues -v` - observe RSRP of UE (type in `onos-cli`) 230 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.4-dev 2 | -------------------------------------------------------------------------------- /build/bin/version_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2024 Intel Corporation 4 | 5 | set +x 6 | 7 | # input should be all, is_valid_format, is_dev, and is_unique 8 | INPUT=$1 9 | 10 | function is_valid_format() { 11 | # check if version format is matched to SemVer 12 | VER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' 13 | if [[ ! $(cat VERSION | tr -d '\n' | sed s/-dev//) =~ $VER_REGEX ]] 14 | then 15 | return 1 16 | fi 17 | return 0 18 | } 19 | 20 | function is_dev_version() { 21 | # check if version has '-dev' 22 | # if there is, no need to check version 23 | if [[ $(cat VERSION | tr -d '\n' | tail -c 4) =~ "-dev" ]] 24 | then 25 | return 0 26 | fi 27 | return 1 28 | } 29 | 30 | function is_unique_version() { 31 | # check if the version is already tagged in GitHub repository 32 | for t in $(git tag | cat) 33 | do 34 | if [[ $t == $(echo v$(cat VERSION | tr -d '\n')) ]] 35 | then 36 | return 1 37 | fi 38 | done 39 | return 0 40 | } 41 | 42 | case $INPUT in 43 | all) 44 | is_valid_format 45 | f_valid=$? 46 | if [[ $f_valid == 1 ]] 47 | then 48 | echo "ERROR: Version $(cat VERSION) is not in SemVer format" 49 | exit 2 50 | fi 51 | 52 | is_dev_version 53 | f_dev=$? 54 | if [[ $f_dev == 0 ]] 55 | then 56 | echo "This is dev version" 57 | exit 0 58 | fi 59 | 60 | is_unique_version 61 | f_unique=$? 62 | if [[ $f_unique == 1 ]] 63 | then 64 | echo "ERROR: duplicated tag $(cat VERSION)" 65 | exit 2 66 | fi 67 | ;; 68 | 69 | is_valid_format) 70 | is_valid_format 71 | ;; 72 | 73 | is_dev) 74 | is_dev_version 75 | f_dev=$? 76 | if [[ $f_dev == 0 ]] 77 | then 78 | echo "true" 79 | exit 0 80 | fi 81 | echo "false" 82 | ;; 83 | 84 | is_unique) 85 | is_unique_version 86 | ;; 87 | 88 | *) 89 | echo -n "unknown input" 90 | exit 2 91 | ;; 92 | 93 | esac 94 | -------------------------------------------------------------------------------- /build/rimedo-ts/Dockerfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | # SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | FROM onosproject/golang-build:v1.0 as build 7 | 8 | ENV GO111MODULE=on 9 | ENV XAPPNAME=rimedo-ts 10 | 11 | COPY Makefile go.mod go.sum /go/src/github.com/rimedo-labs/rimedo-ts/ 12 | COPY cmd/ /go/src/github.com/rimedo-labs/rimedo-ts/cmd/ 13 | COPY vendor/ /go/src/github.com/rimedo-labs/rimedo-ts/vendor/ 14 | COPY pkg/ /go/src/github.com/rimedo-labs/rimedo-ts/pkg/ 15 | COPY build/ /go/src/github.com/rimedo-labs/rimedo-ts/build/ 16 | # COPY policytypes /go/src/github.com/rimedo-labs/rimedo-ts/policytypes/ 17 | # COPY schemas /go/src/github.com/rimedo-labs/rimedo-ts/schemas/ 18 | 19 | RUN cd /go/src/github.com/rimedo-labs/rimedo-ts && GOFLAGS=-mod=vendor make build 20 | 21 | FROM alpine:3.11 22 | RUN apk add libc6-compat 23 | 24 | USER nobody 25 | 26 | COPY --from=build /go/src/github.com/rimedo-labs/rimedo-ts/build/_output/rimedo-ts /usr/local/bin/rimedo-ts 27 | # COPY --from=build /go/src/github.com/rimedo-labs/rimedo-ts/schemas /data/schemas 28 | # COPY --from=build /go/src/github.com/rimedo-labs/rimedo-ts/policytypes /data/policytypes 29 | 30 | ENTRYPOINT ["rimedo-ts"] 31 | -------------------------------------------------------------------------------- /cmd/rimedo-ts-test/rimedo-ts-test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/onosproject/helmit/pkg/registry" 10 | "github.com/onosproject/helmit/pkg/test" 11 | "github.com/onosproject/rimedo-ts/test/ts" 12 | ) 13 | 14 | func main() { 15 | registry.RegisterTestSuite("ts", &ts.TestSuite{}) 16 | test.Main() 17 | } 18 | -------------------------------------------------------------------------------- /cmd/rimedo-ts/rimedo-ts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Created by RIMEDO-Labs team 6 | 7 | package main 8 | 9 | import ( 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | 14 | "github.com/onosproject/onos-lib-go/pkg/certs" 15 | "github.com/onosproject/onos-lib-go/pkg/logging" 16 | "github.com/onosproject/rimedo-ts/pkg/manager" 17 | "github.com/onosproject/rimedo-ts/pkg/northbound/a1" 18 | "github.com/onosproject/rimedo-ts/pkg/sdran" 19 | ) 20 | 21 | var log = logging.GetLogger("rimedo-ts") 22 | 23 | func main() { 24 | 25 | log.SetLevel(logging.DebugLevel) 26 | log.Info("Starting RIMEDO Labs Traffic Steering xAPP") 27 | 28 | sdranConfig := sdran.Config{ 29 | AppID: "rimedo-ts", 30 | E2tAddress: "onos-e2t", 31 | E2tPort: 5150, 32 | TopoAddress: "onos-topo", 33 | TopoPort: 5150, 34 | SMName: "oran-e2sm-mho", 35 | SMVersion: "v2", 36 | TSPolicySchemePath: "/data/schemas/ORAN_TrafficSteeringPreference_v102.json", 37 | } 38 | 39 | a1Config := a1.Config{ 40 | PolicyName: "ORAN_TrafficSteeringPreference", 41 | PolicyVersion: "2.0.0", 42 | PolicyID: "ORAN_TrafficSteeringPreference_2.0.0", 43 | PolicyDescription: "O-RAN traffic steering", 44 | A1tPort: 5150, 45 | } 46 | 47 | _, err := certs.HandleCertPaths("", "", "", true) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | mgr := manager.NewManager(sdranConfig, a1Config, false) 53 | mgr.Run() 54 | 55 | killSignal := make(chan os.Signal, 1) 56 | signal.Notify(killSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 57 | log.Debug("app: received a shutdown signal:", <-killSignal) 58 | mgr.Close() 59 | } 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/onosproject/rimedo-ts 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gogo/protobuf v1.3.2 7 | github.com/google/uuid v1.3.0 8 | github.com/onosproject/helmit v0.6.19 9 | github.com/onosproject/onos-a1-dm/go v0.0.5 10 | github.com/onosproject/onos-api/go v0.9.11 11 | github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go v0.8.5 12 | github.com/onosproject/onos-lib-go v0.8.13 13 | github.com/onosproject/onos-mho v0.3.1 14 | github.com/onosproject/onos-ric-sdk-go v0.8.9 15 | github.com/onosproject/onos-test v0.6.5 16 | github.com/xeipuuv/gojsonschema v1.2.0 17 | google.golang.org/grpc v1.41.0 18 | google.golang.org/protobuf v1.27.1 19 | ) 20 | -------------------------------------------------------------------------------- /images/blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/blog.png -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/demo.gif -------------------------------------------------------------------------------- /images/demo_slice_5qi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/demo_slice_5qi.gif -------------------------------------------------------------------------------- /images/install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/install.gif -------------------------------------------------------------------------------- /images/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/linkedin.png -------------------------------------------------------------------------------- /images/rimedo_ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/rimedo_ts.png -------------------------------------------------------------------------------- /images/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onosproject/rimedo-ts/ba5e95079e176dfef9fd7c7a8373af58c752ee51/images/web.png -------------------------------------------------------------------------------- /pkg/manager/manager.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Created by RIMEDO-Labs team 6 | // based on any onosproject manager 7 | package manager 8 | 9 | import ( 10 | "context" 11 | "fmt" 12 | "sort" 13 | "strconv" 14 | "sync" 15 | "time" 16 | 17 | policyAPI "github.com/onosproject/onos-a1-dm/go/policy_schemas/traffic_steering_preference/v2" 18 | topoAPI "github.com/onosproject/onos-api/go/onos/topo" 19 | "github.com/onosproject/onos-lib-go/pkg/logging" 20 | "github.com/onosproject/rimedo-ts/pkg/mho" 21 | "github.com/onosproject/rimedo-ts/pkg/northbound/a1" 22 | "github.com/onosproject/rimedo-ts/pkg/sdran" 23 | ) 24 | 25 | var log = logging.GetLogger("rimedo-ts", "ts-manager") 26 | var logLength = 150 27 | var nodesLogLen = 0 28 | var policiesLogLen = 0 29 | 30 | type Config struct { 31 | AppID string 32 | E2tAddress string 33 | E2tPort int 34 | TopoAddress string 35 | TopoPort int 36 | SMName string 37 | SMVersion string 38 | } 39 | 40 | func NewManager(sdranConfig sdran.Config, a1Config a1.Config, flag bool) *Manager { 41 | 42 | sdranManager := sdran.NewManager(sdranConfig, flag) 43 | 44 | a1PolicyTypes := make([]*topoAPI.A1PolicyType, 0) 45 | a1Policy := &topoAPI.A1PolicyType{ 46 | Name: topoAPI.PolicyTypeName(a1Config.PolicyName), 47 | Version: topoAPI.PolicyTypeVersion(a1Config.PolicyVersion), 48 | ID: topoAPI.PolicyTypeID(a1Config.PolicyID), 49 | Description: topoAPI.PolicyTypeDescription(a1Config.PolicyDescription), 50 | } 51 | a1PolicyTypes = append(a1PolicyTypes, a1Policy) 52 | 53 | a1Manager, err := a1.NewManager("", "", "", a1Config.A1tPort, sdranConfig.AppID, a1PolicyTypes) 54 | if err != nil { 55 | log.Warn(err) 56 | } 57 | 58 | manager := &Manager{ 59 | sdranManager: sdranManager, 60 | a1Manager: *a1Manager, 61 | topoIDsEnabled: flag, 62 | mutex: sync.RWMutex{}, 63 | } 64 | return manager 65 | } 66 | 67 | type Manager struct { 68 | sdranManager *sdran.Manager 69 | a1Manager a1.Manager 70 | topoIDsEnabled bool 71 | mutex sync.RWMutex 72 | } 73 | 74 | func (m *Manager) Run() { 75 | 76 | if err := m.start(); err != nil { 77 | log.Fatal("Unable to run Manager", err) 78 | } 79 | 80 | } 81 | 82 | func (m *Manager) Close() { 83 | m.a1Manager.Close(context.Background()) 84 | } 85 | 86 | func (m *Manager) start() error { 87 | 88 | ctx := context.Background() 89 | 90 | policyMap := make(map[string][]byte) 91 | 92 | policyChange := make(chan bool) 93 | 94 | m.sdranManager.AddService(a1.NewA1EIService()) 95 | m.sdranManager.AddService(a1.NewA1PService(&policyMap, policyChange)) 96 | 97 | handleFlag := false 98 | 99 | m.sdranManager.Run(&handleFlag) 100 | 101 | m.a1Manager.Start() 102 | 103 | go func() { 104 | for range policyChange { 105 | log.Debug("") 106 | drawWithLine("POLICY STORE CHANGED!", logLength) 107 | log.Debug("") 108 | if err := m.updatePolicies(ctx, policyMap); err != nil { 109 | log.Warn("Some problems occured when updating Policy store!") 110 | } 111 | log.Debug("") 112 | m.checkPolicies(ctx, true, true, true) 113 | } 114 | 115 | }() 116 | flag := true 117 | show := false 118 | prepare := false 119 | counter := 0 120 | delay := 3 121 | time.Sleep(5 * time.Second) 122 | log.Info("\n\n\n\n\n\n\n\n\n\n") 123 | handleFlag = true 124 | go func() { 125 | for { 126 | time.Sleep(1 * time.Second) 127 | counter++ 128 | if counter == delay { 129 | compareLengths() 130 | counter = 0 131 | show = true 132 | } else if counter == delay-1 { 133 | prepare = true 134 | } else { 135 | show = false 136 | prepare = false 137 | } 138 | m.checkPolicies(ctx, flag, show, prepare) 139 | m.showAvailableNodes(ctx, show, prepare) 140 | flag = false 141 | } 142 | }() 143 | 144 | return nil 145 | } 146 | 147 | func (m *Manager) updatePolicies(ctx context.Context, policyMap map[string][]byte) error { 148 | m.mutex.Lock() 149 | defer m.mutex.Unlock() 150 | policies := m.sdranManager.GetPolicies(ctx) 151 | for k := range policies { 152 | if _, ok := policyMap[k]; !ok { 153 | m.sdranManager.DeletePolicy(ctx, k) 154 | log.Infof("POLICY MESSAGE: Policy [ID:%v] deleted\n", k) 155 | } 156 | } 157 | for i := range policyMap { 158 | r, err := policyAPI.UnmarshalAPI(policyMap[i]) 159 | if err == nil { 160 | policyObject := m.sdranManager.CreatePolicy(ctx, i, &r) 161 | info := fmt.Sprintf("POLICY MESSAGE: Policy [ID:%v] applied -> ", policyObject.Key) 162 | previous := false 163 | if policyObject.API.Scope.SliceID != nil { 164 | info = info + fmt.Sprintf("Slice [SD:%v, SST:%v, PLMN:(MCC:%v, MNC:%v)]", *policyObject.API.Scope.SliceID.SD, policyObject.API.Scope.SliceID.Sst, policyObject.API.Scope.SliceID.PlmnID.Mcc, policyObject.API.Scope.SliceID.PlmnID.Mnc) 165 | previous = true 166 | } 167 | if policyObject.API.Scope.UeID != nil { 168 | if previous { 169 | info = info + ", " 170 | } 171 | ue := *policyObject.API.Scope.UeID 172 | new_ue := ue 173 | for i := 0; i < len(ue); i++ { 174 | if ue[i:i+1] == "0" { 175 | new_ue = ue[i+1:] 176 | } else { 177 | break 178 | } 179 | } 180 | info = info + fmt.Sprintf("UE [ID:%v]", new_ue) 181 | previous = true 182 | } 183 | if policyObject.API.Scope.QosID != nil { 184 | if previous { 185 | info = info + ", " 186 | } 187 | if policyObject.API.Scope.QosID.QcI != nil { 188 | info = info + fmt.Sprintf("QoS [QCI:%v]", *policyObject.API.Scope.QosID.QcI) 189 | } 190 | if policyObject.API.Scope.QosID.The5QI != nil { 191 | info = info + fmt.Sprintf("QoS [5QI:%v]", *policyObject.API.Scope.QosID.The5QI) 192 | } 193 | } 194 | if policyObject.API.Scope.CellID != nil { 195 | if previous { 196 | info = info + ", " 197 | } 198 | info = info + "CELL [" 199 | if policyObject.API.Scope.CellID.CID.NcI != nil { 200 | 201 | info = info + fmt.Sprintf("NCI:%v, ", *policyObject.API.Scope.CellID.CID.NcI) 202 | } 203 | if policyObject.API.Scope.CellID.CID.EcI != nil { 204 | 205 | info = info + fmt.Sprintf("ECI:%v, ", *policyObject.API.Scope.CellID.CID.EcI) 206 | } 207 | info = info + fmt.Sprintf("PLMN:(MCC:%v, MNC:%v)]", policyObject.API.Scope.CellID.PlmnID.Mcc, policyObject.API.Scope.CellID.PlmnID.Mnc) 208 | } 209 | for i := range policyObject.API.TSPResources { 210 | info = info + fmt.Sprintf(" - (%v) -", policyObject.API.TSPResources[i].Preference) 211 | for j := range policyObject.API.TSPResources[i].CellIDList { 212 | nci := *policyObject.API.TSPResources[i].CellIDList[j].CID.NcI 213 | plmnId, _ := mho.GetPlmnIdFromMccMnc(policyObject.API.TSPResources[i].CellIDList[j].PlmnID.Mcc, policyObject.API.TSPResources[i].CellIDList[j].PlmnID.Mnc) 214 | cgi := m.PlmnIDNciToCGI(plmnId, uint64(nci)) 215 | info = info + fmt.Sprintf(" CELL [CGI:%v],", cgi) 216 | } 217 | info = info[0 : len(info)-1] 218 | 219 | } 220 | info = info + "\n" 221 | log.Info(info) 222 | } else { 223 | log.Warn("Can't unmarshal the JSON file!") 224 | return err 225 | } 226 | } 227 | return nil 228 | } 229 | 230 | func (m *Manager) deployPolicies(ctx context.Context) { 231 | policyManager := m.sdranManager.GetPolicyManager() 232 | ues := m.sdranManager.GetUEs(ctx) 233 | keys := make([]string, 0, len(ues)) 234 | for k := range ues { 235 | keys = append(keys, k) 236 | } 237 | sort.Strings(keys) 238 | 239 | for i := range keys { 240 | var cellIDs []policyAPI.CellID 241 | var rsrps []int 242 | fiveQi := ues[keys[i]].FiveQi 243 | sd := "456DEF" 244 | scopeUe := policyAPI.Scope{ 245 | 246 | SliceID: &policyAPI.SliceID{ 247 | SD: &sd, 248 | Sst: 1, 249 | PlmnID: policyAPI.PlmnID{ 250 | Mcc: "314", 251 | Mnc: "628", 252 | }, 253 | }, 254 | UeID: &keys[i], 255 | QosID: &policyAPI.QosID{ 256 | The5QI: &fiveQi, 257 | }, 258 | } 259 | 260 | cgiKeys := make([]string, 0, len(ues[keys[i]].CgiTable)) 261 | for cgi := range ues[keys[i]].CgiTable { 262 | cgiKeys = append(cgiKeys, cgi) 263 | } 264 | inside := false 265 | for j := range cgiKeys { 266 | 267 | inside = true 268 | cgi := ues[keys[i]].CgiTable[cgiKeys[j]] 269 | nci := int64(mho.GetNciFromCellGlobalID(cgi)) 270 | plmnIdBytes := mho.GetPlmnIDBytesFromCellGlobalID(cgi) 271 | plmnId := mho.PlmnIDBytesToInt(plmnIdBytes) 272 | mcc, mnc := mho.GetMccMncFromPlmnID(plmnId) 273 | cellID := policyAPI.CellID{ 274 | CID: policyAPI.CID{ 275 | NcI: &nci, 276 | }, 277 | PlmnID: policyAPI.PlmnID{ 278 | Mcc: mcc, 279 | Mnc: mnc, 280 | }, 281 | } 282 | 283 | cellIDs = append(cellIDs, cellID) 284 | rsrps = append(rsrps, int(ues[keys[i]].RsrpTable[cgiKeys[j]])) 285 | 286 | } 287 | 288 | if inside { 289 | 290 | tsResult := policyManager.GetTsResultForUEV2(scopeUe, rsrps, cellIDs) 291 | plmnId, err := mho.GetPlmnIdFromMccMnc(tsResult.PlmnID.Mcc, tsResult.PlmnID.Mnc) 292 | 293 | if err != nil { 294 | log.Warnf("Cannot get PLMN ID from these MCC and MNC parameters:%v,%v.", tsResult.PlmnID.Mcc, tsResult.PlmnID.Mnc) 295 | } else { 296 | targetCellCGI := m.PlmnIDNciToCGI(plmnId, uint64(*tsResult.CID.NcI)) 297 | m.sdranManager.SwitchUeBetweenCells(ctx, keys[i], targetCellCGI) 298 | } 299 | 300 | } 301 | 302 | cellIDs = nil 303 | rsrps = nil 304 | 305 | } 306 | 307 | } 308 | 309 | func (m *Manager) checkPolicies(ctx context.Context, defaultFlag bool, showFlag bool, prepareFlag bool) { 310 | m.mutex.Lock() 311 | defer m.mutex.Unlock() 312 | policyLen := 0 313 | policies := m.sdranManager.GetPolicies(ctx) 314 | keys := make([]string, 0, len(policies)) 315 | for k := range policies { 316 | keys = append(keys, k) 317 | } 318 | sort.Strings(keys) 319 | if defaultFlag && (len(policies) == 0) { 320 | log.Infof("POLICY MESSAGE: Default policy applied\n") 321 | } 322 | if prepareFlag && len(policies) != 0 { 323 | if showFlag { 324 | log.Debug("") 325 | drawWithLine("POLICIES", logLength) 326 | } 327 | for _, key := range keys { 328 | policyObject := policies[key] 329 | info := fmt.Sprintf("ID:%v POLICY: {", policyObject.Key) 330 | previous := false 331 | if policyObject.API.Scope.SliceID != nil { 332 | info = info + fmt.Sprintf("Slice [SD:%v, SST:%v, PLMN:(MCC:%v, MNC:%v)]", *policyObject.API.Scope.SliceID.SD, policyObject.API.Scope.SliceID.Sst, policyObject.API.Scope.SliceID.PlmnID.Mcc, policyObject.API.Scope.SliceID.PlmnID.Mnc) 333 | previous = true 334 | } 335 | if policyObject.API.Scope.UeID != nil { 336 | if previous { 337 | info = info + ", " 338 | } 339 | ue := *policyObject.API.Scope.UeID 340 | new_ue := ue 341 | for i := 0; i < len(ue); i++ { 342 | if ue[i:i+1] == "0" { 343 | new_ue = ue[i+1:] 344 | } else { 345 | break 346 | } 347 | } 348 | info = info + fmt.Sprintf("UE [ID:%v]", new_ue) 349 | previous = true 350 | } 351 | if policyObject.API.Scope.QosID != nil { 352 | if previous { 353 | info = info + ", " 354 | } 355 | if policyObject.API.Scope.QosID.QcI != nil { 356 | info = info + fmt.Sprintf("QoS [QCI:%v]", *policyObject.API.Scope.QosID.QcI) 357 | } 358 | if policyObject.API.Scope.QosID.The5QI != nil { 359 | info = info + fmt.Sprintf("QoS [5QI:%v]", *policyObject.API.Scope.QosID.The5QI) 360 | } 361 | } 362 | if policyObject.API.Scope.CellID != nil { 363 | if previous { 364 | info = info + ", " 365 | } 366 | info = info + "CELL [" 367 | if policyObject.API.Scope.CellID.CID.NcI != nil { 368 | 369 | info = info + fmt.Sprintf("NCI:%v, ", *policyObject.API.Scope.CellID.CID.NcI) 370 | } 371 | if policyObject.API.Scope.CellID.CID.EcI != nil { 372 | 373 | info = info + fmt.Sprintf("ECI:%v, ", *policyObject.API.Scope.CellID.CID.EcI) 374 | } 375 | info = info + fmt.Sprintf("PLMN:(MCC:%v, MNC:%v)]", policyObject.API.Scope.CellID.PlmnID.Mcc, policyObject.API.Scope.CellID.PlmnID.Mnc) 376 | } 377 | for i := range policyObject.API.TSPResources { 378 | info = info + fmt.Sprintf(" - (%v) -", policyObject.API.TSPResources[i].Preference) 379 | for j := range policyObject.API.TSPResources[i].CellIDList { 380 | nci := *policyObject.API.TSPResources[i].CellIDList[j].CID.NcI 381 | plmnId, _ := mho.GetPlmnIdFromMccMnc(policyObject.API.TSPResources[i].CellIDList[j].PlmnID.Mcc, policyObject.API.TSPResources[i].CellIDList[j].PlmnID.Mnc) 382 | cgi := m.PlmnIDNciToCGI(plmnId, uint64(nci)) 383 | info = info + fmt.Sprintf(" CELL [CGI:%v],", cgi) 384 | } 385 | info = info[0 : len(info)-1] 386 | 387 | } 388 | info = info + "} STATUS: " 389 | if policyObject.IsEnforced { 390 | info = info + "ENFORCED" 391 | } else { 392 | info = info + "NOT ENFORCED" 393 | } 394 | if policyLen < len(info) { 395 | policyLen = len(info) 396 | } 397 | if showFlag { 398 | log.Debug(info) 399 | } 400 | } 401 | if showFlag { 402 | log.Debug("") 403 | } 404 | } 405 | m.deployPolicies(ctx) 406 | } 407 | 408 | func (m *Manager) showAvailableNodes(ctx context.Context, showFlag bool, prepareFlag bool) { 409 | m.mutex.Lock() 410 | defer m.mutex.Unlock() 411 | cellLen := 0 412 | ueLen := 0 413 | cells := m.sdranManager.GetCellTypes(ctx) 414 | cellsObjects := m.sdranManager.GetCells(ctx) 415 | keys := make([]string, 0, len(cells)) 416 | for k := range cells { 417 | keys = append(keys, k) 418 | } 419 | sort.Strings(keys) 420 | if prepareFlag && len(cells) > 0 && len(cellsObjects) > 0 { 421 | if showFlag { 422 | log.Debug("") 423 | drawWithLine("CELLS", logLength) 424 | } 425 | for _, key := range keys { 426 | cgi_str := m.CgiFromTopoToIndicationFormat(cells[key].CGI) 427 | info := fmt.Sprintf("ID:%v CGI:%v UEs:[", key, cgi_str) 428 | cellObject := m.sdranManager.GetCell(ctx, cgi_str) 429 | inside := false 430 | if cellObject != nil { 431 | for ue := range cellObject.Ues { 432 | inside = true 433 | new_ue := ue 434 | for i := 0; i < len(ue); i++ { 435 | if ue[i:i+1] == "0" { 436 | new_ue = ue[i+1:] 437 | } else { 438 | break 439 | } 440 | } 441 | info = info + new_ue + " " 442 | } 443 | } 444 | if inside { 445 | info = info[:len(info)-1] 446 | } 447 | info = info + "]" 448 | if cellLen < len(info) { 449 | cellLen = len(info) 450 | } 451 | if showFlag { 452 | log.Debug(info) 453 | } 454 | } 455 | } 456 | 457 | ues := m.sdranManager.GetUEs(ctx) 458 | keys = make([]string, 0, len(ues)) 459 | for k := range ues { 460 | keys = append(keys, k) 461 | } 462 | sort.Strings(keys) 463 | if prepareFlag && len(ues) > 0 { 464 | if showFlag { 465 | log.Debug("") 466 | drawWithLine("UES", logLength) 467 | } 468 | for _, key := range keys { 469 | ueIdString, _ := strconv.Atoi(key) 470 | cgiString := ues[key].CGIString 471 | if cgiString == "" { 472 | cgiString = "NONE" 473 | } 474 | status := "CONNECTED" 475 | if ues[key].Idle { 476 | status = "IDLE " 477 | } 478 | info := fmt.Sprintf("ID:%v STATUS:%v 5QI: %v CGI:%v CGIs(RSRP): [", ueIdString, status, ues[key].FiveQi, cgiString) 479 | 480 | cgi_keys := make([]string, 0, len(ues[key].RsrpTable)) 481 | for k := range ues[key].RsrpTable { 482 | cgi_keys = append(cgi_keys, k) 483 | } 484 | sort.Strings(cgi_keys) 485 | inside := false 486 | for _, cgi := range cgi_keys { 487 | inside = true 488 | info += fmt.Sprintf("%v (%v) ", cgi, ues[key].RsrpTable[cgi]) 489 | rsrp_str := strconv.Itoa(int(ues[key].RsrpTable[cgi])) 490 | if len(rsrp_str) < 4 { 491 | diff := 4 - len(rsrp_str) 492 | for i := 0; i < diff; i++ { 493 | info = info + " " 494 | } 495 | } 496 | } 497 | if inside { 498 | info = info[:len(info)-1] 499 | } 500 | info = info + "]" 501 | if ueLen < len(info) { 502 | ueLen = len(info) 503 | } 504 | if showFlag { 505 | log.Debug(info) 506 | } 507 | } 508 | if showFlag { 509 | log.Debug("") 510 | } 511 | } 512 | if cellLen > ueLen { 513 | nodesLogLen = cellLen 514 | } else { 515 | nodesLogLen = ueLen 516 | } 517 | } 518 | 519 | //func (m *Manager) changeCellsTypes(ctx context.Context) { 520 | // m.mutex.Lock() 521 | // defer m.mutex.Unlock() 522 | // cellTypes := make(map[int]string) 523 | // cellTypes[0] = "Macro" 524 | // cellTypes[1] = "SmallCell" 525 | // for { 526 | // time.Sleep(10 * time.Second) 527 | // cells := m.sdranManager.GetCellTypes(ctx) 528 | // type_id := rand.Intn(len(cellTypes)) 529 | // for key, val := range cells { 530 | // _ = val 531 | // err := m.sdranManager.SetCellType(ctx, key, cellTypes[type_id]) 532 | // if err != nil { 533 | // log.Warn(err) 534 | // } 535 | // break 536 | // } 537 | // 538 | // } 539 | //} 540 | 541 | func (m *Manager) PlmnIDNciToCGI(plmnID uint64, nci uint64) string { 542 | cgi := strconv.FormatInt(int64(plmnID<<36|(nci&0xfffffffff)), 16) 543 | if m.topoIDsEnabled { 544 | cgi = cgi[0:6] + cgi[14:15] + cgi[12:14] + cgi[10:12] + cgi[8:10] + cgi[6:8] 545 | } 546 | return cgi 547 | } 548 | 549 | func (m *Manager) CgiFromTopoToIndicationFormat(cgi string) string { 550 | if !m.topoIDsEnabled { 551 | cgi = cgi[0:6] + cgi[13:15] + cgi[11:13] + cgi[9:11] + cgi[7:9] + cgi[6:7] 552 | } 553 | return cgi 554 | } 555 | 556 | func drawWithLine(word string, length int) { 557 | wordLength := len(word) 558 | diff := length - wordLength 559 | info := "" 560 | if diff == length { 561 | for i := 0; i < diff; i++ { 562 | info = info + "-" 563 | } 564 | } else { 565 | info = " " + word + " " 566 | diff -= 2 567 | for i := 0; i < diff/2; i++ { 568 | info = "-" + info + "-" 569 | } 570 | if diff%2 != 0 { 571 | info = info + "-" 572 | } 573 | } 574 | log.Debug(info) 575 | } 576 | 577 | func compareLengths() { 578 | temp := nodesLogLen 579 | if nodesLogLen < policiesLogLen { 580 | temp = policiesLogLen 581 | } 582 | logLength = temp 583 | } 584 | -------------------------------------------------------------------------------- /pkg/mho/data.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package mho 7 | 8 | import ( 9 | policyAPI "github.com/onosproject/onos-a1-dm/go/policy_schemas/traffic_steering_preference/v2" 10 | e2sm_v2_ies "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-v2-ies" 11 | ) 12 | 13 | type UeData struct { 14 | UeID string 15 | E2NodeID string 16 | CGI *e2sm_v2_ies.Cgi 17 | CGIString string 18 | RrcState string 19 | FiveQi int64 20 | RsrpServing int32 21 | RsrpNeighbors map[string]int32 22 | RsrpTable map[string]int32 23 | CgiTable map[string]*e2sm_v2_ies.Cgi 24 | Idle bool 25 | } 26 | 27 | type CellData struct { 28 | CGI *e2sm_v2_ies.Cgi 29 | CGIString string 30 | CumulativeHandoversIn int 31 | CumulativeHandoversOut int 32 | Ues map[string]*UeData 33 | } 34 | 35 | type PolicyData struct { 36 | Key string 37 | API *policyAPI.API 38 | IsEnforced bool 39 | } 40 | -------------------------------------------------------------------------------- /pkg/mho/mho.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package mho 7 | 8 | import ( 9 | "context" 10 | e2api "github.com/onosproject/onos-api/go/onos/e2t/e2/v1beta1" 11 | "reflect" 12 | "strconv" 13 | "sync" 14 | 15 | policyAPI "github.com/onosproject/onos-a1-dm/go/policy_schemas/traffic_steering_preference/v2" 16 | e2sm_mho "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-mho-go" 17 | e2sm_v2_ies "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-v2-ies" 18 | "github.com/onosproject/onos-lib-go/pkg/logging" 19 | "github.com/onosproject/onos-mho/pkg/store" 20 | "google.golang.org/protobuf/proto" 21 | ) 22 | 23 | var log = logging.GetLogger("rimedo-ts", "mho") 24 | 25 | type E2NodeIndication struct { 26 | NodeID string 27 | TriggerType e2sm_mho.MhoTriggerType 28 | IndMsg e2api.Indication 29 | } 30 | 31 | func NewController(indChan chan *E2NodeIndication, ueStore store.Store, cellStore store.Store, onosPolicyStore store.Store, policies map[string]*PolicyData, flag bool) *Controller { 32 | 33 | return &Controller{ 34 | IndChan: indChan, 35 | ueStore: ueStore, 36 | cellStore: cellStore, 37 | onosPolicyStore: onosPolicyStore, 38 | mu: sync.RWMutex{}, 39 | cells: make(map[string]*CellData), 40 | policies: policies, 41 | topoIDsEnabled: flag, 42 | } 43 | } 44 | 45 | type Controller struct { 46 | IndChan chan *E2NodeIndication 47 | ueStore store.Store 48 | cellStore store.Store 49 | onosPolicyStore store.Store 50 | mu sync.RWMutex 51 | cells map[string]*CellData 52 | policies map[string]*PolicyData 53 | topoIDsEnabled bool 54 | } 55 | 56 | func (c *Controller) Run(ctx context.Context, flag *bool) { 57 | go c.listenIndChan(ctx, flag) 58 | } 59 | 60 | func (c *Controller) listenIndChan(ctx context.Context, flag *bool) { 61 | var err error 62 | for indMsg := range c.IndChan { 63 | 64 | indHeaderByte := indMsg.IndMsg.Header 65 | indMessageByte := indMsg.IndMsg.Payload 66 | e2NodeID := indMsg.NodeID 67 | 68 | indHeader := e2sm_mho.E2SmMhoIndicationHeader{} 69 | if err = proto.Unmarshal(indHeaderByte, &indHeader); err == nil { 70 | indMessage := e2sm_mho.E2SmMhoIndicationMessage{} 71 | if err = proto.Unmarshal(indMessageByte, &indMessage); err == nil { 72 | switch x := indMessage.E2SmMhoIndicationMessage.(type) { 73 | case *e2sm_mho.E2SmMhoIndicationMessage_IndicationMessageFormat1: 74 | if indMsg.TriggerType == e2sm_mho.MhoTriggerType_MHO_TRIGGER_TYPE_UPON_RCV_MEAS_REPORT { 75 | go c.handleMeasReport(ctx, indHeader.GetIndicationHeaderFormat1(), indMessage.GetIndicationMessageFormat1(), e2NodeID, flag) 76 | } else if indMsg.TriggerType == e2sm_mho.MhoTriggerType_MHO_TRIGGER_TYPE_PERIODIC { 77 | go c.handlePeriodicReport(ctx, indHeader.GetIndicationHeaderFormat1(), indMessage.GetIndicationMessageFormat1(), e2NodeID, flag) 78 | } 79 | case *e2sm_mho.E2SmMhoIndicationMessage_IndicationMessageFormat2: 80 | go c.handleRrcState(ctx, indHeader.GetIndicationHeaderFormat1(), indMessage.GetIndicationMessageFormat2(), e2NodeID) 81 | default: 82 | log.Warnf("Unknown MHO indication message format, indication message: %v", x) 83 | } 84 | } 85 | } 86 | if err != nil { 87 | log.Error(err) 88 | } 89 | } 90 | } 91 | 92 | func (c *Controller) handlePeriodicReport(ctx context.Context, header *e2sm_mho.E2SmMhoIndicationHeaderFormat1, message *e2sm_mho.E2SmMhoIndicationMessageFormat1, e2NodeID string, flag *bool) { 93 | c.mu.Lock() 94 | defer c.mu.Unlock() 95 | ueID, err := GetUeID(message.GetUeId()) 96 | if err != nil { 97 | log.Errorf("handlePeriodicReport() couldn't extract UeID: %v", err) 98 | } 99 | cgi := GetCGIFromIndicationHeader(header) 100 | cgi = c.ConvertCgiToTheRightForm(cgi) 101 | cgiObject := header.GetCgi() 102 | 103 | ueIdString := strconv.Itoa(int(ueID)) 104 | n := (16 - len(ueIdString)) 105 | for i := 0; i < n; i++ { 106 | ueIdString = "0" + ueIdString 107 | } 108 | var ueData *UeData 109 | newUe := false 110 | ueData = c.GetUe(ctx, ueIdString) 111 | if ueData == nil { 112 | ueData = c.CreateUe(ctx, ueIdString) 113 | c.AttachUe(ctx, ueData, cgi, cgiObject) 114 | newUe = true 115 | } else if ueData.CGIString != cgi { 116 | return 117 | } 118 | 119 | ueData.E2NodeID = e2NodeID 120 | 121 | rsrpServing, rsrpNeighbors, rsrpTable, cgiTable := c.GetRsrpFromMeasReport(ctx, GetNciFromCellGlobalID(header.GetCgi()), message.MeasReport) 122 | 123 | old5qi := ueData.FiveQi 124 | ueData.FiveQi = c.GetFiveQiFromMeasReport(ctx, GetNciFromCellGlobalID(header.GetCgi()), message.MeasReport) 125 | 126 | if *flag && (old5qi != ueData.FiveQi) { 127 | log.Infof("\t\tQUALITY MESSAGE: 5QI for UE [ID:%v] changed [5QI:%v]\n", ueData.UeID, ueData.FiveQi) 128 | } 129 | 130 | if !newUe && rsrpServing == ueData.RsrpServing && reflect.DeepEqual(rsrpNeighbors, ueData.RsrpNeighbors) { 131 | return 132 | } 133 | 134 | ueData.RsrpServing, ueData.RsrpNeighbors, ueData.RsrpTable, ueData.CgiTable = rsrpServing, rsrpNeighbors, rsrpTable, cgiTable 135 | c.SetUe(ctx, ueData) 136 | 137 | } 138 | 139 | func (c *Controller) handleMeasReport(ctx context.Context, header *e2sm_mho.E2SmMhoIndicationHeaderFormat1, message *e2sm_mho.E2SmMhoIndicationMessageFormat1, e2NodeID string, flag *bool) { 140 | c.mu.Lock() 141 | defer c.mu.Unlock() 142 | ueID, err := GetUeID(message.GetUeId()) 143 | if err != nil { 144 | log.Errorf("handleMeasReport() couldn't extract UeID: %v", err) 145 | } 146 | cgi := GetCGIFromIndicationHeader(header) 147 | cgi = c.ConvertCgiToTheRightForm(cgi) 148 | cgiObject := header.GetCgi() 149 | 150 | ueIdString := strconv.Itoa(int(ueID)) 151 | n := (16 - len(ueIdString)) 152 | for i := 0; i < n; i++ { 153 | ueIdString = "0" + ueIdString 154 | } 155 | var ueData *UeData 156 | ueData = c.GetUe(ctx, ueIdString) 157 | if ueData == nil { 158 | ueData = c.CreateUe(ctx, ueIdString) 159 | c.AttachUe(ctx, ueData, cgi, cgiObject) 160 | } else if ueData.CGIString != cgi { 161 | return 162 | } 163 | 164 | ueData.E2NodeID = e2NodeID 165 | 166 | ueData.RsrpServing, ueData.RsrpNeighbors, ueData.RsrpTable, ueData.CgiTable = c.GetRsrpFromMeasReport(ctx, GetNciFromCellGlobalID(header.GetCgi()), message.MeasReport) 167 | 168 | old5qi := ueData.FiveQi 169 | ueData.FiveQi = c.GetFiveQiFromMeasReport(ctx, GetNciFromCellGlobalID(header.GetCgi()), message.MeasReport) 170 | if *flag && (old5qi != ueData.FiveQi) { 171 | log.Infof("\t\tQUALITY MESSAGE: 5QI for UE [ID:%v] changed [5QI:%v]\n", ueData.UeID, ueData.FiveQi) 172 | } 173 | 174 | c.SetUe(ctx, ueData) 175 | 176 | } 177 | 178 | func (c *Controller) handleRrcState(ctx context.Context, header *e2sm_mho.E2SmMhoIndicationHeaderFormat1, message *e2sm_mho.E2SmMhoIndicationMessageFormat2, e2NodeID string) { 179 | c.mu.Lock() 180 | defer c.mu.Unlock() 181 | ueID, err := GetUeID(message.GetUeId()) 182 | if err != nil { 183 | log.Errorf("handleRrcState() couldn't extract UeID: %v", err) 184 | } 185 | cgi := GetCGIFromIndicationHeader(header) 186 | cgi = c.ConvertCgiToTheRightForm(cgi) 187 | cgiObject := header.GetCgi() 188 | 189 | ueIdString := strconv.Itoa(int(ueID)) 190 | n := (16 - len(ueIdString)) 191 | for i := 0; i < n; i++ { 192 | ueIdString = "0" + ueIdString 193 | } 194 | var ueData *UeData 195 | ueData = c.GetUe(ctx, ueIdString) 196 | if ueData == nil { 197 | ueData = c.CreateUe(ctx, ueIdString) 198 | c.AttachUe(ctx, ueData, cgi, cgiObject) 199 | } else if ueData.CGIString != cgi { 200 | return 201 | } 202 | 203 | ueData.E2NodeID = e2NodeID 204 | 205 | newRrcState := message.GetRrcStatus().String() 206 | c.SetUeRrcState(ctx, ueData, newRrcState, cgi, cgiObject) 207 | 208 | c.SetUe(ctx, ueData) 209 | 210 | } 211 | 212 | func (c *Controller) CreateUe(ctx context.Context, ueID string) *UeData { 213 | if len(ueID) == 0 { 214 | panic("bad data") 215 | } 216 | ueData := &UeData{ 217 | UeID: ueID, 218 | CGIString: "", 219 | RrcState: e2sm_mho.Rrcstatus_name[int32(e2sm_mho.Rrcstatus_RRCSTATUS_CONNECTED)], 220 | RsrpNeighbors: make(map[string]int32), 221 | Idle: false, 222 | } 223 | _, err := c.ueStore.Put(ctx, ueID, *ueData) 224 | if err != nil { 225 | log.Warn(err) 226 | } 227 | 228 | return ueData 229 | } 230 | 231 | func (c *Controller) GetUe(ctx context.Context, ueID string) *UeData { 232 | var ueData *UeData 233 | u, err := c.ueStore.Get(ctx, ueID) 234 | if err != nil || u == nil { 235 | return nil 236 | } 237 | t := u.Value.(UeData) 238 | ueData = &t 239 | if ueData.UeID != ueID { 240 | panic("bad data") 241 | } 242 | 243 | return ueData 244 | } 245 | 246 | func (c *Controller) SetUe(ctx context.Context, ueData *UeData) { 247 | _, err := c.ueStore.Put(ctx, ueData.UeID, *ueData) 248 | if err != nil { 249 | panic("bad data") 250 | } 251 | } 252 | 253 | func (c *Controller) AttachUe(ctx context.Context, ueData *UeData, cgi string, cgiObject *e2sm_v2_ies.Cgi) { 254 | 255 | c.DetachUe(ctx, ueData) 256 | 257 | ueData.CGIString = cgi 258 | ueData.CGI = cgiObject 259 | c.SetUe(ctx, ueData) 260 | cell := c.GetCell(ctx, cgi) 261 | if cell == nil { 262 | cell = c.CreateCell(ctx, cgi, cgiObject) 263 | } 264 | cell.Ues[ueData.UeID] = ueData 265 | c.SetCell(ctx, cell) 266 | } 267 | 268 | func (c *Controller) DetachUe(ctx context.Context, ueData *UeData) { 269 | for _, cell := range c.cells { 270 | delete(cell.Ues, ueData.UeID) 271 | } 272 | } 273 | 274 | func (c *Controller) SetUeRrcState(ctx context.Context, ueData *UeData, newRrcState string, cgi string, cgiObject *e2sm_v2_ies.Cgi) { 275 | oldRrcState := ueData.RrcState 276 | 277 | if oldRrcState == e2sm_mho.Rrcstatus_name[int32(e2sm_mho.Rrcstatus_RRCSTATUS_CONNECTED)] && 278 | newRrcState == e2sm_mho.Rrcstatus_name[int32(e2sm_mho.Rrcstatus_RRCSTATUS_IDLE)] { 279 | ueData.Idle = true 280 | c.DetachUe(ctx, ueData) 281 | } else if oldRrcState == e2sm_mho.Rrcstatus_name[int32(e2sm_mho.Rrcstatus_RRCSTATUS_IDLE)] && 282 | newRrcState == e2sm_mho.Rrcstatus_name[int32(e2sm_mho.Rrcstatus_RRCSTATUS_CONNECTED)] { 283 | ueData.Idle = false 284 | c.AttachUe(ctx, ueData, cgi, cgiObject) 285 | } 286 | ueData.RrcState = newRrcState 287 | } 288 | 289 | func (c *Controller) CreateCell(ctx context.Context, cgi string, cgiObject *e2sm_v2_ies.Cgi) *CellData { 290 | if len(cgi) == 0 { 291 | panic("bad data") 292 | } 293 | cellData := &CellData{ 294 | CGI: cgiObject, 295 | CGIString: cgi, 296 | Ues: make(map[string]*UeData), 297 | } 298 | _, err := c.cellStore.Put(ctx, cgi, *cellData) 299 | if err != nil { 300 | panic("bad data") 301 | } 302 | c.cells[cellData.CGIString] = cellData 303 | return cellData 304 | } 305 | 306 | func (c *Controller) GetCell(ctx context.Context, cgi string) *CellData { 307 | var cellData *CellData 308 | cell, err := c.cellStore.Get(ctx, cgi) 309 | if err != nil || cell == nil { 310 | return nil 311 | } 312 | t := cell.Value.(CellData) 313 | if t.CGIString != cgi { 314 | panic("bad data") 315 | } 316 | cellData = &t 317 | return cellData 318 | } 319 | 320 | func (c *Controller) SetCell(ctx context.Context, cellData *CellData) { 321 | if len(cellData.CGIString) == 0 { 322 | panic("bad data") 323 | } 324 | _, err := c.cellStore.Put(ctx, cellData.CGIString, *cellData) 325 | if err != nil { 326 | panic("bad data") 327 | } 328 | c.cells[cellData.CGIString] = cellData 329 | } 330 | 331 | func (c *Controller) GetFiveQiFromMeasReport(ctx context.Context, servingNci uint64, measReport []*e2sm_mho.E2SmMhoMeasurementReportItem) int64 { 332 | var fiveQiServing int64 333 | 334 | for _, measReportItem := range measReport { 335 | 336 | if GetNciFromCellGlobalID(measReportItem.GetCgi()) == servingNci { 337 | fiveQi := measReportItem.GetFiveQi() 338 | if fiveQi != nil { 339 | fiveQiServing = int64(fiveQi.GetValue()) 340 | if fiveQiServing > 127 { 341 | fiveQiServing = 2 342 | } else { 343 | fiveQiServing = 1 344 | } 345 | } else { 346 | fiveQiServing = -1 347 | } 348 | } 349 | } 350 | 351 | return fiveQiServing 352 | } 353 | 354 | func (c *Controller) GetRsrpFromMeasReport(ctx context.Context, servingNci uint64, measReport []*e2sm_mho.E2SmMhoMeasurementReportItem) (int32, map[string]int32, map[string]int32, map[string]*e2sm_v2_ies.Cgi) { 355 | var rsrpServing int32 356 | rsrpNeighbors := make(map[string]int32) 357 | rsrpTable := make(map[string]int32) 358 | cgiTable := make(map[string]*e2sm_v2_ies.Cgi) 359 | 360 | for _, measReportItem := range measReport { 361 | 362 | if GetNciFromCellGlobalID(measReportItem.GetCgi()) == servingNci { 363 | CGIString := GetCGIFromMeasReportItem(measReportItem) 364 | CGIString = c.ConvertCgiToTheRightForm(CGIString) 365 | rsrpServing = measReportItem.GetRsrp().GetValue() 366 | rsrpTable[CGIString] = measReportItem.GetRsrp().GetValue() 367 | cgiTable[CGIString] = measReportItem.GetCgi() 368 | } else { 369 | CGIString := GetCGIFromMeasReportItem(measReportItem) 370 | CGIString = c.ConvertCgiToTheRightForm(CGIString) 371 | rsrpNeighbors[CGIString] = measReportItem.GetRsrp().GetValue() 372 | rsrpTable[CGIString] = measReportItem.GetRsrp().GetValue() 373 | cgiTable[CGIString] = measReportItem.GetCgi() 374 | cell := c.GetCell(ctx, CGIString) 375 | if cell == nil { 376 | _ = c.CreateCell(ctx, CGIString, measReportItem.GetCgi()) 377 | } 378 | } 379 | } 380 | 381 | return rsrpServing, rsrpNeighbors, rsrpTable, cgiTable 382 | } 383 | 384 | func (c *Controller) CreatePolicy(ctx context.Context, key string, policy *policyAPI.API) *PolicyData { 385 | if len(key) == 0 { 386 | panic("bad data") 387 | } 388 | policyData := &PolicyData{ 389 | Key: key, 390 | API: policy, 391 | IsEnforced: true, 392 | } 393 | _, err := c.onosPolicyStore.Put(ctx, key, *policyData) 394 | if err != nil { 395 | log.Panic("bad data") 396 | } 397 | c.policies[policyData.Key] = policyData 398 | return policyData 399 | } 400 | 401 | func (c *Controller) GetPolicy(ctx context.Context, key string) *PolicyData { 402 | var policy *PolicyData 403 | p, err := c.onosPolicyStore.Get(ctx, key) 404 | if err != nil || p == nil { 405 | return nil 406 | } 407 | t := p.Value.(PolicyData) 408 | if t.Key != key { 409 | panic("bad data") 410 | } 411 | policy = &t 412 | 413 | return policy 414 | } 415 | 416 | func (c *Controller) SetPolicy(ctx context.Context, key string, policy *PolicyData) { 417 | _, err := c.onosPolicyStore.Put(ctx, key, *policy) 418 | if err != nil { 419 | panic("bad data") 420 | } 421 | c.policies[policy.Key] = policy 422 | } 423 | 424 | func (c *Controller) DeletePolicy(ctx context.Context, key string) { 425 | if err := c.onosPolicyStore.Delete(ctx, key); err != nil { 426 | panic("bad data") 427 | } else { 428 | delete(c.policies, key) 429 | } 430 | } 431 | 432 | func (c *Controller) GetPolicyStore() *store.Store { 433 | return &c.onosPolicyStore 434 | } 435 | 436 | func (c *Controller) ConvertCgiToTheRightForm(cgi string) string { 437 | if c.topoIDsEnabled { 438 | return cgi[0:6] + cgi[14:15] + cgi[12:14] + cgi[10:12] + cgi[8:10] + cgi[6:8] 439 | } 440 | return cgi 441 | } 442 | -------------------------------------------------------------------------------- /pkg/mho/reader.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Copy from onosproject/onos-mho/pkg/monitoring/monitor.go 6 | // modified by RIMEDO-Labs team 7 | package mho 8 | 9 | import ( 10 | "fmt" 11 | "strconv" 12 | 13 | e2sm_mho "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-mho-go" 14 | e2sm_v2_ies "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-v2-ies" 15 | ) 16 | 17 | func PlmnIDBytesToInt(b []byte) uint64 { 18 | return uint64(b[2])<<16 | uint64(b[1])<<8 | uint64(b[0]) 19 | } 20 | 21 | func PlmnIDNciToCGI(plmnID uint64, nci uint64) string { 22 | cgi := strconv.FormatInt(int64(plmnID<<36|(nci&0xfffffffff)), 16) 23 | return cgi 24 | } 25 | 26 | func GetNciFromCellGlobalID(cellGlobalID *e2sm_v2_ies.Cgi) uint64 { 27 | return BitStringToUint64(cellGlobalID.GetNRCgi().GetNRcellIdentity().GetValue().GetValue(), int(cellGlobalID.GetNRCgi().GetNRcellIdentity().GetValue().GetLen())) 28 | } 29 | 30 | func GetPlmnIDBytesFromCellGlobalID(cellGlobalID *e2sm_v2_ies.Cgi) []byte { 31 | return cellGlobalID.GetNRCgi().GetPLmnidentity().GetValue() 32 | } 33 | 34 | func GetMccMncFromPlmnID(plmnId uint64) (string, string) { 35 | plmnIdString := strconv.FormatUint(plmnId, 16) 36 | return plmnIdString[0:3], plmnIdString[3:] 37 | } 38 | 39 | func GetPlmnIdFromMccMnc(mcc string, mnc string) (uint64, error) { 40 | combined := mcc + mnc 41 | plmnId, err := strconv.ParseUint(combined, 16, 64) 42 | if err != nil { 43 | log.Warn("Cannot convert PLMN ID string into uint64 type!") 44 | } 45 | return plmnId, err 46 | } 47 | 48 | func GetCGIFromIndicationHeader(header *e2sm_mho.E2SmMhoIndicationHeaderFormat1) string { 49 | nci := GetNciFromCellGlobalID(header.GetCgi()) 50 | plmnIDBytes := GetPlmnIDBytesFromCellGlobalID(header.GetCgi()) 51 | plmnID := PlmnIDBytesToInt(plmnIDBytes) 52 | return PlmnIDNciToCGI(plmnID, nci) 53 | } 54 | 55 | func GetCGIFromMeasReportItem(measReport *e2sm_mho.E2SmMhoMeasurementReportItem) string { 56 | nci := GetNciFromCellGlobalID(measReport.GetCgi()) 57 | plmnIDBytes := GetPlmnIDBytesFromCellGlobalID(measReport.GetCgi()) 58 | plmnID := PlmnIDBytesToInt(plmnIDBytes) 59 | return PlmnIDNciToCGI(plmnID, nci) 60 | } 61 | 62 | func BitStringToUint64(bitString []byte, bitCount int) uint64 { 63 | var result uint64 64 | for i, b := range bitString { 65 | result += uint64(b) << ((len(bitString) - i - 1) * 8) 66 | } 67 | if bitCount%8 != 0 { 68 | return result >> (8 - bitCount%8) 69 | } 70 | return result 71 | } 72 | 73 | func GetUeID(ueID *e2sm_v2_ies.Ueid) (int64, error) { 74 | 75 | switch ue := ueID.Ueid.(type) { 76 | case *e2sm_v2_ies.Ueid_GNbUeid: 77 | return ue.GNbUeid.GetAmfUeNgapId().GetValue(), nil 78 | case *e2sm_v2_ies.Ueid_ENbUeid: 79 | return ue.ENbUeid.GetMMeUeS1ApId().GetValue(), nil 80 | case *e2sm_v2_ies.Ueid_EnGNbUeid: 81 | return int64(ue.EnGNbUeid.GetMENbUeX2ApId().GetValue()), nil 82 | case *e2sm_v2_ies.Ueid_NgENbUeid: 83 | return ue.NgENbUeid.GetAmfUeNgapId().GetValue(), nil 84 | default: 85 | return -1, fmt.Errorf("GetUeID() couldn't extract UeID - obtained unexpected type %v", ue) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pkg/monitoring/monitor.go: -------------------------------------------------------------------------------- 1 | // Copy from onosproject/onos-mho/pkg/monitoring/monitor.go 2 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 3 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | // modified by RIMEDO-Labs team 7 | 8 | package monitoring 9 | 10 | import ( 11 | "context" 12 | "github.com/onosproject/rimedo-ts/pkg/mho" 13 | 14 | e2api "github.com/onosproject/onos-api/go/onos/e2t/e2/v1beta1" 15 | topoapi "github.com/onosproject/onos-api/go/onos/topo" 16 | e2sm_mho "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-mho-go" 17 | "github.com/onosproject/onos-lib-go/pkg/logging" 18 | "github.com/onosproject/onos-mho/pkg/broker" 19 | ) 20 | 21 | var log = logging.GetLogger("rimedo-ts", "monitoring") 22 | 23 | func NewMonitor(streamReader broker.StreamReader, nodeID topoapi.ID, indChan chan *mho.E2NodeIndication, triggerType e2sm_mho.MhoTriggerType) *Monitor { 24 | return &Monitor{ 25 | streamReader: streamReader, 26 | nodeID: nodeID, 27 | indChan: indChan, 28 | triggerType: triggerType, 29 | } 30 | } 31 | 32 | type Monitor struct { 33 | streamReader broker.StreamReader 34 | nodeID topoapi.ID 35 | indChan chan *mho.E2NodeIndication 36 | triggerType e2sm_mho.MhoTriggerType 37 | } 38 | 39 | func (m *Monitor) Start(ctx context.Context) error { 40 | errCh := make(chan error) 41 | go func() { 42 | for { 43 | indMsg, err := m.streamReader.Recv(ctx) 44 | if err != nil { 45 | log.Errorf("Error reading indication stream, chanID:%v, streamID:%v, err:%v", m.streamReader.ChannelID(), m.streamReader.StreamID(), err) 46 | errCh <- err 47 | } 48 | err = m.processIndication(ctx, indMsg, m.nodeID) 49 | if err != nil { 50 | log.Errorf("Error processing indication, err:%v", err) 51 | errCh <- err 52 | } 53 | } 54 | }() 55 | 56 | select { 57 | case err := <-errCh: 58 | return err 59 | case <-ctx.Done(): 60 | return ctx.Err() 61 | } 62 | } 63 | 64 | func (m *Monitor) processIndication(ctx context.Context, indication e2api.Indication, nodeID topoapi.ID) error { 65 | 66 | m.indChan <- &mho.E2NodeIndication{ 67 | NodeID: string(nodeID), 68 | TriggerType: m.triggerType, 69 | IndMsg: e2api.Indication{ 70 | Payload: indication.Payload, 71 | Header: indication.Header, 72 | }, 73 | } 74 | 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /pkg/northbound/a1/a1ei-service.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Copy from https://github.com/woojoong88/onos-kpimon/tree/sample-a1t-xapp/pkg/northbound/a1 6 | 7 | package a1 8 | 9 | import ( 10 | "context" 11 | 12 | a1tapi "github.com/onosproject/onos-api/go/onos/a1t/a1" 13 | "github.com/onosproject/onos-lib-go/pkg/logging/service" 14 | "google.golang.org/grpc" 15 | ) 16 | 17 | func NewA1EIService() service.Service { 18 | log.Debugf("A1EI service created") 19 | return &A1EIService{} 20 | } 21 | 22 | type A1EIService struct { 23 | } 24 | 25 | func (a *A1EIService) Register(s *grpc.Server) { 26 | server := &A1EIServer{} 27 | a1tapi.RegisterEIServiceServer(s, server) 28 | } 29 | 30 | type A1EIServer struct { 31 | } 32 | 33 | func (a *A1EIServer) EIQuery(server a1tapi.EIService_EIQueryServer) error { 34 | log.Debug("EIQuery stream established") 35 | ch := make(chan bool) 36 | <-ch 37 | return nil 38 | } 39 | 40 | func (a *A1EIServer) EIJobSetup(server a1tapi.EIService_EIJobSetupServer) error { 41 | log.Debug("EIJobSetup stream established") 42 | ch := make(chan bool) 43 | <-ch 44 | return nil 45 | } 46 | 47 | func (a *A1EIServer) EIJobUpdate(server a1tapi.EIService_EIJobUpdateServer) error { 48 | log.Debug("EIJobUpdate stream established") 49 | ch := make(chan bool) 50 | <-ch 51 | return nil 52 | } 53 | 54 | func (a *A1EIServer) EIJobDelete(server a1tapi.EIService_EIJobDeleteServer) error { 55 | log.Debug("EIJobDelete stream established") 56 | ch := make(chan bool) 57 | <-ch 58 | return nil 59 | } 60 | 61 | func (a *A1EIServer) EIJobStatusQuery(server a1tapi.EIService_EIJobStatusQueryServer) error { 62 | log.Debug("EIJobStatusQuery stream established") 63 | ch := make(chan bool) 64 | <-ch 65 | return nil 66 | } 67 | 68 | func (a *A1EIServer) EIJobStatusNotify(ctx context.Context, message *a1tapi.EIStatusMessage) (*a1tapi.EIAckMessage, error) { 69 | log.Debug("EIJobStatusNotify called %v", message) 70 | return nil, nil 71 | } 72 | 73 | func (a *A1EIServer) EIJobResultDelivery(ctx context.Context, message *a1tapi.EIResultMessage) (*a1tapi.EIAckMessage, error) { 74 | log.Debug("EIJobResultDelivery called %v", message) 75 | return nil, nil 76 | } 77 | -------------------------------------------------------------------------------- /pkg/northbound/a1/a1p-service.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Copy from https://github.com/woojoong88/onos-kpimon/tree/sample-a1t-xapp/pkg/northbound/a1 6 | // modified by RIMEDO Labs team 7 | 8 | package a1 9 | 10 | import ( 11 | "context" 12 | "encoding/json" 13 | "fmt" 14 | "sync" 15 | "time" 16 | 17 | "github.com/google/uuid" 18 | a1tapi "github.com/onosproject/onos-api/go/onos/a1t/a1" 19 | "github.com/onosproject/onos-lib-go/pkg/logging/service" 20 | "google.golang.org/grpc" 21 | ) 22 | 23 | var SampleJSON1 = ` 24 | { 25 | "scope": { 26 | "ueId": "0000000000000001" 27 | }, 28 | "tspResources": [ 29 | { 30 | "cellIdList": [ 31 | {"plmnId": {"mcc": "248","mnc": "35"}, 32 | "cId": {"ncI": 39}}, 33 | {"plmnId": {"mcc": "248","mnc": "35"}, 34 | "cId": {"ncI": 40}} 35 | ], 36 | "preference": "PREFER" 37 | }, 38 | { 39 | "cellIdList": [ 40 | {"plmnId": {"mcc": "248","mnc": "35"}, 41 | "cId": {"ncI": 81}}, 42 | {"plmnId": {"mcc": "248","mnc": "35"}, 43 | "cId": {"ncI": 82}}, 44 | {"plmnId": {"mcc": "248","mnc": "35"}, 45 | "cId": {"ncI": 83}} 46 | ], 47 | "preference": "FORBID" 48 | } 49 | ] 50 | } 51 | ` 52 | 53 | var SampleJSON2 = ` 54 | { 55 | "scope": { 56 | "ueId": "0000000000000002" 57 | }, 58 | "tspResources": [ 59 | { 60 | "cellIdList": [ 61 | {"plmnId": {"mcc": "248","mnc": "35"}, 62 | "cId": {"ncI": 39}}, 63 | {"plmnId": {"mcc": "248","mnc": "35"}, 64 | "cId": {"ncI": 40}} 65 | ], 66 | "preference": "PREFER" 67 | }, 68 | { 69 | "cellIdList": [ 70 | {"plmnId": {"mcc": "248","mnc": "35"}, 71 | "cId": {"ncI": 81}}, 72 | {"plmnId": {"mcc": "248","mnc": "35"}, 73 | "cId": {"ncI": 82}}, 74 | {"plmnId": {"mcc": "248","mnc": "35"}, 75 | "cId": {"ncI": 83}} 76 | ], 77 | "preference": "FORBID" 78 | } 79 | ] 80 | } 81 | ` 82 | 83 | var SampleEnforcedStatus = ` 84 | { 85 | "enforceStatus": "ENFORCED" 86 | } 87 | ` 88 | 89 | var SampleNotEnforcedStatus = ` 90 | { 91 | "enforceStatus": "NOT_ENFORCED", 92 | "enforceReason": "SCOPE_NOT_APPLICABLE" 93 | } 94 | ` 95 | 96 | var SampleNotEnforcedPolicyID = "2" 97 | 98 | func NewA1PService(policyMap *map[string][]byte, notifier chan bool) service.Service { 99 | return &A1PService{ 100 | TsPolicyTypeMap: policyMap, 101 | notifier: notifier, 102 | } 103 | } 104 | 105 | type A1PService struct { 106 | TsPolicyTypeMap *map[string][]byte 107 | notifier chan bool 108 | } 109 | 110 | func (a *A1PService) Register(s *grpc.Server) { 111 | server := &A1PServer{ 112 | TsPolicyTypeMap: *a.TsPolicyTypeMap, 113 | StatusUpdateCh: make(chan *a1tapi.PolicyStatusMessage), 114 | notifier: a.notifier, 115 | } 116 | a1tapi.RegisterPolicyServiceServer(s, server) 117 | } 118 | 119 | type A1PServer struct { 120 | TsPolicyTypeMap map[string][]byte 121 | StatusUpdateCh chan *a1tapi.PolicyStatusMessage 122 | notifier chan bool 123 | mu sync.RWMutex 124 | } 125 | 126 | func (a *A1PServer) PolicySetup(ctx context.Context, message *a1tapi.PolicyRequestMessage) (*a1tapi.PolicyResultMessage, error) { 127 | a.mu.Lock() 128 | defer a.mu.Unlock() 129 | var result map[string]interface{} 130 | _ = json.Unmarshal(message.Message.Payload, &result) 131 | 132 | if message.PolicyType.Id != "ORAN_TrafficSteeringPreference_2.0.0" { 133 | res := &a1tapi.PolicyResultMessage{ 134 | PolicyId: message.PolicyId, 135 | PolicyType: message.PolicyType, 136 | Message: &a1tapi.ResultMessage{ 137 | Header: &a1tapi.Header{ 138 | PayloadType: message.Message.Header.PayloadType, 139 | RequestId: message.Message.Header.RequestId, 140 | Encoding: message.Message.Header.Encoding, 141 | AppId: message.Message.Header.AppId, 142 | }, Payload: message.Message.Payload, 143 | Result: &a1tapi.Result{ 144 | Success: false, 145 | Reason: "Policy type does not support", 146 | }, 147 | }, 148 | } 149 | return res, nil 150 | } 151 | 152 | if _, ok := a.TsPolicyTypeMap[message.PolicyId]; ok { 153 | res := &a1tapi.PolicyResultMessage{ 154 | PolicyId: message.PolicyId, 155 | PolicyType: message.PolicyType, 156 | Message: &a1tapi.ResultMessage{ 157 | Header: &a1tapi.Header{ 158 | PayloadType: message.Message.Header.PayloadType, 159 | RequestId: message.Message.Header.RequestId, 160 | Encoding: message.Message.Header.Encoding, 161 | AppId: message.Message.Header.AppId, 162 | }, Payload: message.Message.Payload, 163 | Result: &a1tapi.Result{ 164 | Success: false, 165 | Reason: "Policy ID already exists", 166 | }, 167 | }, 168 | } 169 | return res, nil 170 | } 171 | 172 | a.TsPolicyTypeMap[message.PolicyId] = message.Message.Payload 173 | 174 | go func() { 175 | if message.NotificationDestination != "" { 176 | statusUpdateMsg := &a1tapi.PolicyStatusMessage{ 177 | PolicyId: message.PolicyId, 178 | PolicyType: message.PolicyType, 179 | Message: &a1tapi.StatusMessage{ 180 | Header: &a1tapi.Header{ 181 | RequestId: uuid.New().String(), 182 | AppId: message.Message.Header.AppId, 183 | Encoding: message.Message.Header.Encoding, 184 | PayloadType: a1tapi.PayloadType_STATUS, 185 | }, 186 | }, 187 | NotificationDestination: message.NotificationDestination, 188 | } 189 | 190 | if message.PolicyId == SampleNotEnforcedPolicyID { 191 | statusUpdateMsg.Message.Payload = []byte(SampleNotEnforcedStatus) 192 | } else { 193 | statusUpdateMsg.Message.Payload = []byte(SampleEnforcedStatus) 194 | } 195 | 196 | a.StatusUpdateCh <- statusUpdateMsg 197 | } 198 | }() 199 | 200 | res := &a1tapi.PolicyResultMessage{ 201 | PolicyId: message.PolicyId, 202 | PolicyType: message.PolicyType, 203 | Message: &a1tapi.ResultMessage{ 204 | Header: &a1tapi.Header{ 205 | PayloadType: message.Message.Header.PayloadType, 206 | RequestId: message.Message.Header.RequestId, 207 | Encoding: message.Message.Header.Encoding, 208 | AppId: message.Message.Header.AppId, 209 | }, 210 | Payload: a.TsPolicyTypeMap[message.PolicyId], 211 | Result: &a1tapi.Result{ 212 | Success: true, 213 | }, 214 | }, 215 | NotificationDestination: message.NotificationDestination, 216 | } 217 | a.notifier <- true 218 | return res, nil 219 | } 220 | 221 | func (a *A1PServer) PolicyUpdate(ctx context.Context, message *a1tapi.PolicyRequestMessage) (*a1tapi.PolicyResultMessage, error) { 222 | a.mu.Lock() 223 | defer a.mu.Unlock() 224 | 225 | var result map[string]interface{} 226 | _ = json.Unmarshal(message.Message.Payload, &result) 227 | 228 | if message.PolicyType.Id != "ORAN_TrafficSteeringPreference_2.0.0" { 229 | res := &a1tapi.PolicyResultMessage{ 230 | PolicyId: message.PolicyId, 231 | PolicyType: message.PolicyType, 232 | Message: &a1tapi.ResultMessage{ 233 | Header: &a1tapi.Header{ 234 | PayloadType: message.Message.Header.PayloadType, 235 | RequestId: message.Message.Header.RequestId, 236 | Encoding: message.Message.Header.Encoding, 237 | AppId: message.Message.Header.AppId, 238 | }, Payload: message.Message.Payload, 239 | Result: &a1tapi.Result{ 240 | Success: false, 241 | Reason: "Policy type does not support", 242 | }, 243 | }, 244 | } 245 | return res, nil 246 | } 247 | 248 | if _, ok := a.TsPolicyTypeMap[message.PolicyId]; !ok { 249 | res := &a1tapi.PolicyResultMessage{ 250 | PolicyId: message.PolicyId, 251 | PolicyType: message.PolicyType, 252 | Message: &a1tapi.ResultMessage{ 253 | Header: &a1tapi.Header{ 254 | PayloadType: message.Message.Header.PayloadType, 255 | RequestId: message.Message.Header.RequestId, 256 | Encoding: message.Message.Header.Encoding, 257 | AppId: message.Message.Header.AppId, 258 | }, Payload: message.Message.Payload, 259 | Result: &a1tapi.Result{ 260 | Success: false, 261 | Reason: "Policy ID does not exists", 262 | }, 263 | }, 264 | } 265 | return res, nil 266 | } 267 | 268 | a.TsPolicyTypeMap[message.PolicyId] = message.Message.Payload 269 | 270 | go func() { 271 | if message.NotificationDestination != "" { 272 | statusUpdateMsg := &a1tapi.PolicyStatusMessage{ 273 | PolicyId: message.PolicyId, 274 | PolicyType: message.PolicyType, 275 | Message: &a1tapi.StatusMessage{ 276 | Header: &a1tapi.Header{ 277 | RequestId: uuid.New().String(), 278 | AppId: message.Message.Header.AppId, 279 | Encoding: message.Message.Header.Encoding, 280 | PayloadType: a1tapi.PayloadType_STATUS, 281 | }, 282 | }, 283 | NotificationDestination: message.NotificationDestination, 284 | } 285 | 286 | if message.PolicyId == SampleNotEnforcedPolicyID { 287 | statusUpdateMsg.Message.Payload = []byte(SampleNotEnforcedStatus) 288 | } else { 289 | statusUpdateMsg.Message.Payload = []byte(SampleEnforcedStatus) 290 | } 291 | 292 | a.StatusUpdateCh <- statusUpdateMsg 293 | } 294 | }() 295 | 296 | res := &a1tapi.PolicyResultMessage{ 297 | PolicyId: message.PolicyId, 298 | PolicyType: message.PolicyType, 299 | Message: &a1tapi.ResultMessage{ 300 | Header: &a1tapi.Header{ 301 | PayloadType: message.Message.Header.PayloadType, 302 | RequestId: message.Message.Header.RequestId, 303 | Encoding: message.Message.Header.Encoding, 304 | AppId: message.Message.Header.AppId, 305 | }, Payload: a.TsPolicyTypeMap[message.PolicyId], 306 | Result: &a1tapi.Result{ 307 | Success: true, 308 | }, 309 | }, 310 | NotificationDestination: message.NotificationDestination, 311 | } 312 | a.notifier <- true 313 | return res, nil 314 | } 315 | 316 | func (a *A1PServer) PolicyDelete(ctx context.Context, message *a1tapi.PolicyRequestMessage) (*a1tapi.PolicyResultMessage, error) { 317 | a.mu.Lock() 318 | defer a.mu.Unlock() 319 | 320 | var result map[string]interface{} 321 | _ = json.Unmarshal(message.Message.Payload, &result) 322 | 323 | if message.PolicyType.Id != "ORAN_TrafficSteeringPreference_2.0.0" { 324 | res := &a1tapi.PolicyResultMessage{ 325 | PolicyId: message.PolicyId, 326 | PolicyType: message.PolicyType, 327 | Message: &a1tapi.ResultMessage{ 328 | Header: &a1tapi.Header{ 329 | PayloadType: message.Message.Header.PayloadType, 330 | RequestId: message.Message.Header.RequestId, 331 | Encoding: message.Message.Header.Encoding, 332 | AppId: message.Message.Header.AppId, 333 | }, Payload: message.Message.Payload, 334 | Result: &a1tapi.Result{ 335 | Success: false, 336 | Reason: "Policy type does not support", 337 | }, 338 | }, 339 | } 340 | return res, nil 341 | } 342 | 343 | if _, ok := a.TsPolicyTypeMap[message.PolicyId]; !ok { 344 | res := &a1tapi.PolicyResultMessage{ 345 | PolicyId: message.PolicyId, 346 | PolicyType: message.PolicyType, 347 | Message: &a1tapi.ResultMessage{ 348 | Header: &a1tapi.Header{ 349 | PayloadType: message.Message.Header.PayloadType, 350 | RequestId: message.Message.Header.RequestId, 351 | Encoding: message.Message.Header.Encoding, 352 | AppId: message.Message.Header.AppId, 353 | }, Payload: message.Message.Payload, 354 | Result: &a1tapi.Result{ 355 | Success: false, 356 | Reason: "Policy ID does not exists", 357 | }, 358 | }, 359 | } 360 | return res, nil 361 | } 362 | 363 | delete(a.TsPolicyTypeMap, message.PolicyId) 364 | 365 | res := &a1tapi.PolicyResultMessage{ 366 | PolicyId: message.PolicyId, 367 | PolicyType: message.PolicyType, 368 | Message: &a1tapi.ResultMessage{ 369 | Header: &a1tapi.Header{ 370 | PayloadType: message.Message.Header.PayloadType, 371 | RequestId: message.Message.Header.RequestId, 372 | Encoding: message.Message.Header.Encoding, 373 | AppId: message.Message.Header.AppId, 374 | }, Payload: a.TsPolicyTypeMap[message.PolicyId], 375 | Result: &a1tapi.Result{ 376 | Success: true, 377 | }, 378 | }, 379 | } 380 | a.notifier <- true 381 | return res, nil 382 | } 383 | 384 | func (a *A1PServer) PolicyQuery(ctx context.Context, message *a1tapi.PolicyRequestMessage) (*a1tapi.PolicyResultMessage, error) { 385 | a.mu.Lock() 386 | defer a.mu.Unlock() 387 | var result map[string]interface{} 388 | _ = json.Unmarshal(message.Message.Payload, &result) 389 | 390 | if message.PolicyType.Id != "ORAN_TrafficSteeringPreference_2.0.0" { 391 | res := &a1tapi.PolicyResultMessage{ 392 | PolicyId: message.PolicyId, 393 | PolicyType: message.PolicyType, 394 | Message: &a1tapi.ResultMessage{ 395 | Header: &a1tapi.Header{ 396 | PayloadType: message.Message.Header.PayloadType, 397 | RequestId: message.Message.Header.RequestId, 398 | Encoding: message.Message.Header.Encoding, 399 | AppId: message.Message.Header.AppId, 400 | }, Payload: message.Message.Payload, 401 | Result: &a1tapi.Result{ 402 | Success: false, 403 | Reason: "Policy type does not support", 404 | }, 405 | }, 406 | } 407 | return res, nil 408 | } 409 | 410 | if message.PolicyId == "" { 411 | 412 | listPolicies := make([]string, 0) 413 | for k := range a.TsPolicyTypeMap { 414 | listPolicies = append(listPolicies, k) 415 | } 416 | 417 | listPoliciesJson, err := json.Marshal(listPolicies) 418 | if err != nil { 419 | log.Error(err) 420 | } 421 | 422 | res := &a1tapi.PolicyResultMessage{ 423 | PolicyType: message.PolicyType, 424 | Message: &a1tapi.ResultMessage{ 425 | Header: &a1tapi.Header{ 426 | PayloadType: message.Message.Header.PayloadType, 427 | RequestId: message.Message.Header.RequestId, 428 | Encoding: message.Message.Header.Encoding, 429 | AppId: message.Message.Header.AppId, 430 | }, Payload: listPoliciesJson, 431 | Result: &a1tapi.Result{ 432 | Success: true, 433 | }, 434 | }, 435 | } 436 | return res, nil 437 | } 438 | 439 | if _, ok := a.TsPolicyTypeMap[message.PolicyId]; !ok { 440 | res := &a1tapi.PolicyResultMessage{ 441 | PolicyId: message.PolicyId, 442 | PolicyType: message.PolicyType, 443 | Message: &a1tapi.ResultMessage{ 444 | Header: &a1tapi.Header{ 445 | PayloadType: message.Message.Header.PayloadType, 446 | RequestId: message.Message.Header.RequestId, 447 | Encoding: message.Message.Header.Encoding, 448 | AppId: message.Message.Header.AppId, 449 | }, Payload: message.Message.Payload, 450 | Result: &a1tapi.Result{ 451 | Success: false, 452 | Reason: "Policy ID does not exists", 453 | }, 454 | }, 455 | } 456 | return res, nil 457 | } 458 | 459 | resultMsg := &a1tapi.PolicyResultMessage{ 460 | PolicyId: message.PolicyId, 461 | PolicyType: message.PolicyType, 462 | Message: &a1tapi.ResultMessage{ 463 | Header: &a1tapi.Header{ 464 | PayloadType: message.Message.Header.PayloadType, 465 | RequestId: message.Message.Header.RequestId, 466 | Encoding: message.Message.Header.Encoding, 467 | AppId: message.Message.Header.AppId, 468 | }, 469 | Result: &a1tapi.Result{ 470 | Success: true, 471 | }, 472 | }, 473 | } 474 | 475 | switch message.Message.Header.PayloadType { 476 | case a1tapi.PayloadType_POLICY: 477 | resultMsg.Message.Payload = a.TsPolicyTypeMap[message.PolicyId] 478 | resultMsg.Message.Header.PayloadType = a1tapi.PayloadType_POLICY 479 | case a1tapi.PayloadType_STATUS: 480 | resultMsg.Message.Header.PayloadType = a1tapi.PayloadType_STATUS 481 | if message.PolicyId == SampleNotEnforcedPolicyID { 482 | resultMsg.Message.Payload = []byte(SampleNotEnforcedStatus) 483 | 484 | } else { 485 | resultMsg.Message.Payload = []byte(SampleEnforcedStatus) 486 | } 487 | } 488 | return resultMsg, nil 489 | } 490 | 491 | func (a *A1PServer) PolicyStatus(server a1tapi.PolicyService_PolicyStatusServer) error { 492 | 493 | watchers := make(map[uuid.UUID]chan *a1tapi.PolicyAckMessage) 494 | mu := sync.RWMutex{} 495 | 496 | go func(m *sync.RWMutex) { 497 | for { 498 | ack, err := server.Recv() 499 | if err != nil { 500 | log.Error(err) 501 | } 502 | m.Lock() 503 | for _, v := range watchers { 504 | select { 505 | case v <- ack: 506 | log.Debugf("Sent msg %v on %v", ack, v) 507 | default: 508 | log.Debugf("Failed to send msg %v on %v", ack, v) 509 | } 510 | } 511 | m.Unlock() 512 | } 513 | }(&a.mu) 514 | 515 | for msg := range a.StatusUpdateCh { 516 | watcherID := uuid.New() 517 | ackCh := make(chan *a1tapi.PolicyAckMessage) 518 | timerCh := make(chan bool, 1) 519 | go func(ch chan bool) { 520 | time.Sleep(5 * time.Second) 521 | timerCh <- true 522 | close(timerCh) 523 | }(timerCh) 524 | 525 | go func(m *sync.RWMutex) { 526 | for { 527 | select { 528 | case ack := <-ackCh: 529 | if ack.Message.Header.RequestId == msg.Message.Header.RequestId { 530 | m.Lock() 531 | close(ackCh) 532 | delete(watchers, watcherID) 533 | m.Unlock() 534 | return 535 | } 536 | case <-timerCh: 537 | log.Error(fmt.Errorf("could not receive PolicyACKMessage in timer")) 538 | m.Lock() 539 | close(ackCh) 540 | delete(watchers, watcherID) 541 | m.Unlock() 542 | return 543 | } 544 | } 545 | }(&a.mu) 546 | 547 | mu.Lock() 548 | watchers[watcherID] = ackCh 549 | mu.Unlock() 550 | 551 | err := server.Send(msg) 552 | if err != nil { 553 | log.Error(err) 554 | mu.Lock() 555 | close(ackCh) 556 | delete(watchers, watcherID) 557 | mu.Unlock() 558 | } 559 | } 560 | 561 | return nil 562 | } 563 | -------------------------------------------------------------------------------- /pkg/northbound/a1/manager.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Copy from https://github.com/woojoong88/onos-kpimon/tree/sample-a1t-xapp/pkg/northbound/a1 6 | // modified by RIMEDO Labs team 7 | 8 | package a1 9 | 10 | import ( 11 | "context" 12 | 13 | "github.com/onosproject/onos-api/go/onos/topo" 14 | "github.com/onosproject/onos-lib-go/pkg/logging" 15 | a1connection "github.com/onosproject/onos-ric-sdk-go/pkg/a1/connection" 16 | ) 17 | 18 | var log = logging.GetLogger("rimedo-ts", "a1") 19 | 20 | type Config struct { 21 | PolicyName string 22 | PolicyVersion string 23 | PolicyID string 24 | PolicyDescription string 25 | A1tPort int 26 | } 27 | 28 | func NewManager(caPath string, keyPath string, certPath string, grpcPort int, xAppName string, a1PolicyTypes []*topo.A1PolicyType) (*Manager, error) { 29 | a1ConnManager, err := a1connection.NewManager(caPath, keyPath, certPath, grpcPort, a1PolicyTypes) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return &Manager{ 34 | a1ConnManager: a1ConnManager, 35 | }, nil 36 | } 37 | 38 | type Manager struct { 39 | a1ConnManager *a1connection.Manager 40 | } 41 | 42 | func (m *Manager) Start() { 43 | m.a1ConnManager.Start(context.Background()) 44 | } 45 | 46 | func (m *Manager) Close(ctx context.Context) { 47 | err := m.a1ConnManager.DeleteXAppElementOnTopo(ctx) 48 | if err != nil { 49 | log.Error(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pkg/policy/manager.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Created by RIMEDO-Labs team 6 | package policy 7 | 8 | import ( 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "io/ioutil" 13 | "math" 14 | "os" 15 | 16 | policyAPI "github.com/onosproject/onos-a1-dm/go/policy_schemas/traffic_steering_preference/v2" 17 | "github.com/onosproject/onos-lib-go/pkg/logging" 18 | "github.com/onosproject/rimedo-ts/pkg/mho" 19 | "github.com/xeipuuv/gojsonschema" 20 | ) 21 | 22 | var log = logging.GetLogger("rimedo-ts", "policy") 23 | 24 | func NewPolicySchemaValidatorV2(path string) *PolicySchemaValidatorV2 { 25 | 26 | return &PolicySchemaValidatorV2{ 27 | schemePath: path, 28 | } 29 | 30 | } 31 | 32 | type PolicySchemaValidatorV2 struct { 33 | schemePath string 34 | } 35 | 36 | func NewPolicyManager(policyMap *map[string]*mho.PolicyData) *PolicyManager { 37 | 38 | var POLICY_WEIGHTS = map[string]int{ 39 | "DEFAULT": 0.0, 40 | "PREFER": 16.0, 41 | "AVOID": -16.0, 42 | "SHALL": 1000.0, 43 | "FORBID": -1000.0, 44 | } 45 | 46 | return &PolicyManager{ 47 | validator: NewPolicySchemaValidatorV2("schemePath"), 48 | policyMap: policyMap, 49 | preferenceMap: POLICY_WEIGHTS, 50 | } 51 | 52 | } 53 | 54 | type PolicyManager struct { 55 | validator *PolicySchemaValidatorV2 56 | policyMap *map[string]*mho.PolicyData 57 | preferenceMap map[string]int 58 | } 59 | 60 | func (m *PolicyManager) ReadPolicyObjectFromFileV2(jsonPath string, policyObject *mho.PolicyData) error { 61 | 62 | jsonFile, err := m.LoadPolicyJsonFromFileV2(jsonPath) 63 | if err != nil { 64 | log.Error("Couldn't read PolicyObject from file") 65 | return err 66 | } 67 | 68 | var ok bool 69 | ok, err = m.ValidatePolicyJsonSchemaV2(jsonPath) 70 | if err != nil { 71 | log.Error("Error validating json scheme") 72 | return err 73 | } 74 | if !ok { 75 | return errors.New("the json file is invalid") 76 | } 77 | if err = m.UnmarshalPolicyJsonV2(jsonFile, policyObject); err != nil { 78 | log.Error("Error unmarshaling json file") 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (m *PolicyManager) CheckPerUePolicyV2(ueScope policyAPI.Scope, policyObject *mho.PolicyData) bool { 86 | 87 | if policyObject.API.Scope.UeID == nil { 88 | return false 89 | } 90 | 91 | if *policyObject.API.Scope.UeID == "" { 92 | return false 93 | } 94 | 95 | if *policyObject.API.Scope.UeID != *ueScope.UeID { 96 | return false 97 | } 98 | 99 | if (policyObject.API.Scope.SliceID != nil) && (((policyObject.API.Scope.SliceID.SD == nil || (policyObject.API.Scope.SliceID.SD != nil && *policyObject.API.Scope.SliceID.SD == "")) || 100 | policyObject.API.Scope.SliceID.Sst <= 0 || policyObject.API.Scope.SliceID.PlmnID.Mcc == "" || policyObject.API.Scope.SliceID.PlmnID.Mnc == "") || 101 | (*policyObject.API.Scope.SliceID.SD != *ueScope.SliceID.SD || policyObject.API.Scope.SliceID.Sst != ueScope.SliceID.Sst || 102 | policyObject.API.Scope.SliceID.PlmnID.Mcc != ueScope.SliceID.PlmnID.Mcc || policyObject.API.Scope.SliceID.PlmnID.Mnc != ueScope.SliceID.PlmnID.Mnc)) { 103 | return false 104 | } 105 | 106 | if (policyObject.API.Scope.QosID != nil) && ((policyObject.API.Scope.QosID.QcI == nil && policyObject.API.Scope.QosID.The5QI == nil) || 107 | (policyObject.API.Scope.QosID.QcI != nil && *policyObject.API.Scope.QosID.QcI != *ueScope.QosID.QcI) || 108 | (policyObject.API.Scope.QosID.The5QI != nil && *policyObject.API.Scope.QosID.The5QI != *ueScope.QosID.The5QI)) { 109 | return false 110 | } 111 | 112 | if (policyObject.API.Scope.CellID != nil) && (((policyObject.API.Scope.CellID.CID.NcI == nil && policyObject.API.Scope.CellID.CID.EcI == nil) || 113 | (policyObject.API.Scope.CellID.CID.NcI != nil && *policyObject.API.Scope.CellID.CID.NcI != *ueScope.CellID.CID.NcI) || 114 | (policyObject.API.Scope.CellID.CID.EcI != nil && *policyObject.API.Scope.CellID.CID.EcI != *ueScope.CellID.CID.EcI)) || 115 | ((policyObject.API.Scope.CellID.PlmnID.Mcc == "" || policyObject.API.Scope.CellID.PlmnID.Mnc == "") || 116 | (policyObject.API.Scope.CellID.PlmnID.Mcc != ueScope.CellID.PlmnID.Mcc || policyObject.API.Scope.CellID.PlmnID.Mnc != ueScope.CellID.PlmnID.Mnc))) { 117 | return false 118 | } 119 | 120 | return true 121 | } 122 | 123 | func (m *PolicyManager) CheckPerSlicePolicyV2(ueScope policyAPI.Scope, policyObject *mho.PolicyData) bool { 124 | 125 | if policyObject.API.Scope.SliceID == nil { 126 | return false 127 | } 128 | 129 | if (policyObject.API.Scope.SliceID != nil && *policyObject.API.Scope.SliceID.SD == "") || 130 | policyObject.API.Scope.SliceID.Sst <= 0 || 131 | policyObject.API.Scope.SliceID.PlmnID.Mcc == "" || 132 | policyObject.API.Scope.SliceID.PlmnID.Mnc == "" { 133 | return false 134 | } 135 | 136 | if (policyObject.API.Scope.UeID != nil) && !((*policyObject.API.Scope.UeID == "") || (*policyObject.API.Scope.UeID == *ueScope.UeID)) { 137 | return false 138 | } 139 | 140 | if (policyObject.API.Scope.QosID != nil) && ((policyObject.API.Scope.QosID.QcI == nil && policyObject.API.Scope.QosID.The5QI == nil) || 141 | (policyObject.API.Scope.QosID.QcI != nil && *policyObject.API.Scope.QosID.QcI != *ueScope.QosID.QcI) || 142 | (policyObject.API.Scope.QosID.The5QI != nil && *policyObject.API.Scope.QosID.The5QI != *ueScope.QosID.The5QI)) { 143 | return false 144 | } 145 | 146 | if (policyObject.API.Scope.CellID != nil) && (((policyObject.API.Scope.CellID.CID.NcI == nil && policyObject.API.Scope.CellID.CID.EcI == nil) || 147 | (policyObject.API.Scope.CellID.CID.NcI != nil && *policyObject.API.Scope.CellID.CID.NcI != *ueScope.CellID.CID.NcI) || 148 | (policyObject.API.Scope.CellID.CID.EcI != nil && *policyObject.API.Scope.CellID.CID.EcI != *ueScope.CellID.CID.EcI)) || 149 | ((policyObject.API.Scope.CellID.PlmnID.Mcc == "" || policyObject.API.Scope.CellID.PlmnID.Mnc == "") || 150 | (policyObject.API.Scope.CellID.PlmnID.Mcc != ueScope.CellID.PlmnID.Mcc || policyObject.API.Scope.CellID.PlmnID.Mnc != ueScope.CellID.PlmnID.Mnc))) { 151 | return false 152 | } 153 | 154 | return true 155 | } 156 | 157 | func (m *PolicyManager) GetTsResultForUEV2(ueScope policyAPI.Scope, rsrps []int, cellIds []policyAPI.CellID) policyAPI.CellID { 158 | 159 | var bestCell policyAPI.CellID 160 | bestScore := -math.MaxFloat64 161 | for i := 0; i < len(rsrps); i++ { 162 | preferece := m.GetPreferenceV2(ueScope, cellIds[i]) 163 | score := m.GetPreferenceScoresV2(preferece, rsrps[i]) 164 | if score > bestScore { 165 | bestCell = cellIds[i] 166 | bestScore = score 167 | } 168 | } 169 | return bestCell 170 | } 171 | 172 | func (m *PolicyManager) GetPreferenceScoresV2(preference string, rsrp int) float64 { 173 | return float64(rsrp) + float64(m.preferenceMap[preference]) 174 | } 175 | 176 | func (m *PolicyManager) GetPreferenceV2(ueScope policyAPI.Scope, queryCellId policyAPI.CellID) string { 177 | 178 | var preference string = "DEFAULT" 179 | for _, policy := range *m.policyMap { 180 | if policy.IsEnforced { 181 | if m.CheckPerSlicePolicyV2(ueScope, policy) || m.CheckPerUePolicyV2(ueScope, policy) { 182 | for _, tspResource := range policy.API.TSPResources { 183 | 184 | for _, cellId := range tspResource.CellIDList { 185 | if ((cellId.CID.NcI != nil && queryCellId.CID.NcI != nil && *cellId.CID.NcI == *queryCellId.CID.NcI) || 186 | (cellId.CID.EcI != nil && queryCellId.CID.EcI != nil && *cellId.CID.EcI == *queryCellId.CID.EcI)) && 187 | (cellId.PlmnID.Mcc == queryCellId.PlmnID.Mcc && cellId.PlmnID.Mnc == queryCellId.PlmnID.Mnc) { 188 | preference = string(tspResource.Preference) 189 | } 190 | } 191 | } 192 | } 193 | } 194 | } 195 | return preference 196 | } 197 | 198 | func (m *PolicyManager) AddPolicyV2(policyId string, policyDir string, policyObject *mho.PolicyData) (*mho.PolicyData, error) { 199 | 200 | policyPath := policyDir + policyId 201 | err := m.ReadPolicyObjectFromFileV2(policyPath, policyObject) 202 | if err != nil { 203 | log.Error(fmt.Sprintf("Couldn't read PolicyObject from file \n policyId: %s from: %s", policyId, policyPath)) 204 | return nil, err 205 | } 206 | return policyObject, nil 207 | } 208 | 209 | func (m *PolicyManager) EnforcePolicyV2(policyId string) bool { 210 | 211 | if _, ok := (*m.policyMap)[policyId]; ok { 212 | (*m.policyMap)[policyId].IsEnforced = true 213 | return true 214 | } 215 | log.Error(fmt.Sprintf("Policy with policyId: %s, not enforced", policyId)) 216 | return false 217 | } 218 | 219 | func (m *PolicyManager) DisablePolicyV2(policyId string) bool { 220 | 221 | if _, ok := (*m.policyMap)[policyId]; ok { 222 | (*m.policyMap)[policyId].IsEnforced = false 223 | return true 224 | } 225 | log.Error(fmt.Sprintf("Policy with policyId: %s, not enforced", policyId)) 226 | return false 227 | } 228 | 229 | func (m *PolicyManager) GetPolicyV2(policyId string) (*mho.PolicyData, bool) { 230 | 231 | if val, ok := (*m.policyMap)[policyId]; ok { 232 | return val, ok 233 | } 234 | log.Error(fmt.Sprintf("Policy with policyId: %s, not enforced", policyId)) 235 | return nil, false 236 | } 237 | 238 | func (m *PolicyManager) ValidatePolicyJsonSchemaV2(jsonPath string) (bool, error) { 239 | 240 | schemaLoader := gojsonschema.NewReferenceLoader("file://" + m.validator.schemePath) 241 | documentLoader := gojsonschema.NewReferenceLoader("file://" + jsonPath) 242 | 243 | result, err := gojsonschema.Validate(schemaLoader, documentLoader) 244 | if err != nil { 245 | return false, err 246 | } 247 | return result.Valid(), nil 248 | } 249 | 250 | func (m *PolicyManager) UnmarshalPolicyJsonV2(jsonFile []byte, policyObject *mho.PolicyData) error { 251 | 252 | if err := json.Unmarshal(jsonFile, policyObject.API); err != nil { 253 | log.Error("Couldn't read PolicyObject from file") 254 | return err 255 | } 256 | policyObject.IsEnforced = false 257 | return nil 258 | 259 | } 260 | 261 | func (m *PolicyManager) LoadPolicyJsonFromFileV2(path string) ([]byte, error) { 262 | 263 | jsonFile, err := os.Open(path) 264 | if err != nil { 265 | log.Error("Failed to open policy JSON File") 266 | return nil, err 267 | } 268 | 269 | defer jsonFile.Close() 270 | 271 | byteValue, err := ioutil.ReadAll(jsonFile) 272 | if err != nil { 273 | log.Error("Failed to read data from policy JSON File") 274 | return nil, err 275 | } 276 | return byteValue, nil 277 | 278 | } 279 | 280 | //func (m *PolicyManager) isSimilarEnforced(policyData *mho.PolicyData) bool { 281 | // for _, policy := range *m.policyMap { 282 | // 283 | // sameSlice := false 284 | // sameUE := false 285 | // sameQoS := false 286 | // sameCellID := false 287 | // 288 | // if (policyData.API.Scope.SliceID == nil && policy.API.Scope.SliceID == nil) || 289 | // (policyData.API.Scope.SliceID != nil && policy.API.Scope.SliceID != nil && 290 | // policy.API.Scope.SliceID.Sst == policyData.API.Scope.SliceID.Sst && 291 | // policy.API.Scope.SliceID.SD != nil && policyData.API.Scope.SliceID.SD != nil && 292 | // *policy.API.Scope.SliceID.SD == *policyData.API.Scope.SliceID.SD && 293 | // policy.API.Scope.SliceID.PlmnID.Mcc == policyData.API.Scope.SliceID.PlmnID.Mcc && 294 | // policy.API.Scope.SliceID.PlmnID.Mnc == policyData.API.Scope.SliceID.PlmnID.Mnc) { 295 | // sameSlice = true 296 | // } 297 | // 298 | // if (policyData.API.Scope.UeID == nil && policy.API.Scope.UeID == nil) || 299 | // (policyData.API.Scope.UeID != nil && policy.API.Scope.UeID != nil && *policy.API.Scope.UeID == *policyData.API.Scope.UeID) { 300 | // sameUE = true 301 | // } 302 | // 303 | // if (policyData.API.Scope.QosID == nil && policy.API.Scope.QosID == nil) || 304 | // (policyData.API.Scope.QosID != nil && policy.API.Scope.QosID != nil && 305 | // ((policy.API.Scope.QosID.QcI != nil && policyData.API.Scope.QosID.QcI != nil && 306 | // *policy.API.Scope.QosID.QcI == *policyData.API.Scope.QosID.QcI) || 307 | // (policy.API.Scope.QosID.The5QI != nil && policyData.API.Scope.QosID.The5QI != nil && 308 | // *policy.API.Scope.QosID.The5QI == *policyData.API.Scope.QosID.The5QI))) { 309 | // sameQoS = true 310 | // } 311 | // 312 | // if (policyData.API.Scope.CellID == nil && policy.API.Scope.CellID == nil) || 313 | // (policyData.API.Scope.CellID != nil && policy.API.Scope.CellID != nil && 314 | // ((policy.API.Scope.CellID.CID.NcI != nil && policyData.API.Scope.CellID.CID.NcI != nil && 315 | // *policy.API.Scope.CellID.CID.NcI == *policyData.API.Scope.CellID.CID.NcI) || 316 | // (policy.API.Scope.CellID.CID.EcI != nil && policyData.API.Scope.CellID.CID.EcI != nil && 317 | // *policy.API.Scope.CellID.CID.EcI == *policyData.API.Scope.CellID.CID.EcI)) && 318 | // policy.API.Scope.CellID.PlmnID.Mcc == policyData.API.Scope.CellID.PlmnID.Mcc && 319 | // policy.API.Scope.CellID.PlmnID.Mnc == policyData.API.Scope.CellID.PlmnID.Mnc) { 320 | // sameCellID = true 321 | // } 322 | // 323 | // if sameSlice { 324 | // if sameUE { 325 | // if sameQoS { 326 | // if !sameCellID { 327 | // continue 328 | // } 329 | // } else { 330 | // continue 331 | // } 332 | // } else { 333 | // if sameQoS { 334 | // if !sameCellID { 335 | // continue 336 | // } 337 | // } else { 338 | // continue 339 | // } 340 | // } 341 | // } else { 342 | // if sameUE { 343 | // if sameQoS { 344 | // if !sameCellID { 345 | // continue 346 | // } 347 | // } else { 348 | // continue 349 | // } 350 | // } else { 351 | // continue 352 | // } 353 | // } 354 | // 355 | // if policy.IsEnforced { 356 | // policy.IsEnforced = false 357 | // return true 358 | // } 359 | // 360 | // } 361 | // return false 362 | //} 363 | -------------------------------------------------------------------------------- /pkg/rnib/rnib.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Created by RIMEDO-Labs team 6 | // based on onosproject/onos-mho/pkg/rnib/rnib.go 7 | package rnib 8 | 9 | import ( 10 | "context" 11 | 12 | topoapi "github.com/onosproject/onos-api/go/onos/topo" 13 | "github.com/onosproject/onos-lib-go/pkg/logging" 14 | toposdk "github.com/onosproject/onos-ric-sdk-go/pkg/topo" 15 | ) 16 | 17 | var log = logging.GetLogger("rimedo-ts", "rnib") 18 | 19 | type TopoClient interface { 20 | WatchE2Connections(ctx context.Context, ch chan topoapi.Event) error 21 | } 22 | 23 | type Options struct { 24 | TopoAddress string 25 | TopoPort int 26 | } 27 | 28 | type Cell struct { 29 | CGI string 30 | CellType string 31 | } 32 | 33 | func NewClient(options Options) (Client, error) { 34 | sdkClient, err := toposdk.NewClient( 35 | toposdk.WithTopoAddress( 36 | options.TopoAddress, 37 | options.TopoPort, 38 | ), 39 | ) 40 | if err != nil { 41 | return Client{}, err 42 | } 43 | return Client{ 44 | client: sdkClient, 45 | }, nil 46 | } 47 | 48 | type Client struct { 49 | client toposdk.Client 50 | } 51 | 52 | func getControlRelationFilter() *topoapi.Filters { 53 | controlRelationFilter := &topoapi.Filters{ 54 | KindFilter: &topoapi.Filter{ 55 | Filter: &topoapi.Filter_Equal_{ 56 | Equal_: &topoapi.EqualFilter{ 57 | Value: topoapi.CONTROLS, 58 | }, 59 | }, 60 | }, 61 | } 62 | return controlRelationFilter 63 | } 64 | 65 | func (c *Client) WatchE2Connections(ctx context.Context, ch chan topoapi.Event) error { 66 | err := c.client.Watch(ctx, ch, toposdk.WithWatchFilters(getControlRelationFilter())) 67 | if err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | func (c *Client) GetE2CellFilter() *topoapi.Filters { 74 | cellEntityFilter := &topoapi.Filters{ 75 | KindFilter: &topoapi.Filter{ 76 | Filter: &topoapi.Filter_In{ 77 | In: &topoapi.InFilter{ 78 | Values: []string{topoapi.E2CELL}, 79 | }, 80 | }, 81 | }, 82 | } 83 | return cellEntityFilter 84 | } 85 | 86 | func (c *Client) GetCellTypes(ctx context.Context) (map[string]Cell, error) { 87 | output := make(map[string]Cell) 88 | 89 | cells, err := c.client.List(ctx, toposdk.WithListFilters(c.GetE2CellFilter())) 90 | if err != nil { 91 | log.Warn(err) 92 | return output, err 93 | } 94 | 95 | for _, cell := range cells { 96 | 97 | cellObject := &topoapi.E2Cell{} 98 | err = cell.GetAspect(cellObject) 99 | if err != nil { 100 | log.Warn(err) 101 | } 102 | output[string(cell.ID)] = Cell{ 103 | CGI: cellObject.CellObjectID, 104 | CellType: cellObject.CellType, 105 | } 106 | } 107 | return output, nil 108 | } 109 | 110 | func (c *Client) SetCellType(ctx context.Context, id string, cellType string) error { 111 | cell, err := c.client.Get(ctx, topoapi.ID(id)) 112 | if err != nil { 113 | log.Warn(err) 114 | return err 115 | } 116 | 117 | cellObject := &topoapi.E2Cell{} 118 | err = cell.GetAspect(cellObject) 119 | if err != nil { 120 | log.Warn(err) 121 | return err 122 | } 123 | 124 | cellObject.CellType = cellType 125 | 126 | err = cell.SetAspect(cellObject) 127 | if err != nil { 128 | log.Warn(err) 129 | return err 130 | } 131 | err = c.client.Update(ctx, cell) 132 | if err != nil { 133 | log.Warn(err) 134 | return err 135 | } 136 | 137 | return nil 138 | } 139 | 140 | func (c *Client) GetE2NodeAspects(ctx context.Context, nodeID topoapi.ID) (*topoapi.E2Node, error) { 141 | object, err := c.client.Get(ctx, nodeID) 142 | if err != nil { 143 | return nil, err 144 | } 145 | e2Node := &topoapi.E2Node{} 146 | err = object.GetAspect(e2Node) 147 | 148 | return e2Node, err 149 | 150 | } 151 | -------------------------------------------------------------------------------- /pkg/sdran/manager.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package sdran 7 | 8 | import ( 9 | "context" 10 | "strconv" 11 | "sync" 12 | 13 | policyAPI "github.com/onosproject/onos-a1-dm/go/policy_schemas/traffic_steering_preference/v2" 14 | e2tAPI "github.com/onosproject/onos-api/go/onos/e2t/e2" 15 | e2api "github.com/onosproject/onos-api/go/onos/e2t/e2/v1beta1" 16 | "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/pdubuilder" 17 | e2sm_v2_ies "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-v2-ies" 18 | "github.com/onosproject/onos-lib-go/pkg/logging" 19 | "github.com/onosproject/onos-lib-go/pkg/logging/service" 20 | "github.com/onosproject/onos-lib-go/pkg/northbound" 21 | control "github.com/onosproject/onos-mho/pkg/mho" 22 | "github.com/onosproject/onos-mho/pkg/store" 23 | "github.com/onosproject/rimedo-ts/pkg/mho" 24 | "github.com/onosproject/rimedo-ts/pkg/policy" 25 | "github.com/onosproject/rimedo-ts/pkg/rnib" 26 | "github.com/onosproject/rimedo-ts/pkg/southbound/e2" 27 | ) 28 | 29 | var log = logging.GetLogger("rimedo-ts", "sdran", "manager") 30 | 31 | type Config struct { 32 | AppID string 33 | E2tAddress string 34 | E2tPort int 35 | TopoAddress string 36 | TopoPort int 37 | SMName string 38 | SMVersion string 39 | TSPolicySchemePath string 40 | } 41 | 42 | func NewManager(config Config, flag bool) *Manager { 43 | 44 | ueStore := store.NewStore() 45 | cellStore := store.NewStore() 46 | onosPolicyStore := store.NewStore() 47 | 48 | policyMap := make(map[string]*mho.PolicyData) 49 | 50 | indCh := make(chan *mho.E2NodeIndication) 51 | ctrlReqChs := make(map[string]chan *e2api.ControlMessage) 52 | 53 | options := e2.Options{ 54 | AppID: config.AppID, 55 | E2tAddress: config.E2tAddress, 56 | E2tPort: config.E2tPort, 57 | TopoAddress: config.TopoAddress, 58 | TopoPort: config.TopoPort, 59 | SMName: config.SMName, 60 | SMVersion: config.SMVersion, 61 | } 62 | 63 | e2Manager, err := e2.NewManager(options, indCh, ctrlReqChs) 64 | if err != nil { 65 | log.Warn(err) 66 | } 67 | 68 | manager := &Manager{ 69 | e2Manager: e2Manager, 70 | mhoCtrl: mho.NewController(indCh, ueStore, cellStore, onosPolicyStore, policyMap, flag), 71 | policyManager: policy.NewPolicyManager(&policyMap), 72 | ueStore: ueStore, 73 | cellStore: cellStore, 74 | onosPolicyStore: onosPolicyStore, 75 | ctrlReqChs: ctrlReqChs, 76 | services: []service.Service{}, 77 | mutex: sync.RWMutex{}, 78 | } 79 | return manager 80 | } 81 | 82 | type Manager struct { 83 | e2Manager e2.Manager 84 | mhoCtrl *mho.Controller 85 | policyManager *policy.PolicyManager 86 | ueStore store.Store 87 | cellStore store.Store 88 | onosPolicyStore store.Store 89 | ctrlReqChs map[string]chan *e2api.ControlMessage 90 | services []service.Service 91 | mutex sync.RWMutex 92 | } 93 | 94 | func (m *Manager) Run(flag *bool) { 95 | if err := m.start(flag); err != nil { 96 | log.Fatal("Unable to run Manager", err) 97 | } 98 | } 99 | 100 | func (m *Manager) start(flag *bool) error { 101 | _ = m.startNorthboundServer() 102 | err := m.e2Manager.Start() 103 | if err != nil { 104 | log.Warn(err) 105 | return err 106 | } 107 | 108 | go m.mhoCtrl.Run(context.Background(), flag) 109 | 110 | return nil 111 | } 112 | 113 | func (m *Manager) startNorthboundServer() error { 114 | 115 | s := northbound.NewServer(northbound.NewServerCfg( 116 | "", 117 | "", 118 | "", 119 | int16(5150), 120 | true, 121 | northbound.SecurityConfig{})) 122 | 123 | for i := range m.services { 124 | s.AddService(m.services[i]) 125 | } 126 | 127 | doneCh := make(chan error) 128 | go func() { 129 | err := s.Serve(func(started string) { 130 | close(doneCh) 131 | }) 132 | if err != nil { 133 | doneCh <- err 134 | } 135 | }() 136 | return <-doneCh 137 | } 138 | 139 | func (m *Manager) AddService(service service.Service) { 140 | 141 | m.services = append(m.services, service) 142 | 143 | } 144 | 145 | func (m *Manager) GetUEs(ctx context.Context) map[string]mho.UeData { 146 | output := make(map[string]mho.UeData) 147 | chEntries := make(chan *store.Entry, 1024) 148 | err := m.ueStore.Entries(ctx, chEntries) 149 | if err != nil { 150 | log.Warn(err) 151 | return output 152 | } 153 | for entry := range chEntries { 154 | ueData := entry.Value.(mho.UeData) 155 | output[ueData.UeID] = ueData 156 | } 157 | return output 158 | } 159 | 160 | func (m *Manager) GetCells(ctx context.Context) map[string]mho.CellData { 161 | output := make(map[string]mho.CellData) 162 | chEntries := make(chan *store.Entry, 1024) 163 | err := m.cellStore.Entries(ctx, chEntries) 164 | if err != nil { 165 | log.Warn(err) 166 | return output 167 | } 168 | for entry := range chEntries { 169 | cellData := entry.Value.(mho.CellData) 170 | output[cellData.CGIString] = cellData 171 | } 172 | return output 173 | } 174 | 175 | func (m *Manager) GetPolicies(ctx context.Context) map[string]mho.PolicyData { 176 | output := make(map[string]mho.PolicyData) 177 | chEntries := make(chan *store.Entry, 1024) 178 | err := m.onosPolicyStore.Entries(ctx, chEntries) 179 | if err != nil { 180 | log.Warn(err) 181 | return output 182 | } 183 | for entry := range chEntries { 184 | policyData := entry.Value.(mho.PolicyData) 185 | output[policyData.Key] = policyData 186 | } 187 | return output 188 | } 189 | 190 | func (m *Manager) GetCellTypes(ctx context.Context) map[string]rnib.Cell { 191 | return m.e2Manager.GetCellTypes(ctx) 192 | } 193 | 194 | func (m *Manager) SetCellType(ctx context.Context, cellID string, cellType string) error { 195 | return m.e2Manager.SetCellType(ctx, cellID, cellType) 196 | } 197 | 198 | func (m *Manager) GetCell(ctx context.Context, CGI string) *mho.CellData { 199 | 200 | return m.mhoCtrl.GetCell(ctx, CGI) 201 | 202 | } 203 | 204 | func (m *Manager) SetCell(ctx context.Context, cell *mho.CellData) { 205 | 206 | m.mhoCtrl.SetCell(ctx, cell) 207 | 208 | } 209 | 210 | func (m *Manager) AttachUe(ctx context.Context, ue *mho.UeData, CGI string, cgiObject *e2sm_v2_ies.Cgi) { 211 | 212 | m.mhoCtrl.AttachUe(ctx, ue, CGI, cgiObject) 213 | 214 | } 215 | 216 | func (m *Manager) GetUe(ctx context.Context, ueID string) *mho.UeData { 217 | 218 | return m.mhoCtrl.GetUe(ctx, ueID) 219 | 220 | } 221 | 222 | func (m *Manager) SetUe(ctx context.Context, ueData *mho.UeData) { 223 | 224 | m.mhoCtrl.SetUe(ctx, ueData) 225 | 226 | } 227 | 228 | func (m *Manager) CreatePolicy(ctx context.Context, key string, policy *policyAPI.API) *mho.PolicyData { 229 | 230 | return m.mhoCtrl.CreatePolicy(ctx, key, policy) 231 | 232 | } 233 | 234 | func (m *Manager) GetPolicy(ctx context.Context, key string) *mho.PolicyData { 235 | 236 | return m.mhoCtrl.GetPolicy(ctx, key) 237 | 238 | } 239 | 240 | func (m *Manager) SetPolicy(ctx context.Context, key string, policy *mho.PolicyData) { 241 | 242 | m.mhoCtrl.SetPolicy(ctx, key, policy) 243 | 244 | } 245 | 246 | func (m *Manager) DeletePolicy(ctx context.Context, key string) { 247 | 248 | m.mhoCtrl.DeletePolicy(ctx, key) 249 | 250 | } 251 | 252 | func (m *Manager) GetPolicyStore() *store.Store { 253 | return m.mhoCtrl.GetPolicyStore() 254 | } 255 | 256 | func (m *Manager) GetControlChannelsMap(ctx context.Context) map[string]chan *e2api.ControlMessage { 257 | return m.ctrlReqChs 258 | } 259 | 260 | func (m *Manager) GetPolicyManager() *policy.PolicyManager { 261 | return m.policyManager 262 | } 263 | 264 | func (m *Manager) SwitchUeBetweenCells(ctx context.Context, ueID string, targetCellCGI string) { 265 | 266 | m.mutex.Lock() 267 | defer m.mutex.Unlock() 268 | 269 | availableUes := m.GetUEs(ctx) 270 | chosenUe := availableUes[ueID] 271 | 272 | if shouldBeSwitched(chosenUe, targetCellCGI) { 273 | 274 | targetCell := m.GetCell(ctx, targetCellCGI) 275 | servingCell := m.GetCell(ctx, chosenUe.CGIString) 276 | 277 | targetCell.CumulativeHandoversOut++ 278 | servingCell.CumulativeHandoversIn++ 279 | 280 | chosenUe.Idle = false 281 | m.AttachUe(ctx, &chosenUe, targetCellCGI, targetCell.CGI) 282 | 283 | m.SetCell(ctx, targetCell) 284 | m.SetCell(ctx, servingCell) 285 | 286 | controlChannel := m.ctrlReqChs[chosenUe.E2NodeID] 287 | 288 | controlHandler := &control.E2SmMhoControlHandler{ 289 | NodeID: chosenUe.E2NodeID, 290 | ControlAckRequest: e2tAPI.ControlAckRequest_NO_ACK, 291 | } 292 | 293 | ueIDnum, err := strconv.Atoi(chosenUe.UeID) 294 | if err != nil { 295 | log.Errorf("SendHORequest() failed to convert string %v to decimal number - assumption is not satisfied (UEID is a decimal number): %v", chosenUe.UeID, err) 296 | } 297 | 298 | ueIdentity, err := pdubuilder.CreateUeIDGNb(int64(ueIDnum), []byte{0xAA, 0xBB, 0xCC}, []byte{0xDD}, []byte{0xCC, 0xC0}, []byte{0xFC}) 299 | if err != nil { 300 | log.Errorf("SendHORequest() Failed to create UEID: %v", err) 301 | } 302 | 303 | servingPlmnIDBytes := servingCell.CGI.GetNRCgi().GetPLmnidentity().GetValue() 304 | servingNCI := servingCell.CGI.GetNRCgi().GetNRcellIdentity().GetValue().GetValue() 305 | servingNCILen := servingCell.CGI.GetNRCgi().GetNRcellIdentity().GetValue().GetLen() 306 | 307 | go func() { 308 | if controlHandler.ControlHeader, err = controlHandler.CreateMhoControlHeader(servingNCI, servingNCILen, 1, servingPlmnIDBytes); err == nil { 309 | 310 | if controlHandler.ControlMessage, err = controlHandler.CreateMhoControlMessage(servingCell.CGI, ueIdentity, targetCell.CGI); err == nil { 311 | 312 | if controlRequest, err := controlHandler.CreateMhoControlRequest(); err == nil { 313 | 314 | controlChannel <- controlRequest 315 | log.Infof("CONTROL MESSAGE: UE [ID:%v, 5QI:%v] switched between CELLs [CGI:%v -> CGI:%v]\n", chosenUe.UeID, chosenUe.FiveQi, servingCell.CGIString, targetCell.CGIString) 316 | 317 | } else { 318 | log.Warn("Control request problem!", err) 319 | } 320 | } else { 321 | log.Warn("Control message problem!", err) 322 | } 323 | } else { 324 | log.Warn("Control header problem!", err) 325 | } 326 | }() 327 | 328 | } 329 | 330 | } 331 | 332 | func shouldBeSwitched(ue mho.UeData, cgi string) bool { 333 | return ue.CGIString != cgi 334 | } 335 | -------------------------------------------------------------------------------- /pkg/southbound/e2/manager.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // Created by RIMEDO-Labs team 6 | // based on onosproject/onos-mho/pkg/southbound/e2/manager.go 7 | 8 | package e2 9 | 10 | import ( 11 | "context" 12 | "fmt" 13 | "strings" 14 | 15 | prototypes "github.com/gogo/protobuf/types" 16 | e2api "github.com/onosproject/onos-api/go/onos/e2t/e2/v1beta1" 17 | topoapi "github.com/onosproject/onos-api/go/onos/topo" 18 | "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/pdubuilder" 19 | e2sm_mho "github.com/onosproject/onos-e2-sm/servicemodels/e2sm_mho_go/v2/e2sm-mho-go" 20 | "github.com/onosproject/onos-lib-go/pkg/errors" 21 | "github.com/onosproject/onos-lib-go/pkg/logging" 22 | "github.com/onosproject/onos-mho/pkg/broker" 23 | e2client "github.com/onosproject/onos-ric-sdk-go/pkg/e2/v1beta1" 24 | "github.com/onosproject/rimedo-ts/pkg/mho" 25 | "github.com/onosproject/rimedo-ts/pkg/monitoring" 26 | "github.com/onosproject/rimedo-ts/pkg/rnib" 27 | "google.golang.org/protobuf/proto" 28 | ) 29 | 30 | var log = logging.GetLogger("rimedo-ts", "e2", "manager") 31 | 32 | const ( 33 | oid = "1.3.6.1.4.1.53148.1.2.2.101" 34 | ) 35 | 36 | type Options struct { 37 | AppID string 38 | E2tAddress string 39 | E2tPort int 40 | TopoAddress string 41 | TopoPort int 42 | SMName string 43 | SMVersion string 44 | } 45 | 46 | func NewManager(options Options, indCh chan *mho.E2NodeIndication, ctrlReqChs map[string]chan *e2api.ControlMessage) (Manager, error) { 47 | 48 | smName := e2client.ServiceModelName(options.SMName) 49 | smVer := e2client.ServiceModelVersion(options.SMVersion) 50 | appID := e2client.AppID(options.AppID) 51 | e2Client := e2client.NewClient( 52 | e2client.WithAppID(appID), 53 | e2client.WithServiceModel(smName, smVer), 54 | e2client.WithE2TAddress(options.E2tAddress, options.E2tPort), 55 | ) 56 | 57 | rnibOptions := rnib.Options{ 58 | TopoAddress: options.TopoAddress, 59 | TopoPort: options.TopoPort, 60 | } 61 | 62 | rnibClient, err := rnib.NewClient(rnibOptions) 63 | if err != nil { 64 | return Manager{}, err 65 | } 66 | 67 | return Manager{ 68 | e2client: e2Client, 69 | rnibClient: rnibClient, 70 | streams: broker.NewBroker(), 71 | indCh: indCh, 72 | ctrlReqChs: ctrlReqChs, 73 | smModelName: smName, 74 | }, nil 75 | } 76 | 77 | type Manager struct { 78 | e2client e2client.Client 79 | rnibClient rnib.Client 80 | streams broker.Broker 81 | indCh chan *mho.E2NodeIndication 82 | ctrlReqChs map[string]chan *e2api.ControlMessage 83 | smModelName e2client.ServiceModelName 84 | } 85 | 86 | func (m *Manager) Start() error { 87 | go func() { 88 | ctx, cancel := context.WithCancel(context.Background()) 89 | defer cancel() 90 | err := m.watchE2Connections(ctx) 91 | if err != nil { 92 | return 93 | } 94 | }() 95 | 96 | return nil 97 | } 98 | 99 | func (m *Manager) watchE2Connections(ctx context.Context) error { 100 | ch := make(chan topoapi.Event) 101 | err := m.rnibClient.WatchE2Connections(ctx, ch) 102 | if err != nil { 103 | log.Warn(err) 104 | return err 105 | } 106 | 107 | for topoEvent := range ch { 108 | if topoEvent.Type == topoapi.EventType_ADDED || topoEvent.Type == topoapi.EventType_NONE { 109 | relation := topoEvent.Object.Obj.(*topoapi.Object_Relation) 110 | e2NodeID := relation.Relation.TgtEntityID 111 | m.ctrlReqChs[string(e2NodeID)] = make(chan *e2api.ControlMessage) 112 | 113 | triggers := make(map[e2sm_mho.MhoTriggerType]bool) 114 | triggers[e2sm_mho.MhoTriggerType_MHO_TRIGGER_TYPE_PERIODIC] = true 115 | triggers[e2sm_mho.MhoTriggerType_MHO_TRIGGER_TYPE_UPON_RCV_MEAS_REPORT] = true 116 | triggers[e2sm_mho.MhoTriggerType_MHO_TRIGGER_TYPE_UPON_CHANGE_RRC_STATUS] = true 117 | 118 | for triggerType, enabled := range triggers { 119 | if enabled { 120 | go func(triggerType e2sm_mho.MhoTriggerType) { 121 | _ = m.createSubscription(ctx, e2NodeID, triggerType) 122 | }(triggerType) 123 | } 124 | } 125 | go m.watchMHOChanges(ctx, e2NodeID) 126 | } 127 | } 128 | 129 | return nil 130 | } 131 | 132 | func (m *Manager) watchMHOChanges(ctx context.Context, e2nodeID topoapi.ID) { 133 | 134 | for ctrlReqMsg := range m.ctrlReqChs[string(e2nodeID)] { 135 | go func(ctrlReqMsg *e2api.ControlMessage) { 136 | node := m.e2client.Node(e2client.NodeID(e2nodeID)) 137 | _, _ = node.Control(ctx, ctrlReqMsg) 138 | }(ctrlReqMsg) 139 | } 140 | } 141 | 142 | func (m *Manager) createSubscription(ctx context.Context, e2nodeID topoapi.ID, triggerType e2sm_mho.MhoTriggerType) error { 143 | eventTriggerData, err := m.createEventTrigger(triggerType) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | actions := m.createSubscriptionActions() 149 | 150 | aspects, err := m.rnibClient.GetE2NodeAspects(ctx, e2nodeID) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | _, err = m.getRanFunction(aspects.ServiceModels) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | ch := make(chan e2api.Indication) 161 | node := m.e2client.Node(e2client.NodeID(e2nodeID)) 162 | subName := fmt.Sprintf("rimedo-ts-subscription-%s", triggerType) 163 | subSpec := e2api.SubscriptionSpec{ 164 | Actions: actions, 165 | EventTrigger: e2api.EventTrigger{ 166 | Payload: eventTriggerData, 167 | }, 168 | } 169 | 170 | channelID, err := node.Subscribe(ctx, subName, subSpec, ch) 171 | if err != nil { 172 | log.Warn(err) 173 | return err 174 | } 175 | 176 | streamReader, err := m.streams.OpenReader(ctx, node, subName, channelID, subSpec) 177 | if err != nil { 178 | return err 179 | } 180 | go m.sendIndicationOnStream(streamReader.StreamID(), ch) 181 | 182 | monitor := monitoring.NewMonitor(streamReader, e2nodeID, m.indCh, triggerType) 183 | 184 | err = monitor.Start(ctx) 185 | if err != nil { 186 | log.Warn(err) 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func (m *Manager) getRanFunction(serviceModelsInfo map[string]*topoapi.ServiceModelInfo) (*topoapi.MHORanFunction, error) { 193 | for _, sm := range serviceModelsInfo { 194 | smName := strings.ToLower(sm.Name) 195 | if smName == string(m.smModelName) && sm.OID == oid { 196 | mhoRanFunction := &topoapi.MHORanFunction{} 197 | for _, ranFunction := range sm.RanFunctions { 198 | if ranFunction.TypeUrl == ranFunction.GetTypeUrl() { 199 | err := prototypes.UnmarshalAny(ranFunction, mhoRanFunction) 200 | if err != nil { 201 | return nil, err 202 | } 203 | return mhoRanFunction, nil 204 | } 205 | } 206 | } 207 | } 208 | return nil, errors.New(errors.NotFound, "cannot retrieve ran functions") 209 | 210 | } 211 | 212 | func (m *Manager) createEventTrigger(triggerType e2sm_mho.MhoTriggerType) ([]byte, error) { 213 | var reportPeriodMs int32 214 | reportingPeriod := 1000 215 | if triggerType == e2sm_mho.MhoTriggerType_MHO_TRIGGER_TYPE_PERIODIC { 216 | reportPeriodMs = int32(reportingPeriod) 217 | } else { 218 | reportPeriodMs = 0 219 | } 220 | e2smRcEventTriggerDefinition, err := pdubuilder.CreateE2SmMhoEventTriggerDefinition(triggerType) 221 | if err != nil { 222 | return []byte{}, err 223 | } 224 | e2smRcEventTriggerDefinition.GetEventDefinitionFormats().GetEventDefinitionFormat1().SetReportingPeriodInMs(reportPeriodMs) 225 | 226 | err = e2smRcEventTriggerDefinition.Validate() 227 | if err != nil { 228 | return []byte{}, err 229 | } 230 | 231 | protoBytes, err := proto.Marshal(e2smRcEventTriggerDefinition) 232 | if err != nil { 233 | return []byte{}, err 234 | } 235 | 236 | return protoBytes, err 237 | } 238 | 239 | func (m *Manager) createSubscriptionActions() []e2api.Action { 240 | actions := make([]e2api.Action, 0) 241 | action := &e2api.Action{ 242 | ID: int32(0), 243 | Type: e2api.ActionType_ACTION_TYPE_REPORT, 244 | SubsequentAction: &e2api.SubsequentAction{ 245 | Type: e2api.SubsequentActionType_SUBSEQUENT_ACTION_TYPE_CONTINUE, 246 | TimeToWait: e2api.TimeToWait_TIME_TO_WAIT_ZERO, 247 | }, 248 | } 249 | actions = append(actions, *action) 250 | return actions 251 | } 252 | 253 | func (m *Manager) sendIndicationOnStream(streamID broker.StreamID, ch chan e2api.Indication) { 254 | streamWriter, err := m.streams.GetWriter(streamID) 255 | if err != nil { 256 | log.Error(err) 257 | return 258 | } 259 | 260 | for msg := range ch { 261 | err := streamWriter.Send(msg) 262 | if err != nil { 263 | log.Warn(err) 264 | return 265 | } 266 | } 267 | } 268 | 269 | func (m *Manager) GetCellTypes(ctx context.Context) map[string]rnib.Cell { 270 | cellTypes, err := m.rnibClient.GetCellTypes(ctx) 271 | if err != nil { 272 | log.Warn(err) 273 | } 274 | return cellTypes 275 | } 276 | 277 | func (m *Manager) SetCellType(ctx context.Context, cellID string, cellType string) error { 278 | err := m.rnibClient.SetCellType(ctx, cellID, cellType) 279 | if err != nil { 280 | log.Warn(err) 281 | return err 282 | } 283 | return nil 284 | } 285 | -------------------------------------------------------------------------------- /test/ts/suite.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-present Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package ts 6 | 7 | import ( 8 | "github.com/onosproject/rimedo-ts/test/utils" 9 | "github.com/onosproject/helmit/pkg/helm" 10 | "github.com/onosproject/helmit/pkg/input" 11 | "github.com/onosproject/helmit/pkg/test" 12 | testutils "github.com/onosproject/onos-ric-sdk-go/pkg/utils" 13 | ) 14 | 15 | // TestSuite has sdran release and test suite 16 | type TestSuite struct { 17 | sdran *helm.HelmRelease 18 | test.Suite 19 | c *input.Context 20 | } 21 | 22 | // SetupTestSuite prepares test suite setup 23 | func (s *TestSuite) SetupTestSuite(c *input.Context) error { 24 | s.c = c 25 | // write files 26 | err := utils.WriteFile("/tmp/tls.cacrt", utils.TLSCacrt) 27 | if err != nil { 28 | return err 29 | } 30 | err = utils.WriteFile("/tmp/tls.crt", utils.TLSCrt) 31 | if err != nil { 32 | return err 33 | } 34 | err = utils.WriteFile("/tmp/tls.key", utils.TLSKey) 35 | if err != nil { 36 | return err 37 | } 38 | err = utils.WriteFile("/tmp/config.json", utils.ConfigJSON) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | sdran, err := utils.CreateSdranRelease(c) 44 | if err != nil { 45 | return err 46 | } 47 | s.sdran = sdran 48 | r := sdran.Install(true) 49 | testutils.StartTestProxy() 50 | return r 51 | } 52 | 53 | // TearDownTestSuite uninstalls helm chart released 54 | func (s *TestSuite) TearDownTestSuite() error { 55 | testutils.StopTestProxy() 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /test/ts/ts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package ts 7 | 8 | import ( 9 | "fmt" 10 | "net" 11 | "os" 12 | "strings" 13 | 14 | "github.com/onosproject/onos-api/go/onos/ransim/types" 15 | 16 | //"context" 17 | "testing" 18 | "time" 19 | 20 | "github.com/onosproject/onos-lib-go/pkg/certs" 21 | "github.com/onosproject/rimedo-ts/pkg/manager" 22 | "github.com/onosproject/rimedo-ts/pkg/northbound/a1" 23 | "github.com/onosproject/rimedo-ts/pkg/sdran" 24 | "github.com/onosproject/rimedo-ts/test/utils" 25 | ) 26 | 27 | // TestTsSm is the function for Helmit-based integration test 28 | func (s *TestSuite) TestTsSm(t *testing.T) { 29 | // update xApp's IP and port number 30 | hostname, err := os.Hostname() 31 | if err != nil { 32 | t.Error(err) 33 | return 34 | } 35 | addr, err := net.LookupIP(hostname) 36 | if err != nil { 37 | t.Error(err) 38 | return 39 | } 40 | 41 | t.Logf("Hostname: %v", hostname) 42 | t.Logf("IP: %v", addr) 43 | err = os.Setenv("POD_NAME", hostname) 44 | if err != nil { 45 | t.Error(err) 46 | return 47 | } 48 | 49 | err = os.Setenv("POD_IP", addr[0].String()) 50 | if err != nil { 51 | t.Error(err) 52 | return 53 | } 54 | 55 | sdranConfig := sdran.Config{ 56 | AppID: "rimedo-ts", 57 | E2tAddress: "onos-e2t", 58 | E2tPort: 5150, 59 | TopoAddress: "onos-topo", 60 | TopoPort: 5150, 61 | SMName: "oran-e2sm-mho", 62 | SMVersion: "v2", 63 | TSPolicySchemePath: "/data/schemas/ORAN_TrafficSteeringPreference_v102.json", 64 | } 65 | 66 | a1Config := a1.Config{ 67 | PolicyName: "ORAN_TrafficSteeringPreference", 68 | PolicyVersion: "2.0.0", 69 | PolicyID: "ORAN_TrafficSteeringPreference_2.0.0", 70 | PolicyDescription: "O-RAN traffic steering", 71 | A1tPort: 5150, 72 | } 73 | 74 | _, err = certs.HandleCertPaths("", "", "", true) 75 | if err != nil { 76 | t.Error(err) 77 | return 78 | } 79 | 80 | mgr := manager.NewManager(sdranConfig, a1Config, false) 81 | mgr.Run() 82 | 83 | // get UE ID 84 | ues, err := utils.GetUEID() 85 | if err != nil { 86 | t.Error(err) 87 | return 88 | } else if len(ues) != 1 { 89 | t.Errorf("the number of UEs should be 1, currently it is %v", len(ues)) 90 | return 91 | } 92 | 93 | ueID := fmt.Sprintf("%016d", ues[0].IMSI) 94 | t.Logf("ueID: %s", ueID) 95 | 96 | // get a1 policy string 97 | a1Policy := strings.Replace(utils.A1Policy, "", ueID, 1) 98 | t.Log(a1Policy) 99 | 100 | // 341114881 - 138426010504510 101 | // 341098497 - 13842601454c001 102 | 103 | // put a1 policy 104 | err = utils.PutA1Policy(a1Policy) 105 | if err != nil { 106 | t.Error(err) 107 | return 108 | } 109 | 110 | // guard interval 111 | time.Sleep(10 * time.Second) 112 | 113 | // verification step 114 | // check serving cell - periodically for 2 mins 115 | // get sCell before verification 116 | var pNCGI types.NCGI 117 | 118 | for i := 0; i < utils.VerificationTimer; i++ { 119 | time.Sleep(1 * time.Second) 120 | if i < 30 { 121 | ues, err = utils.GetUEID() 122 | if err != nil { 123 | t.Error(err) 124 | return 125 | } else if len(ues) != 1 { 126 | t.Errorf("the number of UEs should be 1, currently it is %v", len(ues)) 127 | return 128 | } 129 | pNCGI = ues[0].ServingTower 130 | t.Logf("[In Guard interval] UEID: %016d / NCGI: %x", ues[0].IMSI, ues[0].ServingTower) 131 | continue 132 | } 133 | 134 | ues, err := utils.GetUEID() 135 | if err != nil { 136 | t.Error(err) 137 | return 138 | } else if len(ues) != 1 { 139 | t.Errorf("the number of UEs should be 1, currently it is %v", len(ues)) 140 | return 141 | } 142 | 143 | t.Logf("[Checking] UEID: %016d / NCGI: %x", ues[0].IMSI, ues[0].ServingTower) 144 | // if it is different, HO happens - test failed because the installed policy prohibit the HO. 145 | if ues[0].ServingTower != pNCGI { 146 | t.Errorf("Wrong sCell NCGI: original %v / changed %x", pNCGI, ues[0].ServingTower) 147 | return 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/utils/a1.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package utils 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "net/http" 12 | ) 13 | 14 | const ( 15 | A1TAddress = "http://onos-a1t:9639" 16 | ) 17 | 18 | func PutA1Policy(policy string) error { 19 | client := &http.Client{} 20 | url := fmt.Sprintf("%s/policytypes/ORAN_TrafficSteeringPreference_2.0.0/policies/1", A1TAddress) 21 | req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer([]byte(policy))) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | req.Header.Set("Content-Type", "application/json; charset=utf-8") 27 | resp, err := client.Do(req) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | if resp.StatusCode != 200 && resp.StatusCode != 201 { 33 | return fmt.Errorf("status code is not 200 or 201: %v", resp.StatusCode) 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /test/utils/defs.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package utils 7 | 8 | const ( 9 | // MhoServiceModelName is MHO service model name 10 | MhoServiceModelName = "oran-e2sm-mho" 11 | // MhoServiceModelVersion is the version of MHO SM 12 | MhoServiceModelVersion = "v2" 13 | 14 | // TLSCacrt is TLS cacrt value 15 | TLSCacrt = "-----BEGIN CERTIFICATE-----\nMIIDYDCCAkgCCQDe99fSN9qxSTANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJV\nUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCU1lbmxvUGFyazEMMAoGA1UECgwDT05G\nMRQwEgYDVQQLDAtFbmdpbmVlcmluZzEeMBwGA1UEAwwVY2Eub3Blbm5ldHdvcmtp\nbmcub3JnMB4XDTE5MDQxMTA5MDYxM1oXDTI5MDQwODA5MDYxM1owcjELMAkGA1UE\nBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlNZW5sb1BhcmsxDDAKBgNVBAoM\nA09ORjEUMBIGA1UECwwLRW5naW5lZXJpbmcxHjAcBgNVBAMMFWNhLm9wZW5uZXR3\nb3JraW5nLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEg7CZR\nX8Y+syKHaQCh6mNIL1D065trwX8RnuKM2kBwSu034zefQAPloWugSoJgJnf5fe0j\nnUD8gN3Sm8XRhCkvf67pzfabgw4n8eJmHScyL/ugyExB6Kahwzn37bt3oT3gSqhr\n6PUznWJ8fvfVuCHZZkv/HPRp4eyAcGzbJ4TuB0go4s6VE0WU5OCxCSlAiK3lvpVr\n3DOLdYLVoCa5q8Ctl3wXDrfTLw5/Bpfrg9fF9ED2/YKIdV8KZ2ki/gwEOQqWcKp8\n0LkTlfOWsdGjp4opPuPT7njMBGXMJzJ8/J1e1aJvIsoB7n8XrfvkNiWL5U3fM4N7\nUZN9jfcl7ULmm7cCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAIh6FjkQuTfXddmZY\nFYpoTen/VD5iu2Xxc1TexwmKeH+YtaKp1Zk8PTgbCtMEwEiyslfeHTMtODfnpUIk\nDwvtB4W0PAnreRsqh9MBzdU6YZmzGyZ92vSUB3yukkHaYzyjeKM0AwgVl9yRNEZw\nY/OM070hJXXzJh3eJpLl9dlUbMKzaoAh2bZx6y3ZJIZFs/zrpGfg4lvBAvfO/59i\nmxJ9bQBSN3U2Hwp6ioOQzP0LpllfXtx9N5LanWpB0cu/HN9vAgtp3kRTBZD0M1XI\nCtit8bXV7Mz+1iGqoyUhfCYcCSjuWTgAxzir+hrdn7uO67Hv4ndCoSj4SQaGka3W\neEfVeA==\n-----END CERTIFICATE-----" 16 | // TLSKey is TLS key value 17 | TLSKey = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC42bpv4K93I1QN\nSGpTxU26V0BUm2+1ciebv5YIKOlcm1gHD7YCaxrVLbRjmHoeccjFcSyYHzB6FD2O\nXTcJTxndiBcjswk2MUQAdLsAo18rPtJL3bGgDjrC6RXeeOY3jGT8ll8WttpQngdw\nTk9WLl0nLJDGDqY9yl1uJgdDj64M3hwpTyYJFZ2PiA1RoL6bz6oeq3dXdV9ryIEx\n2myRPBP7WMFBwMa34DLaKKcICZ/mbSmASRpWCG1cgotSMb9rXsUCSDL+xNYnsnjB\noPSJFCwQF5D7ivYwIi20tGT+ZNN0Es6QZSK6fON6H7USIWyU1TBfG7Sp+GEkHco1\n/B7RsX4tAgMBAAECggEBAIvky0HsKx7g77V1vnJTebWyXo8pa2tIT02BusvGGoXp\nUr9VVouR/yaihkhxlsn/ltBGDFe8EvXw530ccpBq+so7OjfcQPZwZmRp8zRSb63M\nx15/EvRskG/98n0Bxkj3yV2Xd7M7AxHL5xlJSqWQRRNmmNIrOAi/Y+H+ibTJwhEd\npV6pULHmvu4mU2YdkX/6RLOS89aAxOzXgs6nUzp826/ugb4X34izp/WwMzCs+QJd\nQVvjA/zuLnzIqspqt9bNJ3bCs+/ovqpdYlDBYbYHA0kLEMYOQsAkrWjJWPoLoDyD\nHLZZgG9jkQzkxdUzquku4UahsZZi8317jyT5b/GX0AECgYEA43jvJk887exwc04q\ns2/ef0spPD4JGVmblyD1upWdiWNgyRxxNbOkqaR1Gp5TqcdCmNRaraP3ZKbpngmp\nQKvKGLf/RH7H1b9/NuAlLAI8rSiP6h1MDyTbO1wwdSU4f1HO/3pVyRJfXd2h8+eD\nVfXe8rfXafWBBXWOeJ52JuDStC0CgYEA0Ahn7ikHKo8HM2FIJkrhm2Y4jwSL9TvO\nS6ikBbQPUbhcdyLbHVrOLlmiiBqNnuqe8AzaHrH+T6TeP312M5GGVT9CUkIb1Vq6\nfC8yVTDg4gPlSuz974xRSujWWLutX/6Hr8eAN8L/E78LD/Ojy+DISnIzmTC2B5lM\no6WJ+BcmMgECgYAxrrY9Hc1nAd9Fr+rvqh1knBvzhnEiUkoDZjWFfSwdV9FJ26Z2\nXjg2vS6+k5oeWOEY1DjB+DAOkc4wsFeBQoQvhfCBG1e2Pc8hQy+bPxnVkChur9tu\n61Pe0THcRDbkyA94CVY3RoYB0GiRBx3OZpc9WB36jJ6TfKuTeLjBoRUkOQKBgHl9\nPzy9pxq6lojx+hGqz2BSbRtQm2+m8o4KuWc/RWcDFLTanT3iZuB4pkt3vlcdS56C\n0ur0JcFbVhOb8GijRuEH5XJmexy5NIkLgwhvWBWGEuUTzCSWPG9T1MHTMKgL3C/S\ngVWPQinE+u/g6DpLVozrbqi64sNDSpeTOCSzWDIBAoGAWBxIN1k+xQQYneI1+uvi\nd8NpUcLRilayIKQkaZFA+efXpyPOq8r0WtAh8tpTA3NFXunMiejJUF1wkzL4+NLt\n3ct4RY4eHoPQHtxOqZn7aMx+8/V4yz6IDKEsAsxK1p7AP6yt6GEWzj8OvrP7cm4Z\nNVQirzdY6fbkOULBISdVSWk=\n-----END PRIVATE KEY-----" 18 | //TLSCrt is TLS crt value 19 | TLSCrt = "-----BEGIN CERTIFICATE-----\nMIIDcTCCAlkCFErBGzsXHo1l8bmZRmDkF+h2bsdVMA0GCSqGSIb3DQEBCwUAMHIx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJTWVubG9QYXJrMQww\nCgYDVQQKDANPTkYxFDASBgNVBAsMC0VuZ2luZWVyaW5nMR4wHAYDVQQDDBVjYS5v\ncGVubmV0d29ya2luZy5vcmcwHhcNMjAwOTAxMDYwNTI2WhcNMzAwODMwMDYwNTI2\nWjB4MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCU1lbmxvUGFy\nazEMMAoGA1UECgwDT05GMRQwEgYDVQQLDAtFbmdpbmVlcmluZzEkMCIGA1UEAwwb\nb25vcy1lMnQub3Blbm5ldHdvcmtpbmcub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAuNm6b+CvdyNUDUhqU8VNuldAVJtvtXInm7+WCCjpXJtYBw+2\nAmsa1S20Y5h6HnHIxXEsmB8wehQ9jl03CU8Z3YgXI7MJNjFEAHS7AKNfKz7SS92x\noA46wukV3njmN4xk/JZfFrbaUJ4HcE5PVi5dJyyQxg6mPcpdbiYHQ4+uDN4cKU8m\nCRWdj4gNUaC+m8+qHqt3V3Vfa8iBMdpskTwT+1jBQcDGt+Ay2iinCAmf5m0pgEka\nVghtXIKLUjG/a17FAkgy/sTWJ7J4waD0iRQsEBeQ+4r2MCIttLRk/mTTdBLOkGUi\nunzjeh+1EiFslNUwXxu0qfhhJB3KNfwe0bF+LQIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQCAron90Id5rzqn73M7FcCN9pFtu1MZ/WYNBxPmrcRc/yZ80PecZoHgTnJh\nmBDTLwpoRLPimxTL4OzrnA6Go0kD/CPAThehGb8BBZ+aiSJ17I0/EL1HDmXStgRk\nWuqP2DxenckWHaNmPVE0PbB6BCsd5HP0tCC4vGBbGYbJmAhhjzhzEmEypqskt+Np\neFe1DgDyfVrroIHmDLPCEu2ny9Syr/LslDmndGses8/QSVDDyAK/LFFMukCJRWsQ\nuIUJM/aDEAqbZUs4bb60hVfcZTU1HVPcp2xuOmVUFKUvHyCpt/n65Y/5XKQYQpTr\n1qa1krCQOnuwSstIpqBCnX+TecP7\n-----END CERTIFICATE-----" 20 | //ConfigJSON has JSON-type config parameters 21 | ConfigJSON = "{\n \"reportingPeriod\": 1000,\n \"periodic\": true,\n \"uponRcvMeasReport\": true,\n \"uponChangeRrcStatus\": true,\n \"A3OffsetRange\": 0,\n \"HysteresisRange\": 0,\n \"CellIndividualOffset\": 0,\n \"FrequencyOffset\": 0,\n \"TimeToTrigger\": 0\n}" 22 | 23 | // Verification time 24 | VerificationTimer = 120 // (Seconds) 25 | 26 | // Serving cell 27 | SCellID = "138426014550001" 28 | 29 | A1Policy = `{ 30 | "scope":{ 31 | "ueId":"" 32 | }, 33 | "tspResources":[ 34 | { 35 | "cellIdList":[ 36 | { 37 | "plmnId":{ 38 | "mcc":"138", 39 | "mnc":"426" 40 | }, 41 | "cId":{ 42 | "ncI":470106432 43 | } 44 | } 45 | ], 46 | "preference":"FORBID" 47 | } 48 | ] 49 | }` 50 | ) 51 | -------------------------------------------------------------------------------- /test/utils/file_io.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package utils 7 | 8 | import "os" 9 | 10 | func WriteFile(path string, content string) error { 11 | f, err := os.Create(path) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | defer f.Close() 17 | 18 | _, err = f.WriteString(content) 19 | 20 | if err != nil { 21 | return err 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /test/utils/rnib.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package utils 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | topoapi "github.com/onosproject/onos-api/go/onos/topo" 12 | toposdk "github.com/onosproject/onos-ric-sdk-go/pkg/topo" 13 | ) 14 | 15 | func getXappFilter() *topoapi.Filters { 16 | controlRelationFilter := &topoapi.Filters{ 17 | KindFilter: &topoapi.Filter{ 18 | Filter: &topoapi.Filter_Equal_{ 19 | Equal_: &topoapi.EqualFilter{ 20 | Value: topoapi.XAPP, 21 | }, 22 | }, 23 | }, 24 | } 25 | return controlRelationFilter 26 | } 27 | 28 | func UpdateXAppA1InterfaceIPAddr(ipAddress string) error { 29 | sdkClient, err := toposdk.NewClient() 30 | if err != nil { 31 | return err 32 | } 33 | objects, err := sdkClient.List(context.Background(), toposdk.WithListFilters(getXappFilter())) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | if len(objects) != 1 { 39 | return fmt.Errorf("number of xApp should be 1, currently %v", len(objects)) 40 | } 41 | 42 | xAppObject := &topoapi.XAppInfo{} 43 | err = objects[0].GetAspect(xAppObject) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | for i := 0; i < len(xAppObject.Interfaces); i++ { 49 | if xAppObject.Interfaces[i].Type == topoapi.Interface_INTERFACE_A1_XAPP { 50 | xAppObject.Interfaces[i].IP = ipAddress 51 | } 52 | } 53 | 54 | err = objects[0].SetAspect(xAppObject) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | err = sdkClient.Update(context.Background(), &objects[0]) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /test/utils/sdran.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package utils 7 | 8 | import ( 9 | "context" 10 | "github.com/onosproject/helmit/pkg/helm" 11 | "github.com/onosproject/helmit/pkg/input" 12 | "github.com/onosproject/helmit/pkg/kubernetes" 13 | "github.com/onosproject/onos-test/pkg/onostest" 14 | ) 15 | 16 | func getCredentials() (string, string, error) { 17 | kubClient, err := kubernetes.New() 18 | if err != nil { 19 | return "", "", err 20 | } 21 | secrets, err := kubClient.CoreV1().Secrets().Get(context.Background(), onostest.SecretsName) 22 | if err != nil { 23 | return "", "", err 24 | } 25 | username := string(secrets.Object.Data["sd-ran-username"]) 26 | password := string(secrets.Object.Data["sd-ran-password"]) 27 | 28 | return username, password, nil 29 | } 30 | 31 | // CreateSdranRelease creates a helm release for an sd-ran instance 32 | func CreateSdranRelease(c *input.Context) (*helm.HelmRelease, error) { 33 | username, password, err := getCredentials() 34 | registry := c.GetArg("registry").String("") 35 | 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | sdran := helm.Chart("sd-ran", onostest.SdranChartRepo). 41 | Release("sd-ran"). 42 | SetUsername(username). 43 | SetPassword(password). 44 | Set("import.onos-config.enabled", false). 45 | Set("import.onos-topo.enabled", true). 46 | Set("onos-e2t.image.tag", "latest"). 47 | Set("global.image.registry", registry). 48 | Set("ran-simulator.image.tag", "latest"). 49 | Set("onos-a1t.image.tag", "latest"). 50 | Set("import.ran-simulator", true). 51 | Set("ran-simulator.pci.modelName", "two-cell-two-node-model") 52 | 53 | return sdran, nil 54 | } 55 | -------------------------------------------------------------------------------- /test/utils/ts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019-present Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2019-present Rimedo Labs 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package utils 7 | 8 | import ( 9 | "context" 10 | "github.com/onosproject/onos-api/go/onos/ransim/types" 11 | "io" 12 | 13 | "github.com/onosproject/onos-api/go/onos/ransim/trafficsim" 14 | "github.com/onosproject/onos-ric-sdk-go/pkg/e2/creds" 15 | "google.golang.org/grpc" 16 | "google.golang.org/grpc/credentials" 17 | ) 18 | 19 | const ( 20 | RanSimulatorAddress = "ran-simulator:5150" 21 | ) 22 | 23 | func GetUEID() ([]*types.Ue, error) { 24 | result := make([]*types.Ue, 0) 25 | tlsConfig, err := creds.GetClientCredentials() 26 | if err != nil { 27 | return []*types.Ue{}, err 28 | } 29 | ctx, cancel := context.WithCancel(context.Background()) 30 | defer cancel() 31 | opts := []grpc.DialOption{ 32 | grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), 33 | } 34 | conn, err := grpc.DialContext(ctx, RanSimulatorAddress, opts...) 35 | if err != nil { 36 | return []*types.Ue{}, err 37 | } 38 | trafficSimClient := trafficsim.NewTrafficClient(conn) 39 | ues, err := trafficSimClient.ListUes(ctx, &trafficsim.ListUesRequest{}) 40 | if err != nil { 41 | return []*types.Ue{}, err 42 | } 43 | 44 | for { 45 | ue, err := ues.Recv() 46 | if err == io.EOF { 47 | break 48 | } else if err != nil { 49 | return []*types.Ue{}, err 50 | } 51 | if ue != nil { 52 | result = append(result, ue.GetUe()) 53 | } 54 | } 55 | 56 | return result, nil 57 | } 58 | --------------------------------------------------------------------------------