mirror of
				https://github.com/Telecominfraproject/wlan-cloud-owprov.git
				synced 2025-10-30 02:02:36 +00:00 
			
		
		
		
	Compare commits
	
		
			77 Commits
		
	
	
		
			v2.6.0-RC2
			...
			v2.7.0-RC1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 3a33815096 | ||
|   | f515bb8e30 | ||
|   | 02fd6d726a | ||
|   | 27ffb31a7c | ||
|   | 5fd9831d6b | ||
|   | fed085cc4a | ||
|   | 121fee841e | ||
|   | 4c6f03ba14 | ||
|   | 0fb9478675 | ||
|   | 97c2af83fd | ||
|   | 599ba0793c | ||
|   | 6df780dba3 | ||
|   | a00287ae85 | ||
|   | c7a300b81e | ||
|   | 5dc507a82e | ||
|   | 8b3e1326b7 | ||
|   | 667f8bc4bd | ||
|   | 0168301d6b | ||
|   | 1a0b00e989 | ||
|   | 7c2229f3d6 | ||
|   | 541df429ec | ||
|   | 6f9fe6cd5d | ||
|   | 2594a2c5f2 | ||
|   | 2e02d96523 | ||
|   | a1375f9468 | ||
|   | 777bc0f2aa | ||
|   | bd0e99309c | ||
|   | 6cb6a60142 | ||
|   | fc7947394d | ||
|   | 1341e15874 | ||
|   | f065815df3 | ||
|   | 03ba51e869 | ||
|   | efd7ef2a9b | ||
|   | 4b90a6e893 | ||
|   | f5cb4a5a87 | ||
|   | e3e4aac202 | ||
|   | 5945d02b3d | ||
|   | 0ac192cdc0 | ||
|   | 1b5eb87eef | ||
|   | 46db18d7cd | ||
|   | 30b8665d7d | ||
|   | c8b3a3b060 | ||
|   | 096da35ff4 | ||
|   | bd7f3af11c | ||
|   | 2a06021c4a | ||
|   | bf18bb25ba | ||
|   | e93f899b76 | ||
|   | eda73038f6 | ||
|   | 953ca155a4 | ||
|   | 898806f232 | ||
|   | 7d97b19b85 | ||
|   | d6c587fde6 | ||
|   | 58c9a7805b | ||
|   | 94dd4c84e9 | ||
|   | 2636715f6f | ||
|   | f9f4624add | ||
|   | cf441de197 | ||
|   | 158455a528 | ||
|   | 4d2ccec1a8 | ||
|   | 7dad5a9bdb | ||
|   | cd2ac84c5b | ||
|   | 9735f709e9 | ||
|   | ae5fd31818 | ||
|   | 2b46ad4a66 | ||
|   | 43d7078cb7 | ||
|   | 18f5d42f00 | ||
|   | 70622b2bb8 | ||
|   | 5b24aea47c | ||
|   | e97617a0db | ||
|   | ba63a7033f | ||
|   | e9db2e1a0d | ||
|   | d85fef7725 | ||
|   | 543c46bf68 | ||
|   | 73eec53fe4 | ||
|   | 8ad2d67c2c | ||
|   | 442f810688 | ||
|   | 2dca5204ea | 
							
								
								
									
										9
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							| @@ -4,6 +4,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - 'release/*' | ||||
|     types: [ closed ] | ||||
|  | ||||
| defaults: | ||||
| @@ -16,4 +17,10 @@ jobs: | ||||
|     steps: | ||||
|       - run: | | ||||
|           export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||
|           curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owprov/$PR_BRANCH_TAG" | ||||
|  | ||||
|           if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then | ||||
|             echo "PR branch is $PR_BRANCH_TAG, deleting Docker image" | ||||
|             curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owprov/$PR_BRANCH_TAG" | ||||
|           else | ||||
|             echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image" | ||||
|           fi | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(owprov VERSION 2.6.0) | ||||
| project(owprov VERSION 2.7.0) | ||||
|  | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
|  | ||||
| @@ -125,7 +125,6 @@ add_executable(owprov | ||||
|         src/RESTAPI/RESTAPI_db_helpers.h | ||||
|         src/JobController.cpp src/JobController.h | ||||
|         src/JobRegistrations.cpp | ||||
|         src/storage/storage_jobs.cpp src/storage/storage_jobs.h | ||||
|         src/storage/storage_maps.cpp src/storage/storage_maps.h | ||||
|         src/RESTAPI/RESTAPI_map_handler.cpp src/RESTAPI/RESTAPI_map_handler.h | ||||
|         src/RESTAPI/RESTAPI_map_list_handler.cpp src/RESTAPI/RESTAPI_map_list_handler.h | ||||
| @@ -135,7 +134,14 @@ add_executable(owprov | ||||
|         src/storage/storage_variables.cpp src/storage/storage_variables.h | ||||
|         src/RESTAPI/RESTAPI_variables_handler.cpp src/RESTAPI/RESTAPI_variables_handler.h | ||||
|         src/RESTAPI/RESTAPI_variables_list_handler.cpp src/RESTAPI/RESTAPI_variables_list_handler.h | ||||
|         src/FileDownloader.cpp src/FileDownloader.h src/Tasks/VenueConfigUpdater.h src/Kafka_ProvUpdater.cpp src/Kafka_ProvUpdater.h src/storage/storage_operataor.cpp src/storage/storage_operataor.h src/storage/storage_sub_devices.cpp src/storage/storage_sub_devices.h src/storage/storage_service_class.cpp src/storage/storage_service_class.h src/RESTAPI/RESTAPI_sub_devices_list_handler.cpp src/RESTAPI/RESTAPI_sub_devices_list_handler.h src/RESTAPI/RESTAPI_sub_devices_handler.cpp src/RESTAPI/RESTAPI_sub_devices_handler.h src/RESTAPI/RESTAPI_service_class_list_handler.cpp src/RESTAPI/RESTAPI_service_class_list_handler.h src/RESTAPI/RESTAPI_service_class_handler.cpp src/RESTAPI/RESTAPI_service_class_handler.h src/RESTAPI/RESTAPI_operators_list_handler.cpp src/RESTAPI/RESTAPI_operators_list_handler.h src/RESTAPI/RESTAPI_operators_handler.cpp src/RESTAPI/RESTAPI_operators_handler.h src/storage/storage_op_contacts.cpp src/storage/storage_op_contacts.h src/storage/storage_op_locations.cpp src/storage/storage_op_locations.h src/RESTAPI/RESTAPI_op_contact_list_handler.cpp src/RESTAPI/RESTAPI_op_contact_list_handler.h src/RESTAPI/RESTAPI_op_contact_handler.cpp src/RESTAPI/RESTAPI_op_contact_handler.h src/RESTAPI/RESTAPI_op_location_list_handler.cpp src/RESTAPI/RESTAPI_op_location_list_handler.h src/RESTAPI/RESTAPI_op_location_handler.cpp src/RESTAPI/RESTAPI_op_location_handler.h src/ProvWebSocketClient.cpp src/ProvWebSocketClient.h src/Tasks/VenueRebooter.h src/Tasks/VenueUpgrade.h src/sdks/SDK_fms.cpp src/sdks/SDK_fms.h) | ||||
|         src/FileDownloader.cpp src/FileDownloader.h | ||||
|         src/Tasks/VenueConfigUpdater.h | ||||
|         src/libs/croncpp.h | ||||
|         src/Kafka_ProvUpdater.cpp src/Kafka_ProvUpdater.h | ||||
|         src/storage/storage_operataor.cpp src/storage/storage_operataor.h | ||||
|         src/storage/storage_sub_devices.cpp src/storage/storage_sub_devices.h | ||||
|         src/storage/storage_service_class.cpp src/storage/storage_service_class.h | ||||
|         src/RESTAPI/RESTAPI_sub_devices_list_handler.cpp src/RESTAPI/RESTAPI_sub_devices_list_handler.h src/RESTAPI/RESTAPI_sub_devices_handler.cpp src/RESTAPI/RESTAPI_sub_devices_handler.h src/RESTAPI/RESTAPI_service_class_list_handler.cpp src/RESTAPI/RESTAPI_service_class_list_handler.h src/RESTAPI/RESTAPI_service_class_handler.cpp src/RESTAPI/RESTAPI_service_class_handler.h src/RESTAPI/RESTAPI_operators_list_handler.cpp src/RESTAPI/RESTAPI_operators_list_handler.h src/RESTAPI/RESTAPI_operators_handler.cpp src/RESTAPI/RESTAPI_operators_handler.h src/storage/storage_op_contacts.cpp src/storage/storage_op_contacts.h src/storage/storage_op_locations.cpp src/storage/storage_op_locations.h src/RESTAPI/RESTAPI_op_contact_list_handler.cpp src/RESTAPI/RESTAPI_op_contact_list_handler.h src/RESTAPI/RESTAPI_op_contact_handler.cpp src/RESTAPI/RESTAPI_op_contact_handler.h src/RESTAPI/RESTAPI_op_location_list_handler.cpp src/RESTAPI/RESTAPI_op_location_list_handler.h src/RESTAPI/RESTAPI_op_location_handler.cpp src/RESTAPI/RESTAPI_op_location_handler.h src/ProvWebSocketClient.cpp src/ProvWebSocketClient.h src/Tasks/VenueRebooter.h src/Tasks/VenueUpgrade.h src/sdks/SDK_fms.cpp src/sdks/SDK_fms.h) | ||||
|  | ||||
| target_link_libraries(owprov PUBLIC | ||||
|         ${Poco_LIBRARIES} | ||||
|   | ||||
							
								
								
									
										54
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,4 +1,10 @@ | ||||
| FROM alpine:3.15 AS build-base | ||||
| ARG ALPINE_VERSION=3.16.2 | ||||
| ARG POCO_VERSION=poco-tip-v1 | ||||
| ARG FMTLIB_VERSION=9.0.0 | ||||
| ARG CPPKAFKA_VERSION=tip-v1 | ||||
| ARG JSON_VALIDATOR_VERSION=2.1.0 | ||||
|  | ||||
| FROM alpine:$ALPINE_VERSION AS build-base | ||||
|  | ||||
| RUN apk add --update --no-cache \ | ||||
|     make cmake g++ git \ | ||||
| @@ -9,8 +15,10 @@ RUN apk add --update --no-cache \ | ||||
|  | ||||
| FROM build-base AS poco-build | ||||
|  | ||||
| ADD https://api.github.com/repos/stephb9959/poco/git/refs/heads/master version.json | ||||
| RUN git clone https://github.com/stephb9959/poco /poco | ||||
| ARG POCO_VERSION | ||||
|  | ||||
| ADD https://api.github.com/repos/AriliaWireless/poco/git/refs/tags/${POCO_VERSION} version.json | ||||
| RUN git clone https://github.com/AriliaWireless/poco --branch ${POCO_VERSION} /poco | ||||
|  | ||||
| WORKDIR /poco | ||||
| RUN mkdir cmake-build | ||||
| @@ -19,10 +27,26 @@ RUN cmake .. | ||||
| RUN cmake --build . --config Release -j8 | ||||
| RUN cmake --build . --target install | ||||
|  | ||||
| FROM build-base AS fmtlib-build | ||||
|  | ||||
| ARG FMTLIB_VERSION | ||||
|  | ||||
| ADD https://api.github.com/repos/fmtlib/fmt/git/refs/tags/${FMTLIB_VERSION} version.json | ||||
| RUN git clone https://github.com/fmtlib/fmt --branch ${FMTLIB_VERSION} /fmtlib | ||||
|  | ||||
| WORKDIR /fmtlib | ||||
| RUN mkdir cmake-build | ||||
| WORKDIR cmake-build | ||||
| RUN cmake .. | ||||
| RUN make | ||||
| RUN make install | ||||
|  | ||||
| FROM build-base AS cppkafka-build | ||||
|  | ||||
| ADD https://api.github.com/repos/stephb9959/cppkafka/git/refs/heads/master version.json | ||||
| RUN git clone https://github.com/stephb9959/cppkafka /cppkafka | ||||
| ARG CPPKAFKA_VERSION | ||||
|  | ||||
| ADD https://api.github.com/repos/AriliaWireless/cppkafka/git/refs/tags/${CPPKAFKA_VERSION} version.json | ||||
| RUN git clone https://github.com/AriliaWireless/cppkafka --branch ${CPPKAFKA_VERSION} /cppkafka | ||||
|  | ||||
| WORKDIR /cppkafka | ||||
| RUN mkdir cmake-build | ||||
| @@ -33,8 +57,10 @@ RUN cmake --build . --target install | ||||
|  | ||||
| FROM build-base AS json-schema-validator-build | ||||
|  | ||||
| ADD https://api.github.com/repos/pboettch/json-schema-validator/git/refs/heads/master version.json | ||||
| RUN git clone https://github.com/pboettch/json-schema-validator /json-schema-validator | ||||
| ARG JSON_VALIDATOR_VERSION | ||||
|  | ||||
| ADD https://api.github.com/repos/pboettch/json-schema-validator/git/refs/tags/${JSON_VALIDATOR_VERSION} version.json | ||||
| RUN git clone https://github.com/pboettch/json-schema-validator --branch ${JSON_VALIDATOR_VERSION} /json-schema-validator | ||||
|  | ||||
| WORKDIR /json-schema-validator | ||||
| RUN mkdir cmake-build | ||||
| @@ -43,18 +69,6 @@ RUN cmake .. | ||||
| RUN make | ||||
| RUN make install | ||||
|  | ||||
| FROM build-base AS fmtlib-build | ||||
|  | ||||
| ADD https://api.github.com/repos/fmtlib/fmt/git/refs/heads/master version.json | ||||
| RUN git clone https://github.com/fmtlib/fmt /fmtlib | ||||
|  | ||||
| WORKDIR /fmtlib | ||||
| RUN mkdir cmake-build | ||||
| WORKDIR cmake-build | ||||
| RUN cmake .. | ||||
| RUN make | ||||
| RUN make install | ||||
|  | ||||
| FROM build-base AS owprov-build | ||||
|  | ||||
| ADD CMakeLists.txt build /owprov/ | ||||
| @@ -77,7 +91,7 @@ WORKDIR /owprov/cmake-build | ||||
| RUN cmake .. | ||||
| RUN cmake --build . --config Release -j8 | ||||
|  | ||||
| FROM alpine:3.15 | ||||
| FROM alpine:$ALPINE_VERSION | ||||
|  | ||||
| ENV OWPROV_USER=owprov \ | ||||
|     OWPROV_ROOT=/owprov-data \ | ||||
|   | ||||
| @@ -5,7 +5,7 @@ if [ "$SELFSIGNED_CERTS" = 'true' ]; then | ||||
|     update-ca-certificates | ||||
| fi | ||||
|  | ||||
| if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWPROV_CONFIG"/owprov.properties ]]; then | ||||
| if [[ "$TEMPLATE_CONFIG" = 'true' ]]; then | ||||
|   RESTAPI_HOST_ROOTCA=${RESTAPI_HOST_ROOTCA:-"\$OWPROV_ROOT/certs/restapi-ca.pem"} \ | ||||
|   RESTAPI_HOST_PORT=${RESTAPI_HOST_PORT:-"16005"} \ | ||||
|   RESTAPI_HOST_CERT=${RESTAPI_HOST_CERT:-"\$OWPROV_ROOT/certs/restapi-cert.pem"} \ | ||||
|   | ||||
							
								
								
									
										2
									
								
								helm/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								helm/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,3 @@ | ||||
| *.swp | ||||
| Chart.lock | ||||
| charts/ | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| {{- $root := . -}} | ||||
| {{- $storageType := index .Values.configProperties "storage.type" -}} | ||||
| --- | ||||
| apiVersion: apps/v1 | ||||
| kind: Deployment | ||||
| @@ -46,6 +47,39 @@ spec: | ||||
|             - -timeout | ||||
|             - 600s | ||||
|  | ||||
| {{- if eq $storageType "postgresql" }} | ||||
|         - name: wait-postgres | ||||
|           image: "{{ .Values.images.owprov.repository }}:{{ .Values.images.owprov.tag }}" | ||||
|           imagePullPolicy: {{ .Values.images.owprov.pullPolicy }} | ||||
|           command: | ||||
|             - /wait-for-postgres.sh | ||||
|             - {{ index .Values.configProperties "storage.type.postgresql.host" }} | ||||
|             - echo | ||||
|             - "PostgreSQL is ready" | ||||
|           env: | ||||
|             - name: KUBERNETES_DEPLOYED | ||||
|               value: "{{ now }}" | ||||
|           {{- range $key, $value := .Values.public_env_variables }} | ||||
|             - name: {{ $key }} | ||||
|               value: {{ $value | quote }} | ||||
|           {{- end }} | ||||
|           {{- range $key, $value := .Values.secret_env_variables }} | ||||
|             - name: {{ $key }} | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: {{ include "owprov.fullname" $root }}-env | ||||
|                   key: {{ $key }} | ||||
|           {{- end }} | ||||
|           volumeMounts: | ||||
|           {{- range .Values.volumes.owprov }} | ||||
|           - name: {{ .name }} | ||||
|             mountPath: {{ .mountPath }} | ||||
|             {{- if .subPath }} | ||||
|             subPath: {{ .subPath }} | ||||
|             {{- end }} | ||||
|           {{- end }} | ||||
| {{- end }} | ||||
|  | ||||
|       containers: | ||||
|  | ||||
|         - name: owprov | ||||
|   | ||||
| @@ -9,7 +9,7 @@ fullnameOverride: "" | ||||
| images: | ||||
|   owprov: | ||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owprov | ||||
|     tag: main | ||||
|     tag: v2.7.0-RC1 | ||||
|     pullPolicy: Always | ||||
| #    regcred: | ||||
| #      registry: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||
|   | ||||
| @@ -27,71 +27,13 @@ components: | ||||
|  | ||||
|   responses: | ||||
|     NotFound: | ||||
|       description: The specified resource was not found. | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             properties: | ||||
|               ErrorCode: | ||||
|                 type: integer | ||||
|               ErrorDetails: | ||||
|                 type: string | ||||
|               ErrorDescription: | ||||
|                 type: string | ||||
|  | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/NotFound' | ||||
|     Unauthorized: | ||||
|       description: The requested does not have sufficient rights to perform the operation. | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             properties: | ||||
|               ErrorCode: | ||||
|                 type: integer | ||||
|                 enum: | ||||
|                   - 0     # Success | ||||
|                   - 1     # PASSWORD_CHANGE_REQUIRED, | ||||
|                   - 2     # INVALID_CREDENTIALS, | ||||
|                   - 3     # PASSWORD_ALREADY_USED, | ||||
|                   - 4     # USERNAME_PENDING_VERIFICATION, | ||||
|                   - 5     # PASSWORD_INVALID, | ||||
|                   - 6     # INTERNAL_ERROR, | ||||
|                   - 7     # ACCESS_DENIED, | ||||
|                   - 8     # INVALID_TOKEN | ||||
|                   - 9     # EXPIRED_TOKEN | ||||
|                   - 10    # RATE_LIMIT_EXCEEDED | ||||
|                   - 11    # BAD_MFA_TRANSACTION | ||||
|                   - 12    # MFA_FAILURE | ||||
|                   - 13    # SECURITY_SERVICE_UNREACHABLE | ||||
|               ErrorDetails: | ||||
|                 type: string | ||||
|               ErrorDescription: | ||||
|                 type: string | ||||
|  | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/Unauthorized' | ||||
|     Success: | ||||
|       description: The requested operation was performed. | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             properties: | ||||
|               Operation: | ||||
|                 type: string | ||||
|               Details: | ||||
|                 type: string | ||||
|               Code: | ||||
|                 type: integer | ||||
|  | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/Success' | ||||
|     BadRequest: | ||||
|       description: The requested operation failed. | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             properties: | ||||
|               ErrorCode: | ||||
|                 type: integer | ||||
|               ErrorDetails: | ||||
|                 type: string | ||||
|               ErrorDescription: | ||||
|                 type: integer | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/BadRequest' | ||||
|  | ||||
|   schemas: | ||||
|  | ||||
| @@ -2016,11 +1958,6 @@ paths: | ||||
|           schema: | ||||
|             type: boolean | ||||
|           required: false | ||||
|         - in: query | ||||
|           name: deviceType | ||||
|           schema: | ||||
|             type: string | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: Pagination start (starts at 1. If not specified, 1 is assumed) | ||||
|           name: offset | ||||
| @@ -2092,6 +2029,21 @@ paths: | ||||
|             type: string | ||||
|             format: uuid | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: return RRM settings for a specific device | ||||
|           name: rrmSettings | ||||
|           schema: | ||||
|             type: boolean | ||||
|             default: false | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: return the resolved configuration for a specific device | ||||
|           name:   resolveConfig | ||||
|           schema: | ||||
|             type: boolean | ||||
|             default: false | ||||
|           required: false | ||||
|  | ||||
|       responses: | ||||
|         200: | ||||
|           description: Return a list of elements | ||||
| @@ -2313,12 +2265,6 @@ paths: | ||||
|             type: string | ||||
|             example: serial1,serial2,serial3 | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: only serial numbers of full device details | ||||
|           name: serialOnly | ||||
|           schema: | ||||
|             type: boolean | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: return the number of devices | ||||
|           name: countOnly | ||||
|   | ||||
							
								
								
									
										174
									
								
								openapi/rrm_provider.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								openapi/rrm_provider.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| openapi: 3.0.1 | ||||
| info: | ||||
|   title: OpenWiFi RRM Provider Model | ||||
|   description: Definitions and APIs to manages an OpenWiFi RRM Providers. | ||||
|   version: 1.0.0 | ||||
|   license: | ||||
|     name: BSD3 | ||||
|     url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||
|  | ||||
| servers: | ||||
|   - url: 'https://localhost:16022/api/v1' | ||||
|  | ||||
| security: | ||||
|   - bearerAuth: [] | ||||
|   - ApiKeyAuth: [] | ||||
|  | ||||
| components: | ||||
|   securitySchemes: | ||||
|     ApiKeyAuth: | ||||
|       type: apiKey | ||||
|       in: header | ||||
|       name: X-API-KEY | ||||
|     bearerAuth: | ||||
|       type: http | ||||
|       scheme: bearer | ||||
|       bearerFormat: JWT | ||||
|  | ||||
|   responses: | ||||
|     NotFound: | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/NotFound' | ||||
|     Unauthorized: | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/Unauthorized' | ||||
|     Success: | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/Success' | ||||
|     BadRequest: | ||||
|       $ref: 'https://github.com/Telecominfraproject/wlan-cloud-ucentralsec/blob/main/openpapi/owsec.yaml#/components/responses/BadRequest' | ||||
|  | ||||
|   schemas: | ||||
|  | ||||
|     Provider: | ||||
|       type: object | ||||
|       properties: | ||||
|         vendor: | ||||
|           description: The name of the vendor for display. | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           maxLength: 128 | ||||
|         vendorShortname: | ||||
|           description: A shortname for the vendor. Only letters and numbers are allowed. This is the name used internally. | ||||
|           type: string | ||||
|           minLength: 4 | ||||
|           maxLength: 16 | ||||
|         version: | ||||
|           description: An identifier that will help users identify the version of the RRM module they are using. | ||||
|           type: string | ||||
|         about: | ||||
|           description: A link to the Vendor page for this RRM Module | ||||
|           type: string | ||||
|  | ||||
|  | ||||
|     Algorithm: | ||||
|       type: object | ||||
|       properties: | ||||
|         name: | ||||
|           description: A display for this algorithm. | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           maxLength: 128 | ||||
|         description: | ||||
|           description: A description of the algorithm. | ||||
|           type: string | ||||
|         shortName: | ||||
|           description: This is the name used internally. | ||||
|           type: string | ||||
|           minLength: 4 | ||||
|           maxLength: 16 | ||||
|         parameterFormat: | ||||
|           description: this is a Regex used to validate the input. If this is empty, no validation will be performed. | ||||
|           type: string | ||||
|         parameterSamples: | ||||
|           description: These samples will be displayed in the UI to the user trying to configure the options | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         helper: | ||||
|           description: A link to a web page or PDF document explaining the algorithm and its parameters | ||||
|           type: string | ||||
|  | ||||
|     Algorithms: | ||||
|       description: The list of all algorithms supported by the vendor | ||||
|       type: array | ||||
|       items: | ||||
|         $ref: '#/components/schemas/Algorithm' | ||||
|  | ||||
| paths: | ||||
|   /provider: | ||||
|     get: | ||||
|       tags: | ||||
|         - RRM | ||||
|       operationId: getProvider | ||||
|       summary: Retrieve information about the provider for this RRM Module | ||||
|       responses: | ||||
|         200: | ||||
|           $ref: '#/components/schemas/Provider' | ||||
|         400: | ||||
|           $ref: '#/components/responses/BadRequest' | ||||
|         403: | ||||
|           $ref: '#/components/responses/Unauthorized' | ||||
|         404: | ||||
|           $ref: '#/components/responses/NotFound' | ||||
|  | ||||
|   /algorithms: | ||||
|     get: | ||||
|       tags: | ||||
|         - RRM | ||||
|       operationId: getAlgorithms | ||||
|       summary: Retrieve a lists of algorithms supported in the module. | ||||
|       responses: | ||||
|         200: | ||||
|           $ref: '#/components/schemas/Algorithms' | ||||
|         400: | ||||
|           $ref: '#/components/responses/BadRequest' | ||||
|         403: | ||||
|           $ref: '#/components/responses/Unauthorized' | ||||
|         404: | ||||
|           $ref: '#/components/responses/NotFound' | ||||
|  | ||||
|   /runRRM: | ||||
|     put: | ||||
|       tags: | ||||
|         - RRM | ||||
|       operationId: runRRMNow | ||||
|       summary: Run a specific or default RRM algorithm. The UI user or CLI user will have the ability to run an algorithm on demand. | ||||
|       parameters: | ||||
|         - in: query | ||||
|           description: | ||||
|           name: venue | ||||
|           schema: | ||||
|             type: string | ||||
|             format: uuid | ||||
|           required: true | ||||
|         - in: query | ||||
|           description: Perform RRM without updating anything. This may be used by an admin to see what RRM would do. | ||||
|           name: mock | ||||
|           schema: | ||||
|             type: boolean | ||||
|             default: false | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: Specify the RRM algorithm to use. If omitted, select the default algorithm. | ||||
|           schema: | ||||
|             type: string | ||||
|           required: false | ||||
|         - in: query | ||||
|           description: Specify the parameters to use with the RRM algorithm to use. If omitted, select the default parameters. | ||||
|           schema: | ||||
|             type: string | ||||
|           required: false | ||||
|       responses: | ||||
|         200: | ||||
|           description: Return the list of actions that were or would be performed. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   type: string | ||||
|         400: | ||||
|           $ref: '#/components/responses/BadRequest' | ||||
|         403: | ||||
|           $ref: '#/components/responses/Unauthorized' | ||||
|         404: | ||||
|           $ref: '#/components/responses/NotFound' | ||||
|  | ||||
| @@ -153,60 +153,62 @@ namespace OpenWifi { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         std::set<std::string>   Sections; | ||||
|         for(const auto &i:Config_) { | ||||
|             Poco::JSON::Parser  P; | ||||
|             auto O = P.parse(i.element.configuration).extract<Poco::JSON::Object::Ptr>(); | ||||
|             auto Names = O->getNames(); | ||||
|             for(const auto &SectionName:Names) { | ||||
|                 auto InsertInfo = Sections.insert(SectionName); | ||||
|                 if (InsertInfo.second) { | ||||
|                     if (O->isArray(SectionName)) { | ||||
|                         auto OriginalArray = O->getArray(SectionName); | ||||
|                         if (Explain_) { | ||||
|                             Poco::JSON::Object ExObj; | ||||
|                             ExObj.set("from-uuid", i.info.id); | ||||
|                             ExObj.set("from-name", i.info.name); | ||||
|                             ExObj.set("action", "added"); | ||||
|                             ExObj.set("element", OriginalArray); | ||||
|                             Explanation_.add(ExObj); | ||||
|         try { | ||||
|             std::set<std::string> Sections; | ||||
|             for (const auto &i: Config_) { | ||||
|                 Poco::JSON::Parser P; | ||||
|                 auto O = P.parse(i.element.configuration).extract<Poco::JSON::Object::Ptr>(); | ||||
|                 auto Names = O->getNames(); | ||||
|                 for (const auto &SectionName: Names) { | ||||
|                     auto InsertInfo = Sections.insert(SectionName); | ||||
|                     if (InsertInfo.second) { | ||||
|                         if (O->isArray(SectionName)) { | ||||
|                             auto OriginalArray = O->getArray(SectionName); | ||||
|                             if (Explain_) { | ||||
|                                 Poco::JSON::Object ExObj; | ||||
|                                 ExObj.set("from-uuid", i.info.id); | ||||
|                                 ExObj.set("from-name", i.info.name); | ||||
|                                 ExObj.set("action", "added"); | ||||
|                                 ExObj.set("element", OriginalArray); | ||||
|                                 Explanation_.add(ExObj); | ||||
|                             } | ||||
|                             auto ExpandedArray = Poco::makeShared<Poco::JSON::Array>(); | ||||
|                             ReplaceVariablesInArray(OriginalArray, ExpandedArray); | ||||
|                             Configuration->set(SectionName, ExpandedArray); | ||||
|                         } else if (O->isObject(SectionName)) { | ||||
|                             auto OriginalSection = O->get(SectionName).extract<Poco::JSON::Object::Ptr>(); | ||||
|                             if (Explain_) { | ||||
|                                 Poco::JSON::Object ExObj; | ||||
|                                 ExObj.set("from-uuid", i.info.id); | ||||
|                                 ExObj.set("from-name", i.info.name); | ||||
|                                 ExObj.set("action", "added"); | ||||
|                                 ExObj.set("element", OriginalSection); | ||||
|                                 Explanation_.add(ExObj); | ||||
|                             } | ||||
|                             auto ExpandedSection = Poco::makeShared<Poco::JSON::Object>(); | ||||
|                             ReplaceVariablesInObject(OriginalSection, ExpandedSection); | ||||
|                             Configuration->set(SectionName, ExpandedSection); | ||||
|                         } else { | ||||
|                             std::cout << " --- unknown element type --- " << O->get(SectionName).toString() | ||||
|                                       << std::endl; | ||||
|                         } | ||||
|                         auto ExpandedArray = Poco::makeShared<Poco::JSON::Array>(); | ||||
|                         ReplaceVariablesInArray(OriginalArray, ExpandedArray); | ||||
|                         Configuration->set(SectionName, ExpandedArray); | ||||
|                     } else if (O->isObject(SectionName)) { | ||||
|                         auto OriginalSection = O->get(SectionName).extract<Poco::JSON::Object::Ptr>(); | ||||
|                         if (Explain_) { | ||||
|                             Poco::JSON::Object ExObj; | ||||
|                             ExObj.set("from-uuid", i.info.id); | ||||
|                             ExObj.set("from-name", i.info.name); | ||||
|                             ExObj.set("action", "added"); | ||||
|                             ExObj.set("element", OriginalSection); | ||||
|                             Explanation_.add(ExObj); | ||||
|                         } | ||||
|                         auto ExpandedSection = Poco::makeShared<Poco::JSON::Object>(); | ||||
|                         ReplaceVariablesInObject(OriginalSection, ExpandedSection); | ||||
|                         Configuration->set(SectionName, ExpandedSection); | ||||
|                     } else { | ||||
|                         std::cout << " --- unknown element type --- " << O->get(SectionName).toString() << std::endl; | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (Explain_) { | ||||
|                         Poco::JSON::Object ExObj; | ||||
|                         ExObj.set("from-uuid", i.info.id); | ||||
|                         ExObj.set("from-name", i.info.name); | ||||
|                         ExObj.set("action", "ignored"); | ||||
|                         ExObj.set("reason", "weight insufficient"); | ||||
|                         ExObj.set("element", O->get(SectionName)); | ||||
|                         Explanation_.add(ExObj); | ||||
|                         if (Explain_) { | ||||
|                             Poco::JSON::Object ExObj; | ||||
|                             ExObj.set("from-uuid", i.info.id); | ||||
|                             ExObj.set("from-name", i.info.name); | ||||
|                             ExObj.set("action", "ignored"); | ||||
|                             ExObj.set("reason", "weight insufficient"); | ||||
|                             ExObj.set("element", O->get(SectionName)); | ||||
|                             Explanation_.add(ExObj); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if(Config_.empty()) | ||||
|             return false; | ||||
|         } catch (...) { | ||||
|  | ||||
|         return true; | ||||
|         } | ||||
|         return !Config_.empty(); | ||||
|     } | ||||
|  | ||||
|     static bool DeviceTypeMatch(const std::string &DeviceType, const Types::StringVec & Types) { | ||||
|   | ||||
| @@ -26,6 +26,7 @@ namespace OpenWifi { | ||||
|  | ||||
|     void AutoDiscovery::run() { | ||||
|         Poco::AutoPtr<Poco::Notification>	Note(Queue_.waitDequeueNotification()); | ||||
|         Utils::SetThreadName("auto-discovery"); | ||||
|         while(Note && Running_) { | ||||
|             auto Msg = dynamic_cast<DiscoveryMessage *>(Note.get()); | ||||
|             if(Msg!= nullptr) { | ||||
|   | ||||
| @@ -33,7 +33,7 @@ namespace OpenWifi { | ||||
|             void Stop() override; | ||||
|             void ConnectionReceived( const std::string & Key, const std::string & Payload) { | ||||
|                 std::lock_guard G(Mutex_); | ||||
|                 Logger().information(Poco::format("Device(%s): Connection/Ping message.", Key)); | ||||
|                 poco_debug(Logger(),Poco::format("Device(%s): Connection/Ping message.", Key)); | ||||
|                 Queue_.enqueueNotification( new DiscoveryMessage(Key,Payload)); | ||||
|             } | ||||
|             void run() override; | ||||
|   | ||||
| @@ -26,6 +26,8 @@ namespace OpenWifi { | ||||
|                 {"https://ucentral.io/ucentral.schema.pretty.json", "ucentral.schema.pretty.json" } | ||||
|             }; | ||||
|  | ||||
|         Utils::SetThreadName("file-dmnldr"); | ||||
|  | ||||
|         for(const auto &[url,filename]:Files) { | ||||
|             try { | ||||
|                 std::string FileContent; | ||||
|   | ||||
| @@ -27,11 +27,32 @@ namespace OpenWifi { | ||||
|  | ||||
|     void JobController::run() { | ||||
|         Running_ = true ; | ||||
|  | ||||
|         Utils::SetThreadName("job-controller"); | ||||
|         while(Running_) { | ||||
|             Poco::Thread::trySleep(2000); | ||||
|  | ||||
|             std::lock_guard G(Mutex_); | ||||
|  | ||||
|             for(auto ¤t_job:jobs_) { | ||||
|                 if(current_job!=nullptr) { | ||||
|                     if(current_job->Started()==0 && Pool_.used()<Pool_.available()) { | ||||
|                         current_job->Logger().information(fmt::format("Starting {}: {}",current_job->JobId(),current_job->Name())); | ||||
|                         current_job->Start(); | ||||
|                         Pool_.start(*current_job); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             for(auto it = jobs_.begin(); it!=jobs_.end();) {\ | ||||
|                 auto current_job = *it; | ||||
|                 if(current_job!=nullptr && current_job->Completed()!=0) { | ||||
|                     current_job->Logger().information(fmt::format("Completed {}: {}",current_job->JobId(),current_job->Name())); | ||||
|                     it = jobs_.erase(it); | ||||
|                     delete current_job; | ||||
|                 } else { | ||||
|                     ++it; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -7,99 +7,45 @@ | ||||
| #include <vector> | ||||
| #include <utility> | ||||
| #include <functional> | ||||
| #include <list> | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     class Job { | ||||
|         public: | ||||
|             struct Parameter { | ||||
|                 std::string name; | ||||
|                 std::string value; | ||||
|                 inline void to_json(Poco::JSON::Object &Obj) const { | ||||
|                     RESTAPI_utils::field_to_json(Obj,"name",name); | ||||
|                     RESTAPI_utils::field_to_json(Obj,"value",value); | ||||
|                 } | ||||
|     class Job : public Poco::Runnable { | ||||
|     public: | ||||
|         Job(const std::string &JobID, const std::string &name, const std::vector<std::string> & parameters, uint64_t when, const SecurityObjects::UserInfo &UI, Poco::Logger &L) : | ||||
|             jobId_(JobID), | ||||
|             name_(name), | ||||
|             parameters_(parameters), | ||||
|             when_(when), | ||||
|             userinfo_(UI), | ||||
|             Logger_(L) | ||||
|         {}; | ||||
|  | ||||
|                 inline bool from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|                     try { | ||||
|                         RESTAPI_utils::field_from_json(Obj,"name",name); | ||||
|                         RESTAPI_utils::field_from_json(Obj,"value",value); | ||||
|                         return true; | ||||
|                     } catch (...) { | ||||
|         virtual void run() = 0; | ||||
|         [[nodiscard]] std::string Name() const { return name_; } | ||||
|         const SecurityObjects::UserInfo & UserInfo() const { return userinfo_; } | ||||
|         Poco::Logger & Logger() { return Logger_; } | ||||
|         const std::string & JobId() const { return jobId_; } | ||||
|         const std::string & Parameter(int x) const { return parameters_[x];} | ||||
|         uint64_t When() const { return when_; } | ||||
|         void Start() { started_ = OpenWifi::Now(); } | ||||
|         uint64_t Started() const { return started_; } | ||||
|         uint64_t Completed() const { return completed_;} | ||||
|         void Complete() { completed_ = OpenWifi::Now(); } | ||||
|  | ||||
|                     } | ||||
|                     return false; | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             struct Status { | ||||
|                 Types::UUID_t   UUID; | ||||
|                 uint64_t        Start = 0 ; | ||||
|                 uint64_t        Progress = 0 ; | ||||
|                 uint64_t        Completed = 0 ; | ||||
|                 std::string     CurrentDisplay; | ||||
|             }; | ||||
|  | ||||
|             struct Result { | ||||
|                 int         Error=0; | ||||
|                 std::string Reason; | ||||
|             }; | ||||
|  | ||||
|             typedef std::vector<Parameter>       Parameters; | ||||
|             typedef std::vector<Parameters>      ParametersVec; | ||||
|             typedef std::function<bool(const Parameters &Parameters, Result &Result, bool &Retry)>  WorkerFunction; | ||||
|             typedef std::vector<Status>          Statuses; | ||||
|  | ||||
|             Job(std::string Title, | ||||
|                          std::string Description, | ||||
|                          std::string RegisteredName, | ||||
|                          ParametersVec Parameters, | ||||
|                          [[maybe_unused]] bool Parallel=true) : | ||||
|                     Title_(std::move(Title)), | ||||
|                     Description_(std::move(Description)), | ||||
|                     RegisteredName_(std::move(RegisteredName)), | ||||
|                     Parameters_(std::move(Parameters)) | ||||
|                 { | ||||
|                     UUID_ = MicroService::instance().CreateUUID(); | ||||
|                 } | ||||
|  | ||||
|             [[nodiscard]] inline const Types::UUID_t & ID() const { return UUID_; } | ||||
|  | ||||
|         private: | ||||
|             Types::UUID_t       UUID_; | ||||
|             std::string         Title_; | ||||
|             std::string         Description_; | ||||
|             std::string         RegisteredName_; | ||||
|             ParametersVec       Parameters_; | ||||
|     private: | ||||
|         std::string                 jobId_; | ||||
|         std::string                 name_; | ||||
|         std::vector<std::string>    parameters_; | ||||
|         uint64_t                    when_=0; | ||||
|         SecurityObjects::UserInfo   userinfo_; | ||||
|         Poco::Logger                & Logger_; | ||||
|         uint64_t                    started_=0; | ||||
|         uint64_t                    completed_=0; | ||||
|     }; | ||||
|  | ||||
|     class JobRegistry { | ||||
|         public: | ||||
|             static auto instance() { | ||||
|                 static auto instance_ = new JobRegistry; | ||||
|                 return instance_; | ||||
|             } | ||||
|  | ||||
|             inline void RegisterJobType( const std::string & JobType, Job::WorkerFunction Function) { | ||||
|                     JobTypes_[JobType] = std::move(Function); | ||||
|             } | ||||
|  | ||||
|             inline bool Execute(const std::string &JobType, const Job::Parameters & Params, Job::Result &Result, bool & Retry) { | ||||
|                 auto Hint = JobTypes_.find(JobType); | ||||
|                 if(Hint != end(JobTypes_)) { | ||||
|                     Hint->second(Params, Result, Retry); | ||||
|                     return true; | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|         private: | ||||
|             std::map<std::string,Job::WorkerFunction>  JobTypes_; | ||||
|     }; | ||||
|  | ||||
|     inline auto JobRegistry() { return JobRegistry::instance(); } | ||||
|  | ||||
|     class JobController : public SubSystemServer, Poco::Runnable { | ||||
|         public: | ||||
|             static auto instance() { | ||||
| @@ -112,11 +58,16 @@ namespace OpenWifi { | ||||
|             void run() override; | ||||
|             inline void wakeup() { Thr_.wakeUp(); } | ||||
|  | ||||
|             bool JobList(Job::Statuses & Statuses); | ||||
|             void AddJob( Job* newJob ) { | ||||
|                 std::lock_guard G(Mutex_); | ||||
|                 jobs_.push_back(newJob); | ||||
|             } | ||||
|  | ||||
|         private: | ||||
|             Poco::Thread            Thr_; | ||||
|             std::atomic_bool        Running_=false; | ||||
|             Poco::Thread                        Thr_; | ||||
|             std::atomic_bool                    Running_=false; | ||||
|             std::list<Job *>                    jobs_; | ||||
|             Poco::ThreadPool                    Pool_; | ||||
|  | ||||
|         JobController() noexcept: | ||||
|             SubSystemServer("JobController", "JOB-SVR", "job") | ||||
|   | ||||
| @@ -23,6 +23,7 @@ namespace OpenWifi { | ||||
|                                                               bool &Done, std::string &Answer) { | ||||
|         Done = false; | ||||
|         auto Prefix = O->get("serial_prefix").toString(); | ||||
|         Poco::toLowerInPlace(Prefix); | ||||
|         Logger().information(Poco::format("serial_number_search: %s", Prefix)); | ||||
|         if (!Prefix.empty() && Prefix.length() < 13) { | ||||
|             std::vector<uint64_t> Numbers; | ||||
| @@ -83,6 +84,7 @@ namespace OpenWifi { | ||||
|         Done = false; | ||||
|         auto operatorId = O->get("operatorId").toString(); | ||||
|         auto Prefix = O->get("serial_prefix").toString(); | ||||
|         Poco::toLowerInPlace(Prefix); | ||||
|         std::string Query; | ||||
|  | ||||
|         if(Prefix[0]=='*') { | ||||
|   | ||||
| @@ -9,6 +9,9 @@ | ||||
| #include "framework/MicroService.h" | ||||
| #include "framework/ConfigurationValidator.h" | ||||
| #include "sdks/SDK_sec.h" | ||||
| #include "Poco/StringTokenizer.h" | ||||
|  | ||||
| #include "libs/croncpp.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
| @@ -405,7 +408,9 @@ namespace OpenWifi { | ||||
|     } | ||||
|  | ||||
|     inline bool ValidateConfigBlock(const ProvObjects::DeviceConfiguration &Config, RESTAPI::Errors::msg & Error) { | ||||
|         static const std::vector<std::string> SectionNames{ "globals", "interfaces", "metrics", "radios", "services", "unit" }; | ||||
|         static const std::vector<std::string> SectionNames{ "globals", "interfaces", "metrics", "radios", "services", | ||||
|                                                             "unit", "definitions", "ethernet", "switch", "config-raw", | ||||
|                                                             "third-party" }; | ||||
|  | ||||
|         for(const auto &i:Config.configuration) { | ||||
|             Poco::JSON::Parser  P; | ||||
| @@ -521,12 +526,39 @@ namespace OpenWifi { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return Result; | ||||
|     } | ||||
|  | ||||
|     inline bool ValidSchedule(const std::string &v) { | ||||
|         try | ||||
|         { | ||||
|             auto cron = cron::make_cron(v); | ||||
|             return true; | ||||
|         } | ||||
|         catch (cron::bad_cronexpr const & ex) | ||||
|         { | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     inline bool ValidRRM(const std::string &v) { | ||||
|         if((v=="no") || (v=="inherit")) return true; | ||||
|         try { | ||||
|             Poco::JSON::Parser  P; | ||||
|             auto O = P.parse(v).extract<Poco::JSON::Object::Ptr>(); | ||||
|  | ||||
|             ProvObjects::RRMDetails D; | ||||
|             if(D.from_json(O)) { | ||||
|                 return ValidSchedule(D.schedule); | ||||
|             } | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     inline bool ValidDeviceRules(const ProvObjects::DeviceRules & DR) { | ||||
|         return  (DR.rrm=="yes" || DR.rrm=="no" || DR.rrm=="inherit") && | ||||
|         return  (ValidRRM(DR.rrm)) && | ||||
|                 (DR.firmwareUpgrade=="yes" || DR.firmwareUpgrade=="no" || DR.firmwareUpgrade=="inherit") && | ||||
|                 (DR.rcOnly=="yes" || DR.rcOnly=="no" || DR.rcOnly=="inherit"); | ||||
|     } | ||||
|   | ||||
| @@ -35,36 +35,53 @@ namespace OpenWifi{ | ||||
|  | ||||
|     void RESTAPI_inventory_handler::DoGet() { | ||||
|  | ||||
|         ProvObjects::InventoryTag   Existing; | ||||
|         std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER,""); | ||||
|         Logger().debug(Poco::format("%s: Retrieving inventory information.",SerialNumber)); | ||||
|         if(SerialNumber.empty() || !DB_.GetRecord(RESTAPI::Protocol::SERIALNUMBER,SerialNumber,Existing)) { | ||||
|         ProvObjects::InventoryTag Existing; | ||||
|         std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER, ""); | ||||
|         Logger().debug(Poco::format("%s: Retrieving inventory information.", SerialNumber)); | ||||
|         if (SerialNumber.empty() || !DB_.GetRecord(RESTAPI::Protocol::SERIALNUMBER, SerialNumber, Existing)) { | ||||
|             return NotFound(); | ||||
|         } | ||||
|         Logger().debug(Poco::format("%s,%s: Retrieving inventory information.", Existing.serialNumber, Existing.info.id )); | ||||
|         Logger().debug( | ||||
|                 Poco::format("%s,%s: Retrieving inventory information.", Existing.serialNumber, Existing.info.id)); | ||||
|  | ||||
|         Poco::JSON::Object  Answer; | ||||
|         Poco::JSON::Object Answer; | ||||
|         std::string Arg; | ||||
|         if(HasParameter("config",Arg) && Arg=="true") { | ||||
|             bool Explain = (HasParameter("explain",Arg) && Arg == "true"); | ||||
|             APConfig    Device(SerialNumber,Existing.deviceType,Logger(), Explain); | ||||
|         if (HasParameter("config", Arg) && Arg == "true") { | ||||
|             bool Explain = (HasParameter("explain", Arg) && Arg == "true"); | ||||
|             APConfig Device(SerialNumber, Existing.deviceType, Logger(), Explain); | ||||
|  | ||||
|             auto Configuration = Poco::makeShared<Poco::JSON::Object>(); | ||||
|             if(Device.Get(Configuration)) { | ||||
|             if (Device.Get(Configuration)) { | ||||
|                 Answer.set("config", Configuration); | ||||
|                 if(Explain) | ||||
|                 if (Explain) | ||||
|                     Answer.set("explanation", Device.Explanation()); | ||||
|             } else { | ||||
|                 Answer.set("config","none"); | ||||
|                 Answer.set("config", "none"); | ||||
|             } | ||||
|             return ReturnObject(Answer); | ||||
|         } else if(HasParameter("firmwareOptions", Arg) && Arg=="true") { | ||||
|         } else if (GetBoolParameter("firmwareOptions", false)) { | ||||
|             ProvObjects::DeviceRules Rules; | ||||
|             StorageService()->InventoryDB().EvaluateDeviceSerialNumberRules(SerialNumber,Rules); | ||||
|             Answer.set("firmwareUpgrade",Rules.firmwareUpgrade); | ||||
|             Answer.set("firmwareRCOnly", Rules.rcOnly == "yes" ); | ||||
|             StorageService()->InventoryDB().EvaluateDeviceSerialNumberRules(SerialNumber, Rules); | ||||
|             Answer.set("firmwareUpgrade", Rules.firmwareUpgrade); | ||||
|             Answer.set("firmwareRCOnly", Rules.rcOnly == "yes"); | ||||
|             return ReturnObject(Answer); | ||||
|         } else if(HasParameter("applyConfiguration",Arg) && Arg=="true") { | ||||
|         } else if(GetBoolParameter("rrmSettings",false)) { | ||||
|             ProvObjects::DeviceRules Rules; | ||||
|             StorageService()->InventoryDB().EvaluateDeviceSerialNumberRules(SerialNumber, Rules); | ||||
|             if(Rules.rrm=="no" || Rules.rrm=="inherit") { | ||||
|                 Answer.set("rrm", Rules.rrm); | ||||
|             } else { | ||||
|                 ProvObjects::RRMDetails D; | ||||
|                 Poco::JSON::Parser  P; | ||||
|                 try { | ||||
|                     auto Obj = P.parse(Rules.rrm).extract<Poco::JSON::Object::Ptr>(); | ||||
|                     Answer.set("rrm", Obj); | ||||
|                 } catch (...) { | ||||
|                     Answer.set("rrm", "invalid"); | ||||
|                 } | ||||
|             } | ||||
|             return ReturnObject(Answer); | ||||
|         } else if(GetBoolParameter("applyConfiguration", false)) { | ||||
|             Logger().debug(Poco::format("%s: Retrieving configuration.",Existing.serialNumber)); | ||||
|             auto Device = std::make_shared<APConfig>(SerialNumber, Existing.deviceType, Logger(), false); | ||||
|             auto Configuration = Poco::makeShared<Poco::JSON::Object>(); | ||||
| @@ -91,6 +108,19 @@ namespace OpenWifi{ | ||||
|             } | ||||
|             Results.to_json(Answer); | ||||
|             return ReturnObject(Answer); | ||||
|         } else if(GetBoolParameter("resolveConfig", false)) { | ||||
|             Logger().debug(Poco::format("%s: Retrieving configuration.",Existing.serialNumber)); | ||||
|             auto Device = std::make_shared<APConfig>(SerialNumber, Existing.deviceType, Logger(), false); | ||||
|             auto Configuration = Poco::makeShared<Poco::JSON::Object>(); | ||||
|             Poco::JSON::Object ErrorsObj, WarningsObj; | ||||
|             ProvObjects::InventoryConfigApplyResult Results; | ||||
|             Logger().debug(Poco::format("%s: Computing configuration.",Existing.serialNumber)); | ||||
|             if (Device->Get(Configuration)) { | ||||
|                 Answer.set("configuration", Configuration); | ||||
|             } else { | ||||
|                 Answer.set("error", 1); | ||||
|             } | ||||
|             return ReturnObject(Answer); | ||||
|         }   else if(QB_.AdditionalInfo) { | ||||
|             AddExtendedInfo(Existing,Answer); | ||||
|         } | ||||
| @@ -136,6 +166,7 @@ namespace OpenWifi{ | ||||
|  | ||||
|     void RESTAPI_inventory_handler::DoPost() { | ||||
|         std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER,""); | ||||
|         Poco::toLowerInPlace(SerialNumber); | ||||
|         if(SerialNumber.empty()) { | ||||
|             return BadRequest(RESTAPI::Errors::MissingSerialNumber); | ||||
|         } | ||||
|   | ||||
| @@ -101,7 +101,7 @@ namespace OpenWifi { | ||||
|                 { "email", UserName }, | ||||
|                 { "signupUUID" , SignupUUID }, | ||||
|                 { "owner" , SignupOperator.info.id }, | ||||
|  | ||||
|                 { "operatorName", SignupOperator.registrationId } | ||||
|         }, Body, 30000); | ||||
|  | ||||
|         Poco::JSON::Object::Ptr Answer; | ||||
|   | ||||
| @@ -27,6 +27,7 @@ namespace OpenWifi{ | ||||
|             for (const auto &i: V.children) { | ||||
|                 ProvObjects::Venue V2; | ||||
|                 if (StorageService()->VenueDB().GetRecord("id", i, V2)) { | ||||
|                     std::copy(V2.devices.begin(),V2.devices.end(),std::back_inserter(R)); | ||||
|                     auto LowerDevs = GetDevices(V2, GetChildren); | ||||
|                     std::copy(LowerDevs.begin(), LowerDevs.end(), std::back_inserter(R)); | ||||
|                 } | ||||
| @@ -227,10 +228,10 @@ namespace OpenWifi{ | ||||
|  | ||||
|             Poco::JSON::Object  Answer; | ||||
|             SNL.serialNumbers = Existing.devices; | ||||
|  | ||||
|             auto Task = new VenueConfigUpdater(UUID,UserInfo_.userinfo,0,Logger()); | ||||
|             auto JobId = Task->Start(); | ||||
|  | ||||
|             auto JobId = MicroService::instance().CreateUUID(); | ||||
|             Types::StringVec Parameters{UUID};; | ||||
|             auto NewJob = new VenueConfigUpdater(JobId,"VenueConfigurationUpdater", Parameters, 0, UserInfo_.userinfo, Logger()); | ||||
|             JobController()->AddJob(dynamic_cast<Job*>(NewJob)); | ||||
|             SNL.to_json(Answer); | ||||
|             Answer.set("jobId",JobId); | ||||
|             return ReturnObject(Answer); | ||||
| @@ -241,10 +242,10 @@ namespace OpenWifi{ | ||||
|  | ||||
|             Poco::JSON::Object  Answer; | ||||
|             SNL.serialNumbers = Existing.devices; | ||||
|  | ||||
|             auto Task = new VenueUpgrade(UUID,UserInfo_.userinfo,0,Logger()); | ||||
|             auto JobId = Task->Start(); | ||||
|  | ||||
|             auto JobId = MicroService::instance().CreateUUID(); | ||||
|             Types::StringVec Parameters{UUID};; | ||||
|             auto NewJob = new VenueUpgrade(JobId,"VenueFirmwareUpgrade", Parameters, 0, UserInfo_.userinfo, Logger()); | ||||
|             JobController()->AddJob(dynamic_cast<Job*>(NewJob)); | ||||
|             SNL.to_json(Answer); | ||||
|             Answer.set("jobId",JobId); | ||||
|             return ReturnObject(Answer); | ||||
| @@ -255,10 +256,10 @@ namespace OpenWifi{ | ||||
|  | ||||
|             Poco::JSON::Object  Answer; | ||||
|             SNL.serialNumbers = Existing.devices; | ||||
|  | ||||
|             auto Task = new VenueRebooter(UUID,UserInfo_.userinfo,0,Logger()); | ||||
|             auto JobId = Task->Start(); | ||||
|  | ||||
|             auto JobId = MicroService::instance().CreateUUID(); | ||||
|             Types::StringVec Parameters{UUID};; | ||||
|             auto NewJob = new VenueRebooter(JobId,"VenueRebooter", Parameters, 0, UserInfo_.userinfo, Logger()); | ||||
|             JobController()->AddJob(dynamic_cast<Job*>(NewJob)); | ||||
|             SNL.to_json(Answer); | ||||
|             Answer.set("jobId",JobId); | ||||
|             return ReturnObject(Answer); | ||||
|   | ||||
| @@ -1159,5 +1159,40 @@ namespace OpenWifi::ProvObjects { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void RRMAlgorithmDetails::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"name",name); | ||||
|         field_to_json(Obj,"parameters",parameters); | ||||
|     } | ||||
|  | ||||
|     bool RRMAlgorithmDetails::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj,"name",name); | ||||
|             field_from_json(Obj,"parameters",parameters); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void RRMDetails::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"vendor",vendor); | ||||
|         field_to_json(Obj,"schedule",schedule); | ||||
|         field_to_json(Obj,"algorithms",algorithms); | ||||
|     } | ||||
|  | ||||
|     bool RRMDetails::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj,"vendor",vendor); | ||||
|             field_from_json(Obj,"schedule",schedule); | ||||
|             field_from_json(Obj,"algorithms",algorithms); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -62,6 +62,21 @@ namespace OpenWifi::ProvObjects { | ||||
|     }; | ||||
|     typedef std::vector<ManagementPolicy>      ManagementPolicyVec; | ||||
|  | ||||
|     struct RRMAlgorithmDetails { | ||||
|         std::string     name; | ||||
|         std::string     parameters; | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct RRMDetails { | ||||
|         std::string     vendor; | ||||
|         std::string     schedule; | ||||
|         std::vector<RRMAlgorithmDetails>    algorithms; | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct DeviceRules { | ||||
|         std::string     rcOnly{"inherit"}; | ||||
|         std::string     rrm{"inherit"}; | ||||
|   | ||||
| @@ -42,6 +42,7 @@ namespace OpenWifi { | ||||
|  | ||||
|     void Signup::run() { | ||||
|         Running_ = true; | ||||
|         Utils::SetThreadName("signup-mgr"); | ||||
|         while(Running_) { | ||||
|             Poco::Thread::trySleep(5000); | ||||
|  | ||||
|   | ||||
| @@ -105,6 +105,7 @@ namespace OpenWifi { | ||||
|     } | ||||
|  | ||||
|     void Storage::onTimer([[maybe_unused]] Poco::Timer &timer) { | ||||
|         Utils::SetThreadName("strg-janitor"); | ||||
|     } | ||||
|  | ||||
|     void Storage::Stop() { | ||||
| @@ -194,18 +195,24 @@ namespace OpenWifi { | ||||
|         // check that all inventory in venues and entities actually exists, if not, fix it. | ||||
|         auto FixVenueDevices = [&](const ProvObjects::Venue &V) -> bool { | ||||
|             Types::UUIDvec_t NewDevices; | ||||
|             bool modified=false; | ||||
|             for(const auto &device:V.devices) { | ||||
|                 ProvObjects::InventoryTag T; | ||||
|                 if(InventoryDB().GetRecord("id", device, T)) { | ||||
|                     NewDevices.emplace_back(device); | ||||
|                 } else { | ||||
|  | ||||
|                     modified=true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if(NewDevices!=V.devices) { | ||||
|             ProvObjects::Venue NewVenue = V; | ||||
|             if(V.deviceRules.rrm=="yes") { | ||||
|                 NewVenue.deviceRules.rrm="inherit"; | ||||
|                 modified=true; | ||||
|             } | ||||
|  | ||||
|             if(modified) { | ||||
|                 Logger().warning(fmt::format("  fixing venue: {}", V.info.name)); | ||||
|                 ProvObjects::Venue NewVenue = V; | ||||
|                 NewVenue.devices = NewDevices; | ||||
|                 VenueDB().UpdateRecord("id", V.info.id, NewVenue); | ||||
|             } | ||||
| @@ -213,31 +220,175 @@ namespace OpenWifi { | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         auto FixEntityDevices = [&](const ProvObjects::Entity &E) -> bool { | ||||
|         auto FixEntity = [&](const ProvObjects::Entity &E) -> bool { | ||||
|             Types::UUIDvec_t NewDevices; | ||||
|             bool Modified=false; | ||||
|             for(const auto &device:E.devices) { | ||||
|                 ProvObjects::InventoryTag T; | ||||
|                 if(InventoryDB().GetRecord("id", device, T)) { | ||||
|                     NewDevices.emplace_back(device); | ||||
|                 } else { | ||||
|  | ||||
|                     Modified=true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if(NewDevices!=E.devices) { | ||||
|             Types::UUIDvec_t NewContacts; | ||||
|             for(const auto &contact:E.contacts) { | ||||
|                 ProvObjects::Contact C; | ||||
|                 if(ContactDB().GetRecord("id", contact, C)) { | ||||
|                     NewContacts.emplace_back(contact); | ||||
|                 } else { | ||||
|                     Modified=true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Types::UUIDvec_t NewLocations; | ||||
|             for(const auto &location:E.locations) { | ||||
|                 ProvObjects::Location L; | ||||
|                 if(LocationDB().GetRecord("id", location, L)) { | ||||
|                     NewLocations.emplace_back(location); | ||||
|                 } else { | ||||
|                     Modified=true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Types::UUIDvec_t NewVenues; | ||||
|             for(const auto &venue:E.venues) { | ||||
|                 ProvObjects::Venue V; | ||||
|                 if(VenueDB().GetRecord("id", venue, V)) { | ||||
|                     NewVenues.emplace_back(venue); | ||||
|                 } else { | ||||
|                     Modified=true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Types::UUIDvec_t NewVariables; | ||||
|             for(const auto &variable:E.variables) { | ||||
|                 ProvObjects::VariableBlock V; | ||||
|                 if(VariablesDB().GetRecord("id", variable, V)) { | ||||
|                     NewVariables.emplace_back(variable); | ||||
|                 } else { | ||||
|                     Modified=true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             ProvObjects::Entity NewEntity = E; | ||||
|  | ||||
|             if(E.deviceRules.rrm=="yes") { | ||||
|                 NewEntity.deviceRules.rrm="inherit"; | ||||
|                 Modified=true; | ||||
|             } | ||||
|  | ||||
|             if(Modified) | ||||
|             { | ||||
|                 Logger().warning(fmt::format("  fixing entity: {}",E.info.name)); | ||||
|                 ProvObjects::Entity NewEntity = E; | ||||
|                 NewEntity.devices = NewDevices; | ||||
|                 NewEntity.contacts = NewContacts; | ||||
|                 NewEntity.locations = NewLocations; | ||||
|                 NewEntity.venues = NewVenues; | ||||
|                 NewEntity.variables = NewVariables; | ||||
|                 EntityDB().UpdateRecord("id", E.info.id, NewEntity); | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         auto FixInventory = [&](const ProvObjects::InventoryTag &T) -> bool { | ||||
|             // check the venue/entity for this device. | ||||
|             ProvObjects::InventoryTag   NewTag{T}; | ||||
|             bool modified=false; | ||||
|             if(!T.venue.empty() && !VenueDB().Exists("id",T.venue)) { | ||||
|                 NewTag.venue.clear(); | ||||
|                 modified=true; | ||||
|             } | ||||
|  | ||||
|             if(!T.entity.empty() && !EntityDB().Exists("id",T.entity)) { | ||||
|                 NewTag.entity.clear(); | ||||
|                 modified=true; | ||||
|             } | ||||
|  | ||||
|             if(!T.location.empty() && !LocationDB().Exists("id",T.location)) { | ||||
|                 NewTag.location.clear(); | ||||
|                 modified=true; | ||||
|             } | ||||
|  | ||||
|             if(!T.contact.empty() && !ContactDB().Exists("id",T.contact)) { | ||||
|                 NewTag.contact.clear(); | ||||
|                 modified=true; | ||||
|             } | ||||
|  | ||||
|             if(T.deviceRules.rrm=="yes") { | ||||
|                 NewTag.deviceRules.rrm = "inherit"; | ||||
|                 modified=true; | ||||
|             } | ||||
|  | ||||
|             if(modified) { | ||||
|                 Logger().warning(fmt::format("  fixing entity: {}",T.info.name)); | ||||
|                 InventoryDB().UpdateRecord("id", T.info.id, NewTag); | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         auto FixConfiguration = [&](const ProvObjects::DeviceConfiguration &C) -> bool { | ||||
|             ProvObjects::DeviceConfiguration NewConfig{C}; | ||||
|  | ||||
|             bool modified = false; | ||||
|  | ||||
|             if (C.deviceRules.rrm == "yes") { | ||||
|                 NewConfig.deviceRules.rrm = "inherit"; | ||||
|                 modified = true; | ||||
|             } | ||||
|  | ||||
|             if (modified) { | ||||
|                 Logger().warning(fmt::format("  fixing configuration: {}", C.info.name)); | ||||
|                 ConfigurationDB().UpdateRecord("id", C.info.id, NewConfig); | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         auto FixOperator = [&](const ProvObjects::Operator &O) -> bool { | ||||
|             ProvObjects::Operator NewOp{O}; | ||||
|             bool modified = false; | ||||
|  | ||||
|             if (O.deviceRules.rrm == "yes") { | ||||
|                 NewOp.deviceRules.rrm = "inherit"; | ||||
|                 modified = true; | ||||
|             } | ||||
|  | ||||
|             if (modified) { | ||||
|                 Logger().warning(fmt::format("  fixing operator: {}", O.info.name)); | ||||
|                 OperatorDB().UpdateRecord("id", O.info.id, NewOp); | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         auto FixSubscriber = [&](const ProvObjects::SubscriberDevice &O) -> bool { | ||||
|             ProvObjects::SubscriberDevice NewSub{O}; | ||||
|             bool modified = false; | ||||
|  | ||||
|             if (O.deviceRules.rrm == "yes") { | ||||
|                 NewSub.deviceRules.rrm = "inherit"; | ||||
|                 modified = true; | ||||
|             } | ||||
|  | ||||
|             if (modified) { | ||||
|                 Logger().warning(fmt::format("  fixing subscriber: {}", O.info.name)); | ||||
|                 SubscriberDeviceDB().UpdateRecord("id", O.info.id, NewSub); | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         Logger().information("Checking DB consistency: venues"); | ||||
|         VenueDB().Iterate(FixVenueDevices); | ||||
|         Logger().information("Checking DB consistency: entities"); | ||||
|         EntityDB().Iterate(FixEntityDevices); | ||||
|  | ||||
|         EntityDB().Iterate(FixEntity); | ||||
|         Logger().information("Checking DB consistency: inventory"); | ||||
|         InventoryDB().Iterate(FixInventory); | ||||
|         Logger().information("Checking DB consistency: configurations"); | ||||
|         ConfigurationDB().Iterate(FixConfiguration); | ||||
|         Logger().information("Checking DB consistency: operators"); | ||||
|         OperatorDB().Iterate(FixOperator); | ||||
|         Logger().information("Checking DB consistency: subscribers"); | ||||
|         SubscriberDeviceDB().Iterate(FixSubscriber); | ||||
|     } | ||||
|  | ||||
|     void Storage::InitializeSystemDBs() { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ namespace OpenWifi { | ||||
|     } | ||||
|  | ||||
|     void TagServer::run() { | ||||
|  | ||||
|         Utils::SetThreadName("tag-server"); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -9,6 +9,7 @@ | ||||
| #include "APConfig.h" | ||||
| #include "sdks/SDK_gw.h" | ||||
| #include "framework/WebSocketClientNotifications.h" | ||||
| #include "JobController.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
| @@ -19,8 +20,6 @@ namespace OpenWifi { | ||||
|                 auto Status = Results->get("status").extract<Poco::JSON::Object::Ptr>(); | ||||
|                 auto Rejected = Status->getArray("rejected"); | ||||
|                 std::transform(Rejected->begin(),Rejected->end(),std::back_inserter(Warnings), [](auto i) -> auto { return i.toString(); }); | ||||
| //                for(const auto &i:*Rejected) | ||||
|                 //                  Warnings.push_back(i.toString()); | ||||
|             } | ||||
|         } catch (...) { | ||||
|         } | ||||
| @@ -37,35 +36,42 @@ namespace OpenWifi { | ||||
|         void run() final { | ||||
|             ProvObjects::InventoryTag   Device; | ||||
|             started_=true; | ||||
|             Utils::SetThreadName("venue-cfg"); | ||||
|             if(StorageService()->InventoryDB().GetRecord("id",uuid_,Device)) { | ||||
|                 SerialNumber = Device.serialNumber; | ||||
|                 // std::cout << "Starting push for " << Device.serialNumber << std::endl; | ||||
|                 Logger().debug(fmt::format("{}: Computing configuration.",Device.serialNumber)); | ||||
|                 auto DeviceConfig = std::make_shared<APConfig>(Device.serialNumber, Device.deviceType, Logger(), false); | ||||
|                 auto Configuration = Poco::makeShared<Poco::JSON::Object>(); | ||||
|                 if (DeviceConfig->Get(Configuration)) { | ||||
|                     std::ostringstream OS; | ||||
|                     Configuration->stringify(OS); | ||||
|                     auto Response=Poco::makeShared<Poco::JSON::Object>(); | ||||
|                     Logger().debug(fmt::format("{}: Pushing configuration.",Device.serialNumber)); | ||||
|                     if (SDK::GW::Device::Configure(nullptr, Device.serialNumber, Configuration, Response)) { | ||||
|                         Logger().debug(fmt::format("{}: Configuration pushed.",Device.serialNumber)); | ||||
|                         Logger().information(fmt::format("{}: Updated.", Device.serialNumber)); | ||||
|                         // std::cout << Device.serialNumber << ": Updated" << std::endl; | ||||
|                         updated_++; | ||||
|                 try { | ||||
|                     if (DeviceConfig->Get(Configuration)) { | ||||
|                         std::ostringstream OS; | ||||
|                         Configuration->stringify(OS); | ||||
|                         auto Response = Poco::makeShared<Poco::JSON::Object>(); | ||||
|                         Logger().debug(fmt::format("{}: Pushing configuration.", Device.serialNumber)); | ||||
|                         if (SDK::GW::Device::Configure(nullptr, Device.serialNumber, Configuration, Response)) { | ||||
|                             Logger().debug(fmt::format("{}: Configuration pushed.", Device.serialNumber)); | ||||
|                             Logger().information(fmt::format("{}: Updated.", Device.serialNumber)); | ||||
|                             // std::cout << Device.serialNumber << ": Updated" << std::endl; | ||||
|                             updated_++; | ||||
|                         } else { | ||||
|                             Logger().information(fmt::format("{}: Not updated.", Device.serialNumber)); | ||||
|                             // std::cout << Device.serialNumber << ": Failed" << std::endl; | ||||
|                             failed_++; | ||||
|                         } | ||||
|                     } else { | ||||
|                         Logger().information(fmt::format("{}: Not updated.", Device.serialNumber)); | ||||
|                         // std::cout << Device.serialNumber << ": Failed" << std::endl; | ||||
|                         failed_++; | ||||
|                         Logger().debug(fmt::format("{}: Configuration is bad.", Device.serialNumber)); | ||||
|                         bad_config_++; | ||||
|                         // std::cout << Device.serialNumber << ": Bad config" << std::endl; | ||||
|                     } | ||||
|                 } else { | ||||
|                     Logger().debug(fmt::format("{}: Configuration is bad.",Device.serialNumber)); | ||||
|                 } catch (...) { | ||||
|                     Logger().debug(fmt::format("{}: Configuration is bad (caused an exception).", Device.serialNumber)); | ||||
|                     bad_config_++; | ||||
|                     // std::cout << Device.serialNumber << ": Bad config" << std::endl; | ||||
|                 } | ||||
|             } | ||||
|             done_ = true; | ||||
|             // std::cout << "Done push for " << Device.serialNumber << std::endl; | ||||
|             Utils::SetThreadName("free"); | ||||
|         } | ||||
|  | ||||
|         uint64_t        updated_=0, failed_=0, bad_config_=0; | ||||
| @@ -80,133 +86,101 @@ namespace OpenWifi { | ||||
|         inline Poco::Logger & Logger() { return Logger_; } | ||||
|     }; | ||||
|  | ||||
|     class VenueConfigUpdater: public Poco::Runnable { | ||||
|     class VenueConfigUpdater: public Job { | ||||
|     public: | ||||
|         explicit VenueConfigUpdater(const std::string & VenueUUID, const SecurityObjects::UserInfo &UI, uint64_t When, Poco::Logger &L) : | ||||
|             VenueUUID_(VenueUUID), | ||||
|             UI_(UI), | ||||
|             When_(When), | ||||
|             Logger_(L) | ||||
|         { | ||||
|         VenueConfigUpdater(const std::string &JobID, const std::string &name, const std::vector<std::string> & parameters, uint64_t when, const SecurityObjects::UserInfo &UI, Poco::Logger &L) : | ||||
|                 Job(JobID, name, parameters, when, UI, L) { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         inline std::string Start() { | ||||
|             JobId_ = MicroService::CreateUUID(); | ||||
|             Worker_.start(*this); | ||||
|             return JobId_; | ||||
|         } | ||||
|         inline virtual void run() { | ||||
|             std::string                 VenueUUID_; | ||||
|  | ||||
|     private: | ||||
|         std::string                 VenueUUID_; | ||||
|         SecurityObjects::UserInfo   UI_; | ||||
|         uint64_t                    When_; | ||||
|         Poco::Logger                &Logger_; | ||||
|         Poco::Thread                Worker_; | ||||
|         std::string                 JobId_; | ||||
|         Poco::ThreadPool            Pool_{2,16,300}; | ||||
|  | ||||
|         inline Poco::Logger & Logger() { return Logger_; } | ||||
|  | ||||
|         inline void run() final { | ||||
|  | ||||
|             if(When_ && When_>OpenWifi::Now()) | ||||
|                 Poco::Thread::trySleep( (long) (When_ - OpenWifi::Now()) * 1000 ); | ||||
|             Utils::SetThreadName("venue-update"); | ||||
|             VenueUUID_ = Parameter(0); | ||||
|  | ||||
|             WebSocketNotification<WebSocketNotificationJobContent> N; | ||||
|  | ||||
|             Logger().information(fmt::format("Job {} Starting.", JobId_)); | ||||
|  | ||||
|             ProvObjects::Venue  Venue; | ||||
|             uint64_t Updated = 0, Failed = 0 , BadConfigs = 0 ; | ||||
|             if(StorageService()->VenueDB().GetRecord("id",VenueUUID_,Venue)) { | ||||
|                 const std::size_t MaxThreads=16; | ||||
|                 struct tState { | ||||
|                     Poco::Thread                thr_; | ||||
|                     VenueDeviceConfigUpdater    *task= nullptr; | ||||
|                 }; | ||||
|  | ||||
|                 N.content.title = fmt::format("Updating {} configurations", Venue.info.name); | ||||
|                 N.content.jobId = JobId_; | ||||
|                 N.content.jobId = JobId(); | ||||
|  | ||||
|                 Poco::ThreadPool    Pool_; | ||||
|                 std::list<VenueDeviceConfigUpdater*> JobList; | ||||
|  | ||||
|                 std::array<tState,MaxThreads> Tasks; | ||||
|  | ||||
|                 for(const auto &uuid:Venue.devices) { | ||||
|                     auto NewTask = new VenueDeviceConfigUpdater(uuid, Venue.info.name, Logger()); | ||||
|                     // std::cout << "Scheduling config push for " << uuid << std::endl; | ||||
|                     bool found_slot = false; | ||||
|                     while (!found_slot) { | ||||
|                         for (auto &cur_task: Tasks) { | ||||
|                             if (cur_task.task == nullptr) { | ||||
|                                 cur_task.task = NewTask; | ||||
|                                 cur_task.thr_.start(*NewTask); | ||||
|                                 found_slot = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                     bool TaskAdded=false; | ||||
|                     while(!TaskAdded) { | ||||
|                         if (Pool_.available()) { | ||||
|                             JobList.push_back(NewTask); | ||||
|                             Pool_.start(*NewTask); | ||||
|                             TaskAdded = true; | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                         //  Let's look for a slot... | ||||
|                         if (!found_slot) { | ||||
|                             for (auto &cur_task: Tasks) { | ||||
|                                 if (cur_task.task != nullptr && cur_task.task->started_) { | ||||
|                                     if (cur_task.thr_.isRunning()) | ||||
|                                         continue; | ||||
|                                     if (!cur_task.thr_.isRunning() && cur_task.task->done_) { | ||||
|                                         cur_task.thr_.join(); | ||||
|                                         Updated += cur_task.task->updated_; | ||||
|                                         Failed += cur_task.task->failed_; | ||||
|                                         BadConfigs += cur_task.task->bad_config_; | ||||
|                                         cur_task.task->started_ = cur_task.task->done_ = false; | ||||
|                                         delete cur_task.task; | ||||
|                                         cur_task.task = nullptr; | ||||
|                                     } | ||||
|                                 } | ||||
|                     for(auto job_it = JobList.begin(); job_it !=JobList.end();) { | ||||
|                         VenueDeviceConfigUpdater * current_job = *job_it; | ||||
|                         if(current_job!= nullptr && current_job->done_) { | ||||
|                             Updated += current_job->updated_; | ||||
|                             Failed += current_job->failed_; | ||||
|                             BadConfigs += current_job->bad_config_; | ||||
|                             if(current_job->updated_) { | ||||
|                                 N.content.success.push_back(current_job->SerialNumber); | ||||
|                             } else if(current_job->failed_) { | ||||
|                                 N.content.warning.push_back(current_job->SerialNumber); | ||||
|                             } else { | ||||
|                                 N.content.error.push_back(current_job->SerialNumber); | ||||
|                             } | ||||
|                             job_it = JobList.erase(job_it); | ||||
|                             delete current_job; | ||||
|                         } else { | ||||
|                             ++job_it; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Logger().debug("Waiting for outstanding update threads to finish."); | ||||
|                 bool stillTasksRunning=true; | ||||
|                 while(stillTasksRunning) { | ||||
|                     stillTasksRunning = false; | ||||
|                     for(auto &cur_task:Tasks) { | ||||
|                         if(cur_task.task!= nullptr && cur_task.task->started_) { | ||||
|                             if(cur_task.thr_.isRunning()) { | ||||
|                                 stillTasksRunning = true; | ||||
|                                 continue; | ||||
|                             } | ||||
|                             if(!cur_task.thr_.isRunning() && cur_task.task->done_) { | ||||
|                                 cur_task.thr_.join(); | ||||
|                                 if(cur_task.task->updated_) { | ||||
|                                     Updated++; | ||||
|                                     N.content.success.push_back(cur_task.task->SerialNumber); | ||||
|                                 } else if(cur_task.task->failed_) { | ||||
|                                     Failed++; | ||||
|                                     N.content.warning.push_back(cur_task.task->SerialNumber); | ||||
|                                 } else { | ||||
|                                     BadConfigs++; | ||||
|                                     N.content.error.push_back(cur_task.task->SerialNumber); | ||||
|                                 } | ||||
|                                 cur_task.task->started_ = cur_task.task->done_ = false; | ||||
|                                 delete cur_task.task; | ||||
|                                 cur_task.task = nullptr; | ||||
|                             } | ||||
|                 Pool_.joinAll(); | ||||
|                 for(auto job_it = JobList.begin(); job_it !=JobList.end();) { | ||||
|                     VenueDeviceConfigUpdater * current_job = *job_it; | ||||
|                     if(current_job!= nullptr && current_job->done_) { | ||||
|                         Updated += current_job->updated_; | ||||
|                         Failed += current_job->failed_; | ||||
|                         BadConfigs += current_job->bad_config_; | ||||
|                         if(current_job->updated_) { | ||||
|                             N.content.success.push_back(current_job->SerialNumber); | ||||
|                         } else if(current_job->failed_) { | ||||
|                             N.content.warning.push_back(current_job->SerialNumber); | ||||
|                         } else { | ||||
|                             N.content.error.push_back(current_job->SerialNumber); | ||||
|                         } | ||||
|                         job_it = JobList.erase(job_it); | ||||
|                         delete current_job; | ||||
|                     } else { | ||||
|                         ++job_it; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 N.content.details = fmt::format("Job {} Completed: {} updated, {} failed to update, {} bad configurations. ", | ||||
|                                                 JobId_, Updated ,Failed, BadConfigs); | ||||
|                                                 JobId(), Updated ,Failed, BadConfigs); | ||||
|  | ||||
|             } else { | ||||
|                 N.content.details = fmt::format("Venue {} no longer exists.",VenueUUID_); | ||||
|                 Logger().warning(N.content.details); | ||||
|             } | ||||
|  | ||||
|             WebSocketClientNotificationVenueUpdateJobCompletionToUser(UI_.email, N); | ||||
|             // std::cout << N.content.details << std::endl; | ||||
|             WebSocketClientNotificationVenueUpdateJobCompletionToUser(UserInfo().email, N); | ||||
|             Logger().information(fmt::format("Job {} Completed: {} updated, {} failed to update , {} bad configurations.", | ||||
|                                              JobId_, Updated ,Failed, BadConfigs)); | ||||
|             delete this; | ||||
|                                              JobId(), Updated ,Failed, BadConfigs)); | ||||
|             Utils::SetThreadName("free"); | ||||
|             Complete(); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #include "StorageService.h" | ||||
| #include "APConfig.h" | ||||
| #include "sdks/SDK_gw.h" | ||||
| #include "JobController.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
| @@ -48,129 +49,90 @@ namespace OpenWifi { | ||||
|         inline Poco::Logger & Logger() { return Logger_; } | ||||
|     }; | ||||
|  | ||||
|     class VenueRebooter: public Poco::Runnable { | ||||
|     class VenueRebooter: public Job { | ||||
|     public: | ||||
|         explicit VenueRebooter(const std::string & VenueUUID, const SecurityObjects::UserInfo &UI, uint64_t When, Poco::Logger &L) : | ||||
|                 VenueUUID_(VenueUUID), | ||||
|                 UI_(UI), | ||||
|                 When_(When), | ||||
|                 Logger_(L) | ||||
|         { | ||||
|         VenueRebooter(const std::string &JobID, const std::string &name, const std::vector<std::string> & parameters, uint64_t when, const SecurityObjects::UserInfo &UI, Poco::Logger &L) : | ||||
|                 Job(JobID, name, parameters, when, UI, L) { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         inline std::string Start() { | ||||
|             JobId_ = MicroService::CreateUUID(); | ||||
|             Worker_.start(*this); | ||||
|             return JobId_; | ||||
|         } | ||||
|         inline virtual void run() final { | ||||
|  | ||||
|     private: | ||||
|         std::string                 VenueUUID_; | ||||
|         SecurityObjects::UserInfo   UI_; | ||||
|         uint64_t                    When_; | ||||
|         Poco::Logger                &Logger_; | ||||
|         Poco::Thread                Worker_; | ||||
|         std::string                 JobId_; | ||||
|         Poco::ThreadPool            Pool_{2,16,300}; | ||||
|  | ||||
|         inline Poco::Logger & Logger() { return Logger_; } | ||||
|  | ||||
|         inline void run() final { | ||||
|  | ||||
|             if(When_ && When_>OpenWifi::Now()) | ||||
|                 Poco::Thread::trySleep( (long) (When_ - OpenWifi::Now()) * 1000 ); | ||||
|             Utils::SetThreadName("venue-reboot"); | ||||
|  | ||||
|             WebSocketClientNotificationVenueRebootList_t        N; | ||||
|  | ||||
|             Logger().information(fmt::format("Job {} Starting.", JobId_)); | ||||
|             auto VenueUUID_ = Parameter(0); | ||||
|  | ||||
|             ProvObjects::Venue  Venue; | ||||
|             uint64_t rebooted_ = 0, failed_ = 0; | ||||
|             if(StorageService()->VenueDB().GetRecord("id",VenueUUID_,Venue)) { | ||||
|                 const std::size_t MaxThreads=16; | ||||
|                 struct tState { | ||||
|                     Poco::Thread                thr_; | ||||
|                     VenueDeviceRebooter    *task= nullptr; | ||||
|                 }; | ||||
|  | ||||
|                 N.content.title = fmt::format("Rebooting {} devices.", Venue.info.name); | ||||
|                 N.content.jobId = JobId_; | ||||
|                 N.content.jobId = JobId(); | ||||
|  | ||||
|                 std::array<tState,MaxThreads> Tasks; | ||||
|                 Poco::ThreadPool    Pool_; | ||||
|                 std::list<VenueDeviceRebooter*> JobList; | ||||
|  | ||||
|                 for(const auto &uuid:Venue.devices) { | ||||
|                     auto NewTask = new VenueDeviceRebooter(uuid, Venue.info.name, Logger()); | ||||
|                     // std::cout << "Scheduling config push for " << uuid << std::endl; | ||||
|                     bool found_slot = false; | ||||
|                     while (!found_slot) { | ||||
|                         for (auto &cur_task: Tasks) { | ||||
|                             if (cur_task.task == nullptr) { | ||||
|                                 cur_task.task = NewTask; | ||||
|                                 cur_task.thr_.start(*NewTask); | ||||
|                                 found_slot = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                     bool TaskAdded=false; | ||||
|                     while(!TaskAdded) { | ||||
|                         if (Pool_.available()) { | ||||
|                             JobList.push_back(NewTask); | ||||
|                             Pool_.start(*NewTask); | ||||
|                             TaskAdded = true; | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                         //  Let's look for a slot... | ||||
|                         if (!found_slot) { | ||||
|                             for (auto &cur_task: Tasks) { | ||||
|                                 if (cur_task.task != nullptr && cur_task.task->started_) { | ||||
|                                     if (cur_task.thr_.isRunning()) | ||||
|                                         continue; | ||||
|                                     if (!cur_task.thr_.isRunning() && cur_task.task->done_) { | ||||
|                                         cur_task.thr_.join(); | ||||
|                                         rebooted_ += cur_task.task->rebooted_; | ||||
|                                         failed_ += cur_task.task->failed_; | ||||
|                                         cur_task.task->started_ = cur_task.task->done_ = false; | ||||
|                                         delete cur_task.task; | ||||
|                                         cur_task.task = nullptr; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                     for(auto job_it = JobList.begin(); job_it !=JobList.end();) { | ||||
|                         VenueDeviceRebooter * current_job = *job_it; | ||||
|                         if(current_job!= nullptr && current_job->done_) { | ||||
|                             if(current_job->rebooted_) | ||||
|                                 N.content.success.push_back(current_job->SerialNumber); | ||||
|                             else | ||||
|                                 N.content.warning.push_back(current_job->SerialNumber); | ||||
|                             rebooted_ += current_job->rebooted_; | ||||
|                             failed_ += current_job->failed_; | ||||
|                             job_it = JobList.erase(job_it); | ||||
|                             delete current_job; | ||||
|                         } else { | ||||
|                             ++job_it; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Logger().debug("Waiting for outstanding update threads to finish."); | ||||
|                 bool stillTasksRunning=true; | ||||
|                 while(stillTasksRunning) { | ||||
|                     stillTasksRunning = false; | ||||
|                     for(auto &cur_task:Tasks) { | ||||
|                         if(cur_task.task!= nullptr && cur_task.task->started_) { | ||||
|                             if(cur_task.thr_.isRunning()) { | ||||
|                                 stillTasksRunning = true; | ||||
|                                 continue; | ||||
|                             } | ||||
|                             if(!cur_task.thr_.isRunning() && cur_task.task->done_) { | ||||
|                                 cur_task.thr_.join(); | ||||
|                                 if(cur_task.task->rebooted_) { | ||||
|                                     rebooted_++; | ||||
|                                     N.content.success.push_back(cur_task.task->SerialNumber); | ||||
|                                 } else if(cur_task.task->failed_) { | ||||
|                                     failed_++; | ||||
|                                     N.content.warning.push_back(cur_task.task->SerialNumber); | ||||
|                                 } | ||||
|                                 cur_task.task->started_ = cur_task.task->done_ = false; | ||||
|                                 delete cur_task.task; | ||||
|                                 cur_task.task = nullptr; | ||||
|                             } | ||||
|                         } | ||||
|                 Pool_.joinAll(); | ||||
|                 for(auto job_it = JobList.begin(); job_it !=JobList.end();) { | ||||
|                     VenueDeviceRebooter * current_job = *job_it; | ||||
|                     if(current_job!= nullptr && current_job->done_) { | ||||
|                         if(current_job->rebooted_) | ||||
|                             N.content.success.push_back(current_job->SerialNumber); | ||||
|                         else | ||||
|                             N.content.warning.push_back(current_job->SerialNumber); | ||||
|                         rebooted_ += current_job->rebooted_; | ||||
|                         failed_ += current_job->failed_; | ||||
|                         job_it = JobList.erase(job_it); | ||||
|                         delete current_job; | ||||
|                     } else { | ||||
|                         ++job_it; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 N.content.details = fmt::format("Job {} Completed: {} rebooted, {} failed to reboot.", | ||||
|                                                 JobId_, rebooted_ ,failed_); | ||||
|                                                 JobId(), rebooted_ ,failed_); | ||||
|  | ||||
|             } else { | ||||
|                 N.content.details = fmt::format("Venue {} no longer exists.",VenueUUID_); | ||||
|                 Logger().warning(N.content.details); | ||||
|             } | ||||
|  | ||||
|             WebSocketClientNotificationVenueRebootCompletionToUser(UI_.email,N); | ||||
|             // std::cout << N.content.details << std::endl; | ||||
|             WebSocketClientNotificationVenueRebootCompletionToUser(UserInfo().email,N); | ||||
|             Logger().information(fmt::format("Job {} Completed: {} rebooted, {} failed to reboot.", | ||||
|                                              JobId_, rebooted_ ,failed_)); | ||||
|             delete this; | ||||
|                                              JobId(), rebooted_ ,failed_)); | ||||
|             Utils::SetThreadName("free"); | ||||
|             Complete(); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include "APConfig.h" | ||||
| #include "sdks/SDK_gw.h" | ||||
| #include "sdks/SDK_fms.h" | ||||
| #include "JobController.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|     class VenueDeviceUpgrade : public Poco::Runnable { | ||||
| @@ -30,7 +31,7 @@ namespace OpenWifi { | ||||
|  | ||||
|                 Storage::ApplyRules(rules_,Device.deviceRules); | ||||
|                 if(Device.deviceRules.firmwareUpgrade=="no") { | ||||
|                     std::cout << "Skipped Upgrade:" << Device.serialNumber << std::endl; | ||||
|                     poco_debug(Logger(),fmt::format("Skipped Upgrade: {}", Device.serialNumber)); | ||||
|                     skipped_++; | ||||
|                     done_=true; | ||||
|                     return; | ||||
| @@ -39,24 +40,25 @@ namespace OpenWifi { | ||||
|                 FMSObjects::Firmware    F; | ||||
|                 if(SDK::FMS::Firmware::GetLatest(Device.deviceType,Device.deviceRules.rcOnly=="yes",F)) { | ||||
|                     if (SDK::GW::Device::Upgrade(nullptr, Device.serialNumber, 0, F.uri)) { | ||||
|                         std::cout << "Upgraded:" << Device.serialNumber << " to " << F.uri << std::endl; | ||||
|                         Logger().debug(fmt::format("{}: Upgraded.",Device.serialNumber)); | ||||
|                         upgraded_++; | ||||
|                     } else { | ||||
|                         std::cout << "Did not Upgrade:" << Device.serialNumber << " to " << F.uri << std::endl; | ||||
|                         Logger().information(fmt::format("{}: Not Upgraded.", Device.serialNumber)); | ||||
|                         failed_++; | ||||
|                         not_connected_++; | ||||
|                     } | ||||
|                 } else { | ||||
|                     std::cout << "Did not Upgrade:" << Device.serialNumber << " to <unknown>" << std::endl; | ||||
|                     failed_++; | ||||
|                     Logger().information(fmt::format("{}: Not Upgraded. No firmware available.", Device.serialNumber)); | ||||
|                     no_firmware_++; | ||||
|                 } | ||||
|             } | ||||
|             done_ = true; | ||||
|             // std::cout << "Done push for " << Device.serialNumber << std::endl; | ||||
|         } | ||||
|  | ||||
|         uint64_t        upgraded_=0, failed_=0, skipped_=0; | ||||
|         std::uint64_t   upgraded_ = 0, | ||||
|                         not_connected_ = 0, | ||||
|                         skipped_ = 0, | ||||
|                         no_firmware_ = 0; | ||||
|         bool            started_ = false, | ||||
|                         done_ = false; | ||||
|         std::string     SerialNumber; | ||||
| @@ -69,132 +71,111 @@ namespace OpenWifi { | ||||
|         inline Poco::Logger & Logger() { return Logger_; } | ||||
|     }; | ||||
|  | ||||
|     class VenueUpgrade: public Poco::Runnable { | ||||
|     class VenueUpgrade: public Job { | ||||
|     public: | ||||
|         explicit VenueUpgrade(const std::string & VenueUUID, const SecurityObjects::UserInfo &UI, uint64_t When, Poco::Logger &L) : | ||||
|                 VenueUUID_(VenueUUID), | ||||
|                 UI_(UI), | ||||
|                 When_(When), | ||||
|                 Logger_(L) | ||||
|         { | ||||
|         VenueUpgrade(const std::string &JobID, const std::string &name, const std::vector<std::string> & parameters, uint64_t when, const SecurityObjects::UserInfo &UI, Poco::Logger &L) : | ||||
|                 Job(JobID, name, parameters, when, UI, L) { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         inline std::string Start() { | ||||
|             JobId_ = MicroService::CreateUUID(); | ||||
|             Worker_.start(*this); | ||||
|             return JobId_; | ||||
|         } | ||||
|         inline virtual void run() final { | ||||
|  | ||||
|     private: | ||||
|         std::string                 VenueUUID_; | ||||
|         SecurityObjects::UserInfo   UI_; | ||||
|         uint64_t                    When_; | ||||
|         Poco::Logger                &Logger_; | ||||
|         Poco::Thread                Worker_; | ||||
|         std::string                 JobId_; | ||||
|         Poco::ThreadPool            Pool_{2,16,300}; | ||||
|             Utils::SetThreadName("venue-upgr"); | ||||
|             auto VenueUUID_ = Parameter(0); | ||||
|  | ||||
|         inline Poco::Logger & Logger() { return Logger_; } | ||||
|  | ||||
|         inline void run() final { | ||||
|  | ||||
|             if(When_ && When_>OpenWifi::Now()) | ||||
|                 Poco::Thread::trySleep( (long) (When_ - OpenWifi::Now()) * 1000 ); | ||||
|  | ||||
|             WebSocketClientNotificationVenueRebootList_t        N; | ||||
|  | ||||
|             Logger().information(fmt::format("Job {} Starting.", JobId_)); | ||||
|             WebSocketClientNotificationVenueUpgradeList_t        N; | ||||
|  | ||||
|             ProvObjects::Venue  Venue; | ||||
|             uint64_t upgraded_ = 0, failed_ = 0; | ||||
|             uint64_t    upgraded_ = 0, | ||||
|                         not_connected_ = 0, | ||||
|                         skipped_ = 0, | ||||
|                         no_firmware_ = 0; | ||||
|             if(StorageService()->VenueDB().GetRecord("id",VenueUUID_,Venue)) { | ||||
|                 const std::size_t MaxThreads=16; | ||||
|                 struct tState { | ||||
|                     Poco::Thread                thr_; | ||||
|                     VenueDeviceUpgrade    *task= nullptr; | ||||
|                 }; | ||||
|  | ||||
|                 N.content.title = fmt::format("Upgrading {} devices.", Venue.info.name); | ||||
|                 N.content.jobId = JobId_; | ||||
|                 N.content.jobId = JobId(); | ||||
|  | ||||
|                 std::array<tState,MaxThreads> Tasks; | ||||
|                 ProvObjects::DeviceRules    Rules; | ||||
|                 Poco::ThreadPool                Pool_; | ||||
|                 std::list<VenueDeviceUpgrade*>  JobList; | ||||
|                 ProvObjects::DeviceRules        Rules; | ||||
|  | ||||
|                 StorageService()->VenueDB().EvaluateDeviceRules(Venue.info.id, Rules); | ||||
|  | ||||
|                 for(const auto &uuid:Venue.devices) { | ||||
|                     auto NewTask = new VenueDeviceUpgrade(uuid, Venue.info.name, Rules,Logger()); | ||||
|                     // std::cout << "Scheduling config push for " << uuid << std::endl; | ||||
|                     bool found_slot = false; | ||||
|                     while (!found_slot) { | ||||
|                         for (auto &cur_task: Tasks) { | ||||
|                             if (cur_task.task == nullptr) { | ||||
|                                 cur_task.task = NewTask; | ||||
|                                 cur_task.thr_.start(*NewTask); | ||||
|                                 found_slot = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         //  Let's look for a slot... | ||||
|                         if (!found_slot) { | ||||
|                             for (auto &cur_task: Tasks) { | ||||
|                                 if (cur_task.task != nullptr && cur_task.task->started_) { | ||||
|                                     if (cur_task.thr_.isRunning()) | ||||
|                                         continue; | ||||
|                                     if (!cur_task.thr_.isRunning() && cur_task.task->done_) { | ||||
|                                         cur_task.thr_.join(); | ||||
|                                         upgraded_ += cur_task.task->upgraded_; | ||||
|                                         failed_ += cur_task.task->failed_; | ||||
|                                         cur_task.task->started_ = cur_task.task->done_ = false; | ||||
|                                         delete cur_task.task; | ||||
|                                         cur_task.task = nullptr; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                     auto NewTask = new VenueDeviceUpgrade(uuid, Venue.info.name, Rules, Logger()); | ||||
|                     bool TaskAdded = false; | ||||
|                     while (!TaskAdded) { | ||||
|                         if (Pool_.available()) { | ||||
|                             JobList.push_back(NewTask); | ||||
|                             Pool_.start(*NewTask); | ||||
|                             TaskAdded = true; | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 Logger().debug("Waiting for outstanding update threads to finish."); | ||||
|                 bool stillTasksRunning=true; | ||||
|                 while(stillTasksRunning) { | ||||
|                     stillTasksRunning = false; | ||||
|                     for(auto &cur_task:Tasks) { | ||||
|                         if(cur_task.task!= nullptr && cur_task.task->started_) { | ||||
|                             if(cur_task.thr_.isRunning()) { | ||||
|                                 stillTasksRunning = true; | ||||
|                                 continue; | ||||
|                             } | ||||
|                             if(!cur_task.thr_.isRunning() && cur_task.task->done_) { | ||||
|                                 cur_task.thr_.join(); | ||||
|                                 if(cur_task.task->upgraded_) { | ||||
|                                     upgraded_++; | ||||
|                                     N.content.success.push_back(cur_task.task->SerialNumber); | ||||
|                                 } else if(cur_task.task->failed_) { | ||||
|                                     failed_++; | ||||
|                                     N.content.warning.push_back(cur_task.task->SerialNumber); | ||||
|                                 } | ||||
|                                 cur_task.task->started_ = cur_task.task->done_ = false; | ||||
|                                 delete cur_task.task; | ||||
|                                 cur_task.task = nullptr; | ||||
|                             } | ||||
|  | ||||
|                     for (auto job_it = JobList.begin(); job_it != JobList.end();) { | ||||
|                         VenueDeviceUpgrade *current_job = *job_it; | ||||
|                         if (current_job != nullptr && current_job->done_) { | ||||
|                             if (current_job->upgraded_) | ||||
|                                 N.content.success.push_back(current_job->SerialNumber); | ||||
|                             else  if (current_job->skipped_) | ||||
|                                 N.content.skipped.push_back(current_job->SerialNumber); | ||||
|                             else  if (current_job->not_connected_) | ||||
|                                 N.content.not_connected.push_back(current_job->SerialNumber); | ||||
|                             else  if (current_job->no_firmware_) | ||||
|                                 N.content.no_firmware.push_back(current_job->SerialNumber); | ||||
|                             upgraded_ += current_job->upgraded_; | ||||
|                             skipped_ += current_job->skipped_; | ||||
|                             no_firmware_ += current_job->no_firmware_; | ||||
|                             not_connected_ += current_job->not_connected_; | ||||
|                             job_it = JobList.erase(job_it); | ||||
|                             delete current_job; | ||||
|                         } else { | ||||
|                             ++job_it; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 N.content.details = fmt::format("Job {} Completed: {} upgraded, {} failed to upgrade.", | ||||
|                                                 JobId_, upgraded_ ,failed_); | ||||
|                 Logger().debug("Waiting for outstanding upgrade threads to finish."); | ||||
|                 Pool_.joinAll(); | ||||
|                 for(auto job_it = JobList.begin(); job_it !=JobList.end();) { | ||||
|                     VenueDeviceUpgrade * current_job = *job_it; | ||||
|                     if(current_job!= nullptr && current_job->done_) { | ||||
|                         if (current_job->upgraded_) | ||||
|                             N.content.success.push_back(current_job->SerialNumber); | ||||
|                         else  if (current_job->skipped_) | ||||
|                             N.content.skipped.push_back(current_job->SerialNumber); | ||||
|                         else  if (current_job->not_connected_) | ||||
|                             N.content.not_connected.push_back(current_job->SerialNumber); | ||||
|                         else  if (current_job->no_firmware_) | ||||
|                             N.content.no_firmware.push_back(current_job->SerialNumber); | ||||
|                         upgraded_ += current_job->upgraded_; | ||||
|                         skipped_ += current_job->skipped_; | ||||
|                         no_firmware_ += current_job->no_firmware_; | ||||
|                         not_connected_ += current_job->not_connected_; | ||||
|                         job_it = JobList.erase(job_it); | ||||
|                         delete current_job; | ||||
|                     } else { | ||||
|                         ++job_it; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 N.content.details = fmt::format("Job {} Completed: {} upgraded, {} not connected, {} skipped, {} no firmware.", | ||||
|                                                 JobId(), | ||||
|                                                 upgraded_ , | ||||
|                                                 not_connected_, | ||||
|                                                 skipped_, | ||||
|                                                 no_firmware_); | ||||
|             } else { | ||||
|                 N.content.details = fmt::format("Venue {} no longer exists.",VenueUUID_); | ||||
|                 Logger().warning(N.content.details); | ||||
|             } | ||||
|  | ||||
|             WebSocketClientNotificationVenueRebootCompletionToUser(UI_.email,N); | ||||
|             Logger().information(fmt::format("Job {} Completed: {} upgraded, {} failed to upgrade.", | ||||
|                                              JobId_, upgraded_ ,failed_)); | ||||
|             delete this; | ||||
|             // std::cout << N.content.details << std::endl; | ||||
|             WebSocketClientNotificationVenueUpgradeCompletionToUser(UserInfo().email,N); | ||||
|             Logger().information(N.content.details); | ||||
|             Utils::SetThreadName("free"); | ||||
|             Complete(); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| @@ -27,6 +27,11 @@ namespace OpenWifi { | ||||
|     inline uint64_t Now() { return std::time(nullptr); }; | ||||
| } | ||||
|  | ||||
| namespace OpenWifi::Utils { | ||||
|     std::vector<unsigned char> base64decode(const std::string& input); | ||||
|     std::string base64encode(const unsigned char *input, uint32_t size); | ||||
| } | ||||
|  | ||||
| using namespace std::chrono_literals; | ||||
|  | ||||
| #include "Poco/Util/Application.h" | ||||
| @@ -238,6 +243,11 @@ namespace OpenWifi::RESTAPI_utils { | ||||
|         Obj.set(Field,Value); | ||||
|     } | ||||
|  | ||||
|     inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Poco::Data::BLOB &Value) { | ||||
|         auto Result = Utils::base64encode((const unsigned char *)Value.rawContent(),Value.size()); | ||||
|         Obj.set(Field,Result); | ||||
|     } | ||||
|  | ||||
|     inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringPairVec & S) { | ||||
|         Poco::JSON::Array   Array; | ||||
|         for(const auto &i:S) { | ||||
| @@ -334,12 +344,12 @@ namespace OpenWifi::RESTAPI_utils { | ||||
|  | ||||
|     inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, double & Value) { | ||||
|         if(Obj->has(Field) && !Obj->isNull(Field)) | ||||
|             Value = (double) Obj->get(Field); | ||||
|             Value = (double)Obj->get(Field); | ||||
|     } | ||||
|  | ||||
|     inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, float & Value) { | ||||
|         if(Obj->has(Field) && !Obj->isNull(Field)) | ||||
|             Value = (float) Obj->get(Field); | ||||
|             Value = (float)Obj->get(Field); | ||||
|     } | ||||
|  | ||||
|     inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, bool &Value) { | ||||
| @@ -374,7 +384,14 @@ namespace OpenWifi::RESTAPI_utils { | ||||
|  | ||||
|     inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint64_t &Value) { | ||||
|         if(Obj->has(Field) && !Obj->isNull(Field)) | ||||
|             Value = (uint64_t ) Obj->get(Field); | ||||
|             Value = (uint64_t)Obj->get(Field); | ||||
|     } | ||||
|  | ||||
|     inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Poco::Data::BLOB &Value) { | ||||
|         if(Obj->has(Field) && !Obj->isNull(Field)) { | ||||
|             auto Result = Utils::base64decode(Obj->get(Field).toString()); | ||||
|             Value.assignRaw((const unsigned char *)&Result[0],Result.size()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringPairVec &Vec) { | ||||
| @@ -643,6 +660,27 @@ namespace OpenWifi::RESTAPI_utils { | ||||
|  | ||||
| namespace OpenWifi::Utils { | ||||
|  | ||||
| 	inline void SetThreadName(const char *name) { | ||||
| #ifdef __linux__ | ||||
| 		Poco::Thread::current()->setName(name); | ||||
| 		pthread_setname_np(pthread_self(), name); | ||||
| #endif | ||||
| #ifdef __APPLE__ | ||||
| 	Poco::Thread::current()->setName(name); | ||||
| 	pthread_setname_np(name); | ||||
| #endif | ||||
| 	} | ||||
|  | ||||
| 	inline void SetThreadName(Poco::Thread &thr, const char *name) { | ||||
| #ifdef __linux__ | ||||
| 		thr.setName(name); | ||||
| 		pthread_setname_np(thr.tid(), name); | ||||
| #endif | ||||
| #ifdef __APPLE__ | ||||
| 		thr.setName(name); | ||||
| #endif | ||||
| 	} | ||||
|  | ||||
|     enum MediaTypeEncodings { | ||||
|         PLAIN, | ||||
|         BINARY, | ||||
| @@ -665,6 +703,19 @@ namespace OpenWifi::Utils { | ||||
|         return (std::all_of(UUID.begin(),UUID.end(),[&](auto i){ if(i=='-') dashes++; return i=='-' || std::isxdigit(i);})) && (dashes>0); | ||||
|     } | ||||
|  | ||||
| 	template <typename ...Args> std::string ComputeHash(Args&&... args) { | ||||
| 		Poco::SHA2Engine    E; | ||||
| 		auto as_string = [](auto p) { | ||||
| 			if constexpr(std::is_arithmetic_v<decltype(p)>) { | ||||
| 				return std::to_string(p); | ||||
| 			} else { | ||||
| 				return p; | ||||
| 			} | ||||
| 		}; | ||||
| 		(E.update(as_string(args)),...); | ||||
| 		return Poco::SHA2Engine::digestToHex(E.digest()); | ||||
| 	} | ||||
|  | ||||
|     [[nodiscard]] inline std::vector<std::string> Split(const std::string &List, char Delimiter=',' ) { | ||||
|         std::vector<std::string> ReturnList; | ||||
|  | ||||
| @@ -1167,6 +1218,7 @@ namespace OpenWifi { | ||||
|     static const std::string uSERVICE_SUBCRIBER{ "owsub"}; | ||||
|     static const std::string uSERVICE_INSTALLER{ "owinst"}; | ||||
|     static const std::string uSERVICE_ANALYTICS{ "owanalytics"}; | ||||
| 	static const std::string uSERVICE_OWRRM{ "owrrm"}; | ||||
|  | ||||
| 	class ConfigurationEntry { | ||||
| 	  public: | ||||
| @@ -1315,7 +1367,7 @@ namespace OpenWifi { | ||||
| 		inline void Start(); | ||||
| 		inline void Stop(); | ||||
| 	  private: | ||||
| 		std::atomic_bool 	Running_ = false; | ||||
| 		mutable std::atomic_bool 	Running_ = false; | ||||
| 		Poco::Thread		Thread_; | ||||
| 	}; | ||||
|  | ||||
| @@ -1360,13 +1412,14 @@ namespace OpenWifi { | ||||
|  | ||||
| 	    [[nodiscard]] inline const std::string &Address() const { return address_; }; | ||||
| 	    [[nodiscard]] inline uint32_t Port() const { return port_; }; | ||||
| 	    [[nodiscard]] inline const std::string &KeyFile() const { return key_file_; }; | ||||
| 	    [[nodiscard]] inline const std::string &CertFile() const { return cert_file_; }; | ||||
| 	    [[nodiscard]] inline const std::string &RootCA() const { return root_ca_; }; | ||||
| 	    [[nodiscard]] inline const std::string &KeyFilePassword() const { return key_file_password_; }; | ||||
| 	    [[nodiscard]] inline const std::string &IssuerCertFile() const { return issuer_cert_file_; }; | ||||
| 	    [[nodiscard]] inline const std::string &Name() const { return name_; }; | ||||
| 	    [[nodiscard]] inline auto KeyFile() const { return key_file_; }; | ||||
| 	    [[nodiscard]] inline auto CertFile() const { return cert_file_; }; | ||||
| 	    [[nodiscard]] inline auto RootCA() const { return root_ca_; }; | ||||
| 	    [[nodiscard]] inline auto KeyFilePassword() const { return key_file_password_; }; | ||||
| 	    [[nodiscard]] inline auto IssuerCertFile() const { return issuer_cert_file_; }; | ||||
| 	    [[nodiscard]] inline auto Name() const { return name_; }; | ||||
| 	    [[nodiscard]] inline int Backlog() const { return backlog_; } | ||||
| 		[[nodiscard]] inline auto Cas() const { return cas_; } | ||||
|  | ||||
| 	    [[nodiscard]] inline Poco::Net::SecureServerSocket CreateSecureSocket(Poco::Logger &L) const { | ||||
| 	        Poco::Net::Context::Params P; | ||||
| @@ -1846,7 +1899,8 @@ namespace OpenWifi { | ||||
| 	            Request = &RequestIn; | ||||
| 	            Response = &ResponseIn; | ||||
|  | ||||
| 				Poco::Thread::current()->setName("WebServerThread_" + std::to_string(TransactionId_)); | ||||
| //				std::string th_name = "restsvr_" + std::to_string(TransactionId_); | ||||
| //				Utils::SetThreadName(th_name.c_str()); | ||||
|  | ||||
|                 if(Request->getContentLength()>0) { | ||||
|                     if(Request->getContentType().find("application/json")!=std::string::npos) { | ||||
| @@ -1895,36 +1949,32 @@ namespace OpenWifi { | ||||
| 	    [[nodiscard]] inline bool NeedAdditionalInfo() const { return QB_.AdditionalInfo; } | ||||
| 	    [[nodiscard]] inline const std::vector<std::string> & SelectedRecords() const { return QB_.Select; } | ||||
|  | ||||
| /*	    [[nodiscard]] inline const Poco::JSON::Object::Ptr ParseStream() { | ||||
| 	        return IncomingParser_.parse(Request->stream()).extract<Poco::JSON::Object::Ptr>(); | ||||
| 	    } | ||||
| */ | ||||
| 		inline static bool ParseBindings(const std::string & Request, const std::list<std::string> & EndPoints, BindingMap &bindings) { | ||||
| 			bindings.clear(); | ||||
| 			auto PathItems = Poco::StringTokenizer(Request, "/"); | ||||
|  | ||||
| 	    inline static bool ParseBindings(const std::string & Request, const std::list<std::string> & EndPoints, BindingMap &bindings) { | ||||
| 	        bindings.clear(); | ||||
| 	        std::vector<std::string> PathItems = Utils::Split(Request, '/'); | ||||
| 			for(const auto &EndPoint:EndPoints) { | ||||
| 				auto ParamItems = Poco::StringTokenizer(EndPoint, "/"); | ||||
| 				if (PathItems.count() != ParamItems.count()) | ||||
| 					continue; | ||||
|  | ||||
| 	        for(const auto &EndPoint:EndPoints) { | ||||
| 	            std::vector<std::string> ParamItems = Utils::Split(EndPoint, '/'); | ||||
| 	            if (PathItems.size() != ParamItems.size()) | ||||
| 	                continue; | ||||
|  | ||||
| 	            bool Matched = true; | ||||
| 	            for (size_t i = 0; i != PathItems.size() && Matched; i++) { | ||||
| 	                if (PathItems[i] != ParamItems[i]) { | ||||
| 	                    if (ParamItems[i][0] == '{') { | ||||
| 	                        auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2); | ||||
| 	                        bindings[Poco::toLower(ParamName)] = PathItems[i]; | ||||
| 	                    } else { | ||||
| 	                        Matched = false; | ||||
| 	                    } | ||||
| 	                } | ||||
| 	            } | ||||
| 	            if(Matched) | ||||
| 	                return true; | ||||
| 	        } | ||||
| 	        return false; | ||||
| 	    } | ||||
| 				bool Matched = true; | ||||
| 				for (size_t i = 0; i < PathItems.count(); i++) { | ||||
| 					if (PathItems[i] != ParamItems[i]) { | ||||
| 						if (ParamItems[i][0] == '{') { | ||||
| 							auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2); | ||||
| 							bindings[Poco::toLower(ParamName)] = PathItems[i]; | ||||
| 						} else { | ||||
| 							Matched = false; | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				if(Matched) | ||||
| 					return true; | ||||
| 			} | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 	    inline void PrintBindings() { | ||||
| 	        for (const auto &[key, value] : Bindings_) | ||||
| @@ -2045,6 +2095,17 @@ namespace OpenWifi { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, Poco::Data::BLOB &Value) { | ||||
|             if(O->has(Field)) { | ||||
|                 std::string Content = O->get(Field).toString(); | ||||
|                 auto DecodedBlob = Utils::base64decode(Content); | ||||
|                 Value.assignRaw((const unsigned char *)&DecodedBlob[0],DecodedBlob.size()); | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         template <typename T> bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, const T &value, T & assignee) { | ||||
|             if(O->has(Field)) { | ||||
|                 assignee = value; | ||||
| @@ -2385,6 +2446,7 @@ namespace OpenWifi { | ||||
|             Poco::Net::HTTPServerResponse       *Response= nullptr; | ||||
|             SecurityObjects::UserInfoAndPolicy 	UserInfo_; | ||||
|             QueryBlock					QB_; | ||||
| 			const std::string & Requester() const { return REST_Requester_; } | ||||
| 	    protected: | ||||
| 	        BindingMap 					Bindings_; | ||||
| 	        Poco::URI::QueryParameters 	Parameters_; | ||||
| @@ -2401,6 +2463,7 @@ namespace OpenWifi { | ||||
| 	        RateLimit                   MyRates_; | ||||
|             uint64_t                    TransactionId_; | ||||
|             Poco::JSON::Object::Ptr     ParsedBody_; | ||||
| 			std::string					REST_Requester_; | ||||
| 	    }; | ||||
|  | ||||
| 	    class RESTAPI_UnknownRequestHandler : public RESTAPIHandler { | ||||
| @@ -2582,7 +2645,7 @@ namespace OpenWifi { | ||||
|     private: | ||||
|         std::recursive_mutex  	Mutex_; | ||||
|         Poco::Thread        	Worker_; | ||||
|         std::atomic_bool    	Running_=false; | ||||
|         mutable std::atomic_bool    	Running_=false; | ||||
| 		Poco::NotificationQueue	Queue_; | ||||
|     }; | ||||
|  | ||||
| @@ -2608,7 +2671,7 @@ namespace OpenWifi { | ||||
| 	  private: | ||||
| 		std::recursive_mutex  	Mutex_; | ||||
|         Poco::Thread        	Worker_; | ||||
|         std::atomic_bool    	Running_=false; | ||||
|         mutable std::atomic_bool    	Running_=false; | ||||
|     }; | ||||
|  | ||||
| 	class KafkaDispatcher : public Poco::Runnable { | ||||
| @@ -2665,6 +2728,7 @@ namespace OpenWifi { | ||||
|  | ||||
| 		inline void run() override { | ||||
| 			Poco::AutoPtr<Poco::Notification>	Note(Queue_.waitDequeueNotification()); | ||||
| 			Utils::SetThreadName("kafka:dispatch"); | ||||
| 			while(Note && Running_) { | ||||
| 				auto Msg = dynamic_cast<KafkaMessage*>(Note.get()); | ||||
| 				if(Msg!= nullptr) { | ||||
| @@ -2687,12 +2751,12 @@ namespace OpenWifi { | ||||
| 		} | ||||
|  | ||||
| 	  private: | ||||
| 		std::recursive_mutex  	Mutex_; | ||||
| 		Types::NotifyTable      Notifiers_; | ||||
| 		Poco::Thread        	Worker_; | ||||
| 		std::atomic_bool    	Running_=false; | ||||
| 		uint64_t          		FunctionId_=1; | ||||
| 		Poco::NotificationQueue	Queue_; | ||||
| 		std::recursive_mutex  		Mutex_; | ||||
| 		Types::NotifyTable      	Notifiers_; | ||||
| 		Poco::Thread        		Worker_; | ||||
| 		mutable std::atomic_bool    Running_=false; | ||||
| 		uint64_t          			FunctionId_=1; | ||||
| 		Poco::NotificationQueue		Queue_; | ||||
| 	}; | ||||
|  | ||||
| 	class KafkaManager : public SubSystemServer { | ||||
| @@ -2885,6 +2949,7 @@ namespace OpenWifi { | ||||
|  | ||||
| 	            void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override | ||||
| 	            { | ||||
| 					Utils::SetThreadName("alb-request"); | ||||
| 					try { | ||||
| 						if((id_ % 100) == 0) { | ||||
| 							Logger_.debug(fmt::format("ALB-REQUEST({}): ALB Request {}.", | ||||
| @@ -2953,7 +3018,7 @@ namespace OpenWifi { | ||||
| 	    std::unique_ptr<Poco::Net::HTTPServer>   	Server_; | ||||
| 	    std::unique_ptr<Poco::Net::ServerSocket> 	Socket_; | ||||
| 	    int                                     	Port_ = 0; | ||||
| 	    std::atomic_bool                            Running_=false; | ||||
| 	    mutable std::atomic_bool                            Running_=false; | ||||
| 	}; | ||||
|  | ||||
| 	inline auto ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); } | ||||
| @@ -2973,7 +3038,7 @@ namespace OpenWifi { | ||||
| 	    } | ||||
| 	    int Start() override; | ||||
| 	    inline void Stop() override { | ||||
| 	        Logger().information("Stopping "); | ||||
| 	        Logger().information("Stopping..."); | ||||
| 	        for( const auto & svr : RESTServers_ ) | ||||
| 	            svr->stop(); | ||||
| 			Pool_.stopAll(); | ||||
| @@ -2981,22 +3046,23 @@ namespace OpenWifi { | ||||
| 	        RESTServers_.clear(); | ||||
| 	    } | ||||
|  | ||||
|  | ||||
| 	    inline void reinitialize(Poco::Util::Application &self) override; | ||||
|  | ||||
| 	    inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) { | ||||
| 	        RESTAPIHandler::BindingMap Bindings; | ||||
| 			Poco::Thread::current()->setName(fmt::format("RESTAPI_ExtServer_{}",Id)); | ||||
| 			Utils::SetThreadName(fmt::format("x-rest:{}",Id).c_str()); | ||||
| 	        return RESTAPI_ExtRouter(Path, Bindings, Logger(), Server_, Id); | ||||
| 	    } | ||||
|         const Poco::ThreadPool & Pool() { return Pool_; } | ||||
|  | ||||
| 	private: | ||||
| 	    std::vector<std::unique_ptr<Poco::Net::HTTPServer>>   RESTServers_; | ||||
| 	    Poco::ThreadPool	    Pool_; | ||||
| 	    Poco::ThreadPool	    Pool_{"x-rest",2,32}; | ||||
| 	    RESTAPI_GenericServer   Server_; | ||||
|  | ||||
|         RESTAPI_ExtServer() noexcept: | ||||
| 	    SubSystemServer("RESTAPI_ExtServer", "RESTAPIServer", "openwifi.restapi"), | ||||
|         Pool_("RESTAPI_ExtServer",4,50,120) | ||||
| 	    SubSystemServer("RESTAPI_ExtServer", "REST-XSRV", "openwifi.restapi") | ||||
|             { | ||||
|             } | ||||
| 	}; | ||||
| @@ -3009,7 +3075,7 @@ namespace OpenWifi { | ||||
| 	    inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override { | ||||
| 			try { | ||||
| 				Poco::URI uri(Request.getURI()); | ||||
| 				Poco::Thread::current()->setName(fmt::format("ExtWebServer_{}",TransactionId_)); | ||||
| 				Utils::SetThreadName(fmt::format("x-rest:{}",TransactionId_).c_str()); | ||||
| 				return RESTAPI_ExtServer()->CallServer(uri.getPath(), TransactionId_++); | ||||
| 			} catch (...) { | ||||
|  | ||||
| @@ -3107,7 +3173,7 @@ namespace OpenWifi { | ||||
|  | ||||
| 	    inline int Start() override; | ||||
| 	    inline void Stop() override { | ||||
| 	        Logger().information("Stopping "); | ||||
| 	        Logger().information("Stopping..."); | ||||
| 	        for( const auto & svr : RESTServers_ ) | ||||
| 	            svr->stop(); | ||||
| 			Pool_.stopAll(); | ||||
| @@ -3118,17 +3184,18 @@ namespace OpenWifi { | ||||
|  | ||||
| 	    inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) { | ||||
| 	        RESTAPIHandler::BindingMap Bindings; | ||||
| 			Poco::Thread::current()->setName(fmt::format("RESTAPI_IntServer_{}",Id)); | ||||
| 			Utils::SetThreadName(fmt::format("i-rest:{}",Id).c_str()); | ||||
| 	        return RESTAPI_IntRouter(Path, Bindings, Logger(), Server_, Id); | ||||
| 	    } | ||||
|  | ||||
|         const Poco::ThreadPool & Pool() { return Pool_; } | ||||
| 	private: | ||||
| 	    std::vector<std::unique_ptr<Poco::Net::HTTPServer>>   RESTServers_; | ||||
| 	    Poco::ThreadPool	    Pool_; | ||||
| 	    Poco::ThreadPool	    Pool_{"i-rest",2,16}; | ||||
| 	    RESTAPI_GenericServer   Server_; | ||||
|  | ||||
|         RESTAPI_IntServer() noexcept: | ||||
| 		   SubSystemServer("RESTAPI_IntServer", "REST-ISRV", "openwifi.internal.restapi"), | ||||
|             Pool_("RESTAPI_IntServer",4,50,120) | ||||
| 		   SubSystemServer("RESTAPI_IntServer", "REST-ISRV", "openwifi.internal.restapi") | ||||
|         { | ||||
|         } | ||||
| 	}; | ||||
| @@ -3139,6 +3206,7 @@ namespace OpenWifi { | ||||
| 	public: | ||||
|         inline IntRequestHandlerFactory() = default; | ||||
| 	    inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override { | ||||
| 			Utils::SetThreadName(fmt::format("i-rest:{}",TransactionId_).c_str()); | ||||
| 	        Poco::URI uri(Request.getURI()); | ||||
| 	        return RESTAPI_IntServer()->CallServer(uri.getPath(), TransactionId_); | ||||
| 	    } | ||||
| @@ -3182,7 +3250,6 @@ namespace OpenWifi { | ||||
| 		} | ||||
|  | ||||
| 		[[nodiscard]] std::string Version() { return Version_; } | ||||
| 		// [[nodiscard]] const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; } | ||||
| 		[[nodiscard]] inline const std::string & DataDir() { return DataDir_; } | ||||
| 		[[nodiscard]] inline const std::string & WWWAssetsDir() { return WWWAssetsDir_; } | ||||
| 		[[nodiscard]] bool Debug() const { return DebugMode_; } | ||||
| @@ -3215,7 +3282,12 @@ namespace OpenWifi { | ||||
|             return Poco::Logger::get(Name); | ||||
|         } | ||||
|  | ||||
| 		static inline void Exit(int Reason); | ||||
|         virtual void GetExtraConfiguration(Poco::JSON::Object & Cfg) { | ||||
|             Cfg.set("additionalConfiguration",false); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         static inline void Exit(int Reason); | ||||
| 		inline void BusMessageReceived(const std::string &Key, const std::string & Payload); | ||||
| 		inline MicroServiceMetaVec GetServices(const std::string & Type); | ||||
| 		inline MicroServiceMetaVec GetServices(); | ||||
| @@ -3251,7 +3323,6 @@ namespace OpenWifi { | ||||
| 		inline std::string ConfigPath(const std::string &Key); | ||||
| 		inline std::string Encrypt(const std::string &S); | ||||
| 		inline std::string Decrypt(const std::string &S); | ||||
| 		inline std::string CreateHash(const std::string &S); | ||||
| 		inline std::string MakeSystemEventMessage( const std::string & Type ) const; | ||||
| 		[[nodiscard]] inline bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request); | ||||
| 		inline static void SavePID(); | ||||
| @@ -3281,6 +3352,9 @@ namespace OpenWifi { | ||||
|                 return Signer_.sign(T,Algo); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		inline Poco::ThreadPool & TimerPool() { return TimerPool_; } | ||||
|  | ||||
| 	  private: | ||||
| 	    static MicroService         * instance_; | ||||
| 		bool                        HelpRequested_ = false; | ||||
| @@ -3294,7 +3368,6 @@ namespace OpenWifi { | ||||
| 		std::string                 WWWAssetsDir_; | ||||
| 		Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory(); | ||||
| 		Poco::Crypto::Cipher        * Cipher_ = nullptr; | ||||
| 		Poco::SHA2Engine			SHA2_; | ||||
| 		MicroServiceMetaMap			Services_; | ||||
| 		std::string 				MyHash_; | ||||
| 		std::string 				MyPrivateEndPoint_; | ||||
| @@ -3315,6 +3388,7 @@ namespace OpenWifi { | ||||
|         bool                        NoBuiltInCrypto_=false; | ||||
|         Poco::JWT::Signer	        Signer_; | ||||
| 		Poco::Logger				&Logger_; | ||||
| 		Poco::ThreadPool			TimerPool_{"timer:pool",2,16}; | ||||
|     }; | ||||
|  | ||||
| 	inline void MicroService::Exit(int Reason) { | ||||
| @@ -3464,7 +3538,7 @@ namespace OpenWifi { | ||||
| 	    MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private"); | ||||
| 	    MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public"); | ||||
| 	    UIURI_ = ConfigGetString("openwifi.system.uri.ui"); | ||||
| 	    MyHash_ = CreateHash(MyPublicEndPoint_); | ||||
| 	    MyHash_ = Utils::ComputeHash(MyPublicEndPoint_); | ||||
| 	} | ||||
|  | ||||
| 	void MicroServicePostInitialization(); | ||||
| @@ -3527,7 +3601,7 @@ namespace OpenWifi { | ||||
|     void DaemonPostInitialization(Poco::Util::Application &self); | ||||
|  | ||||
| 	inline void MicroService::initialize(Poco::Util::Application &self) { | ||||
| 	    // add the default services | ||||
| 		// add the default services | ||||
|         LoadConfigurationFile(); | ||||
|         InitializeLoggingSystem(); | ||||
|  | ||||
| @@ -3801,11 +3875,6 @@ namespace OpenWifi { | ||||
| 	    return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; | ||||
| 	} | ||||
|  | ||||
| 	inline std::string MicroService::CreateHash(const std::string &S) { | ||||
| 	    SHA2_.update(S); | ||||
| 	    return Utils::ToHex(SHA2_.digest()); | ||||
| 	} | ||||
|  | ||||
| 	inline std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const { | ||||
| 	    Poco::JSON::Object	Obj; | ||||
| 	    Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type); | ||||
| @@ -3868,6 +3937,7 @@ namespace OpenWifi { | ||||
|             Params->setMaxThreads(50); | ||||
|             Params->setMaxQueued(200); | ||||
|             Params->setKeepAlive(true); | ||||
| 			Params->setName("ws:xrest"); | ||||
|  | ||||
|             std::unique_ptr<Poco::Net::HTTPServer>  NewServer; | ||||
|             if(MicroService::instance().NoAPISecurity()) { | ||||
| @@ -3904,6 +3974,7 @@ namespace OpenWifi { | ||||
|             Params->setMaxThreads(50); | ||||
|             Params->setMaxQueued(200); | ||||
|             Params->setKeepAlive(true); | ||||
| 			Params->setName("ws:irest"); | ||||
|  | ||||
|             std::unique_ptr<Poco::Net::HTTPServer>  NewServer; | ||||
|             if(MicroService::instance().NoAPISecurity()) { | ||||
| @@ -3921,7 +3992,6 @@ namespace OpenWifi { | ||||
|     } | ||||
|  | ||||
|     inline int MicroService::main([[maybe_unused]] const ArgVec &args) { | ||||
|  | ||||
| 	    MyErrorHandler	ErrorHandler(*this); | ||||
| 	    Poco::ErrorHandler::set(&ErrorHandler); | ||||
|  | ||||
| @@ -4028,6 +4098,7 @@ namespace OpenWifi { | ||||
| 	        Port_ = (int)MicroService::instance().ConfigGetInt("alb.port",15015); | ||||
| 	        Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_); | ||||
| 	        auto Params = new Poco::Net::HTTPServerParams; | ||||
| 			Params->setName("ws:alb"); | ||||
| 	        Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger()), *Socket_, Params); | ||||
| 	        Server_->start(); | ||||
| 	    } | ||||
| @@ -4037,6 +4108,7 @@ namespace OpenWifi { | ||||
|  | ||||
|     inline void BusEventManager::run() { | ||||
|         Running_ = true; | ||||
| 		Utils::SetThreadName("fmwk:EventMgr"); | ||||
|         auto Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN); | ||||
|         KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); | ||||
|         while(Running_) { | ||||
| @@ -4122,6 +4194,8 @@ namespace OpenWifi { | ||||
| 	} | ||||
|  | ||||
| 	inline void KafkaProducer::run() { | ||||
|  | ||||
| 		Utils::SetThreadName("Kafka:Prod"); | ||||
| 	    cppkafka::Configuration Config({ | ||||
|             { "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") }, | ||||
|             { "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") } | ||||
| @@ -4160,6 +4234,8 @@ namespace OpenWifi { | ||||
| 	} | ||||
|  | ||||
| 	inline void KafkaConsumer::run() { | ||||
| 		Utils::SetThreadName("Kafka:Cons"); | ||||
|  | ||||
| 	    cppkafka::Configuration Config({ | ||||
| 	        { "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") }, | ||||
| 	        { "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") }, | ||||
| @@ -4298,6 +4374,11 @@ namespace OpenWifi { | ||||
| 	            Answer.set("certificates", Certificates); | ||||
| 	            return ReturnObject(Answer); | ||||
| 	        } | ||||
|             if(GetBoolParameter("extraConfiguration")) { | ||||
|                 Poco::JSON::Object  Answer; | ||||
|                 MicroService::instance().GetExtraConfiguration(Answer); | ||||
|                 return ReturnObject(Answer); | ||||
|             } | ||||
| 	        BadRequest(RESTAPI::Errors::InvalidCommand); | ||||
| 	    } | ||||
|  | ||||
| @@ -4670,6 +4751,7 @@ namespace OpenWifi { | ||||
|     inline bool RESTAPIHandler::IsAuthorized( bool & Expired , [[maybe_unused]] bool & Contacted , bool Sub ) { | ||||
|         if(Internal_ && Request->has("X-INTERNAL-NAME")) { | ||||
|             auto Allowed = MicroService::instance().IsValidAPIKEY(*Request); | ||||
| 			Contacted = true; | ||||
|             if(!Allowed) { | ||||
|                 if(Server_.LogBadTokens(false)) { | ||||
|                     Logger_.debug(fmt::format("I-REQ-DENIED({}): Method={} Path={}", | ||||
| @@ -4678,6 +4760,7 @@ namespace OpenWifi { | ||||
|                 } | ||||
|             } else { | ||||
|                 auto Id = Request->get("X-INTERNAL-NAME", "unknown"); | ||||
| 				REST_Requester_ = Id; | ||||
|                 if(Server_.LogIt(Request->getMethod(),true)) { | ||||
|                     Logger_.debug(fmt::format("I-REQ-ALLOWED({}): User='{}' Method={} Path={}", | ||||
|                                                Utils::FormatIPv6(Request->clientAddress().toString()), Id, | ||||
| @@ -4701,6 +4784,7 @@ namespace OpenWifi { | ||||
| #else | ||||
|             if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, Expired, Contacted, Sub)) { | ||||
| #endif | ||||
| 				REST_Requester_ = UserInfo_.userinfo.email; | ||||
|                 if(Server_.LogIt(Request->getMethod(),true)) { | ||||
|                     Logger_.debug(fmt::format("X-REQ-ALLOWED({}): User='{}@{}' Method={} Path={}", | ||||
|                                                UserInfo_.userinfo.email, | ||||
| @@ -4783,7 +4867,7 @@ namespace OpenWifi { | ||||
|         void run() override; | ||||
|         // MyParallelSocketReactor &ReactorPool(); | ||||
| 		Poco::Net::SocketReactor & Reactor() { return Reactor_; } | ||||
|         void NewClient(Poco::Net::WebSocket &WS, const std::string &Id); | ||||
|         void NewClient(Poco::Net::WebSocket &WS, const std::string &Id, const std::string &UserName); | ||||
|         bool Register(WebSocketClient *Client, const std::string &Id); | ||||
|         void SetProcessor(WebSocketClientProcessor *F); | ||||
|         void UnRegister(const std::string &Id); | ||||
| @@ -4818,8 +4902,8 @@ namespace OpenWifi { | ||||
| 		[[nodiscard]] bool SendToUser(const std::string &userName, const std::string &Payload); | ||||
| 		void SendToAll(const std::string &Payload); | ||||
|     private: | ||||
|         std::atomic_bool Running_ = false; | ||||
|         Poco::Thread Thr_; | ||||
|         mutable std::atomic_bool Running_ = false; | ||||
|         Poco::Thread 								Thr_; | ||||
|         // std::unique_ptr<MyParallelSocketReactor> ReactorPool_; | ||||
| 		Poco::Net::SocketReactor					Reactor_; | ||||
| 		Poco::Thread								ReactorThread_; | ||||
| @@ -4834,18 +4918,22 @@ namespace OpenWifi { | ||||
|  | ||||
|     class WebSocketClient { | ||||
|     public: | ||||
|         explicit WebSocketClient(Poco::Net::WebSocket &WS, const std::string &Id, Poco::Logger &L, | ||||
|                                  WebSocketClientProcessor *Processor); | ||||
|         explicit WebSocketClient(Poco::Net::WebSocket &WS, | ||||
| 							   		const std::string &Id, | ||||
| 							   		const std::string &UserName, | ||||
| 							   		Poco::Logger &L, | ||||
|                                  	WebSocketClientProcessor *Processor); | ||||
|         virtual ~WebSocketClient(); | ||||
|         [[nodiscard]] inline const std::string &Id(); | ||||
|         [[nodiscard]] Poco::Logger &Logger(); | ||||
|         inline bool Send(const std::string &Payload); | ||||
|     private: | ||||
|         std::unique_ptr<Poco::Net::WebSocket> WS_; | ||||
|         Poco::Net::SocketReactor &Reactor_; | ||||
|         std::string Id_; | ||||
|         Poco::Logger &Logger_; | ||||
|         bool Authenticated_ = false; | ||||
|         Poco::Net::SocketReactor 	&Reactor_; | ||||
|         std::string 				Id_; | ||||
| 		std::string					UserName_; | ||||
|         Poco::Logger 				&Logger_; | ||||
|         std::atomic_bool 			Authenticated_ = false; | ||||
|         SecurityObjects::UserInfoAndPolicy UserInfo_; | ||||
|         WebSocketClientProcessor *Processor_ = nullptr; | ||||
|         void OnSocketReadable(const Poco::AutoPtr<Poco::Net::ReadableNotification> &pNf); | ||||
| @@ -4853,33 +4941,9 @@ namespace OpenWifi { | ||||
|         void OnSocketError(const Poco::AutoPtr<Poco::Net::ErrorNotification> &pNf); | ||||
|     }; | ||||
|  | ||||
| /*    inline MyParallelSocketReactor::MyParallelSocketReactor(uint32_t NumReactors) : | ||||
|             NumReactors_(NumReactors) | ||||
|     { | ||||
|         Reactors_ = new Poco::Net::SocketReactor[NumReactors_]; | ||||
|         for(uint32_t i=0;i<NumReactors_;i++) { | ||||
|             ReactorPool_.start(Reactors_[i]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     inline MyParallelSocketReactor::~MyParallelSocketReactor() { | ||||
|         for(uint32_t i=0;i<NumReactors_;i++) { | ||||
|             Reactors_[i].stop(); | ||||
|         } | ||||
|         ReactorPool_.stopAll(); | ||||
|         ReactorPool_.joinAll(); | ||||
|         delete [] Reactors_; | ||||
|     } | ||||
|  | ||||
|     inline Poco::Net::SocketReactor & MyParallelSocketReactor::Reactor() { | ||||
|         return Reactors_[ rand() % NumReactors_ ]; | ||||
|     } | ||||
|  | ||||
|     // inline MyParallelSocketReactor & WebSocketClientServer::ReactorPool() { return *ReactorPool_; } | ||||
| */ | ||||
|     inline void WebSocketClientServer::NewClient(Poco::Net::WebSocket & WS, const std::string &Id) { | ||||
|     inline void WebSocketClientServer::NewClient(Poco::Net::WebSocket & WS, const std::string &Id, const std::string &UserName ) { | ||||
|         std::lock_guard G(Mutex_); | ||||
|         auto Client = new WebSocketClient(WS,Id,Logger(), Processor_); | ||||
|         auto Client = new WebSocketClient(WS,Id,UserName,Logger(), Processor_); | ||||
|         Clients_[Id] = std::make_pair(Client,""); | ||||
|     } | ||||
|  | ||||
| @@ -4909,12 +4973,13 @@ namespace OpenWifi { | ||||
|  | ||||
|     [[nodiscard]] inline bool SendToUser(const std::string &userName, const std::string &Payload); | ||||
|     inline WebSocketClientServer::WebSocketClientServer() noexcept: | ||||
|             SubSystemServer("WebSocketClientServer", "WSCLNT-SVR", "websocketclients") | ||||
|             SubSystemServer("WebSocketClientServer", "UI-WSCLNT-SVR", "websocketclients") | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     inline void WebSocketClientServer::run() { | ||||
|         Running_ = true ; | ||||
| 		Utils::SetThreadName("ws:uiclnt-svr"); | ||||
|         while(Running_) { | ||||
|             Poco::Thread::trySleep(2000); | ||||
|  | ||||
| @@ -4962,8 +5027,12 @@ namespace OpenWifi { | ||||
|  | ||||
|         for(const auto &client:Clients_) { | ||||
|             if(client.second.second == UserName) { | ||||
|                 if(client.second.first->Send(Payload)) | ||||
|                     Sent++; | ||||
| 				try { | ||||
| 					if (client.second.first->Send(Payload)) | ||||
| 						Sent++; | ||||
| 				} catch (...) { | ||||
| 					return false; | ||||
| 				} | ||||
|             } | ||||
|         } | ||||
|         return Sent>0; | ||||
| @@ -4985,70 +5054,73 @@ namespace OpenWifi { | ||||
|         int flags; | ||||
|         int n; | ||||
|         bool Done=false; | ||||
|         Poco::Buffer<char>			IncomingFrame(0); | ||||
|         n = WS_->receiveFrame(IncomingFrame, flags); | ||||
|         auto Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK; | ||||
| 		try { | ||||
| 			Poco::Buffer<char> IncomingFrame(0); | ||||
| 			n = WS_->receiveFrame(IncomingFrame, flags); | ||||
| 			auto Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK; | ||||
|  | ||||
|         if(n==0) { | ||||
|             return delete this; | ||||
|         } | ||||
| 			if (n == 0) { | ||||
| 				Logger().warning(Poco::format("CLOSE(%s): %s UI Client is closing WS connection.", Id_, UserName_)); | ||||
| 				return delete this; | ||||
| 			} | ||||
|  | ||||
|         switch(Op) { | ||||
|             case Poco::Net::WebSocket::FRAME_OP_PING: { | ||||
|                 WS_->sendFrame("", 0, | ||||
|                                (int)Poco::Net::WebSocket::FRAME_OP_PONG | | ||||
|                                (int)Poco::Net::WebSocket::FRAME_FLAG_FIN); | ||||
|             } | ||||
|                 break; | ||||
|             case Poco::Net::WebSocket::FRAME_OP_PONG: { | ||||
|             } | ||||
|                 break; | ||||
|             case Poco::Net::WebSocket::FRAME_OP_CLOSE: { | ||||
|                 Logger().warning(Poco::format("CLOSE(%s): Client is closing its connection.",Id_)); | ||||
|                 Done=true; | ||||
|             } | ||||
|                 break; | ||||
|             case Poco::Net::WebSocket::FRAME_OP_TEXT: { | ||||
|                 IncomingFrame.append(0); | ||||
|                 if(!Authenticated_) { | ||||
|                     std::string Frame{IncomingFrame.begin()}; | ||||
|                     auto Tokens = Utils::Split(Frame,':'); | ||||
|                     bool Expired = false, Contacted = false; | ||||
|                     if(Tokens.size()==2 && AuthClient()->IsAuthorized(Tokens[1], UserInfo_, Expired, Contacted)) { | ||||
|                         Authenticated_=true; | ||||
|                         std::string S{"Welcome! Bienvenue! Bienvenidos!"}; | ||||
|                         WS_->sendFrame(S.c_str(),S.size()); | ||||
|                         WebSocketClientServer()->SetUser(Id_,UserInfo_.userinfo.email); | ||||
|                     } else { | ||||
|                         std::string S{"Invalid token. Closing connection."}; | ||||
|                         WS_->sendFrame(S.c_str(),S.size()); | ||||
|                         Done=true; | ||||
|                     } | ||||
| 			switch (Op) { | ||||
| 			case Poco::Net::WebSocket::FRAME_OP_PING: { | ||||
| 				WS_->sendFrame("", 0, | ||||
| 							   (int)Poco::Net::WebSocket::FRAME_OP_PONG | | ||||
| 								   (int)Poco::Net::WebSocket::FRAME_FLAG_FIN); | ||||
| 			} break; | ||||
| 			case Poco::Net::WebSocket::FRAME_OP_PONG: { | ||||
| 			} break; | ||||
| 			case Poco::Net::WebSocket::FRAME_OP_CLOSE: { | ||||
| 				Logger().warning(Poco::format("CLOSE(%s): %s UI Client is closing WS connection.", Id_, UserName_)); | ||||
| 				Done = true; | ||||
| 			} break; | ||||
| 			case Poco::Net::WebSocket::FRAME_OP_TEXT: { | ||||
| 				IncomingFrame.append(0); | ||||
| 				if (!Authenticated_) { | ||||
| 					std::string Frame{IncomingFrame.begin()}; | ||||
| 					auto Tokens = Utils::Split(Frame, ':'); | ||||
| 					bool Expired = false, Contacted = false; | ||||
| 					if (Tokens.size() == 2 && | ||||
| 						AuthClient()->IsAuthorized(Tokens[1], UserInfo_, Expired, Contacted)) { | ||||
| 						Authenticated_ = true; | ||||
| 						UserName_ = UserInfo_.userinfo.email; | ||||
| 						Logger().warning(Poco::format("START(%s): %s UI Client is starting WS connection.", Id_, UserName_)); | ||||
| 						std::string S{"Welcome! Bienvenue! Bienvenidos!"}; | ||||
| 						WS_->sendFrame(S.c_str(), S.size()); | ||||
| 						WebSocketClientServer()->SetUser(Id_, UserInfo_.userinfo.email); | ||||
| 					} else { | ||||
| 						std::string S{"Invalid token. Closing connection."}; | ||||
| 						WS_->sendFrame(S.c_str(), S.size()); | ||||
| 						Done = true; | ||||
| 					} | ||||
|  | ||||
|                 } else { | ||||
|                     try { | ||||
|                         Poco::JSON::Parser P; | ||||
|                         auto Obj = P.parse(IncomingFrame.begin()) | ||||
|                                 .extract<Poco::JSON::Object::Ptr>(); | ||||
|                         std::string Answer; | ||||
|                         if(Processor_!= nullptr) | ||||
|                             Processor_->Processor(Obj, Answer, Done); | ||||
|                         if (!Answer.empty()) | ||||
|                             WS_->sendFrame(Answer.c_str(), (int) Answer.size()); | ||||
|                         else { | ||||
|                             WS_->sendFrame("{}", 2); | ||||
|                         } | ||||
|                     } catch (const Poco::JSON::JSONException & E) { | ||||
|                         Logger().log(E); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|                 break; | ||||
|             default: | ||||
|             { | ||||
|  | ||||
|             } | ||||
|         } | ||||
| 				} else { | ||||
| 					try { | ||||
| 						Poco::JSON::Parser P; | ||||
| 						auto Obj = | ||||
| 							P.parse(IncomingFrame.begin()).extract<Poco::JSON::Object::Ptr>(); | ||||
| 						std::string Answer; | ||||
| 						if (Processor_ != nullptr) | ||||
| 							Processor_->Processor(Obj, Answer, Done); | ||||
| 						if (!Answer.empty()) | ||||
| 							WS_->sendFrame(Answer.c_str(), (int)Answer.size()); | ||||
| 						else { | ||||
| 							WS_->sendFrame("{}", 2); | ||||
| 						} | ||||
| 					} catch (const Poco::JSON::JSONException &E) { | ||||
| 						Logger().log(E); | ||||
| 						Done=true; | ||||
| 					} | ||||
| 				} | ||||
| 			} break; | ||||
| 			default: { | ||||
| 			} | ||||
| 			} | ||||
| 		} catch (...) { | ||||
| 			Done=true; | ||||
| 		} | ||||
|  | ||||
|         if(Done) { | ||||
|             delete this; | ||||
| @@ -5060,9 +5132,10 @@ namespace OpenWifi { | ||||
|     } | ||||
|  | ||||
|  | ||||
|     inline WebSocketClient::WebSocketClient( Poco::Net::WebSocket & WS , const std::string &Id, Poco::Logger & L, WebSocketClientProcessor * Processor) : | ||||
|     inline WebSocketClient::WebSocketClient( Poco::Net::WebSocket & WS , const std::string &Id, const std::string &UserName, Poco::Logger & L, WebSocketClientProcessor * Processor) : | ||||
|             Reactor_(WebSocketClientServer()->Reactor()), | ||||
|             Id_(Id), | ||||
| 			UserName_(UserName), | ||||
|             Logger_(L), | ||||
|             Processor_(Processor) { | ||||
|         try { | ||||
| @@ -5142,9 +5215,8 @@ namespace OpenWifi { | ||||
|                 try | ||||
|                 { | ||||
|                     Poco::Net::WebSocket WS(*Request, *Response); | ||||
|                     Logger().information("WebSocket connection established."); | ||||
|                     auto Id = MicroService::CreateUUID(); | ||||
|                     WebSocketClientServer()->NewClient(WS,Id); | ||||
|                     WebSocketClientServer()->NewClient(WS,Id,UserInfo_.userinfo.email); | ||||
|                 } | ||||
|                 catch (...) { | ||||
|                     std::cout << "Cannot create websocket client..." << std::endl; | ||||
|   | ||||
| @@ -146,6 +146,10 @@ namespace OpenWifi { | ||||
|         WebSocketClientServer()->SendUserNotification(User,N); | ||||
|     } | ||||
|  | ||||
|     ///// | ||||
|     ///// | ||||
|     ///// | ||||
|  | ||||
|     struct WebSocketNotificationRebootList { | ||||
|         std::string                 title, | ||||
|                 details, | ||||
| @@ -189,5 +193,58 @@ namespace OpenWifi { | ||||
|         WebSocketClientServer()->SendUserNotification(User,N); | ||||
|     } | ||||
|  | ||||
|     ///// | ||||
|     ///// | ||||
|     ///// | ||||
|  | ||||
|     struct WebSocketNotificationUpgradeList { | ||||
|         std::string                 title, | ||||
|                 details, | ||||
|                 jobId; | ||||
|         std::vector<std::string>    success, | ||||
|                                     skipped, | ||||
|                                     no_firmware, | ||||
|                                     not_connected; | ||||
|         uint64_t                    timeStamp=OpenWifi::Now(); | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     typedef WebSocketNotification<WebSocketNotificationUpgradeList> WebSocketClientNotificationVenueUpgradeList_t; | ||||
|  | ||||
|     inline void WebSocketNotificationUpgradeList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         RESTAPI_utils::field_to_json(Obj,"title",title); | ||||
|         RESTAPI_utils::field_to_json(Obj,"jobId",jobId); | ||||
|         RESTAPI_utils::field_to_json(Obj,"success",success); | ||||
|         RESTAPI_utils::field_to_json(Obj,"notConnected",not_connected); | ||||
|         RESTAPI_utils::field_to_json(Obj,"noFirmware",no_firmware); | ||||
|         RESTAPI_utils::field_to_json(Obj,"skipped",skipped); | ||||
|         RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp); | ||||
|         RESTAPI_utils::field_to_json(Obj,"details",details); | ||||
|     } | ||||
|  | ||||
|     inline bool WebSocketNotificationUpgradeList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             RESTAPI_utils::field_from_json(Obj,"title",title); | ||||
|             RESTAPI_utils::field_from_json(Obj,"jobId",jobId); | ||||
|             RESTAPI_utils::field_from_json(Obj,"success",success); | ||||
|             RESTAPI_utils::field_from_json(Obj,"notConnected",not_connected); | ||||
|             RESTAPI_utils::field_from_json(Obj,"noFirmware",no_firmware); | ||||
|             RESTAPI_utils::field_from_json(Obj,"skipped",skipped); | ||||
|             RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp); | ||||
|             RESTAPI_utils::field_from_json(Obj,"details",details); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     inline void WebSocketClientNotificationVenueUpgradeCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpgradeList_t &N) { | ||||
|         N.type = "venue_upgrader"; | ||||
|         WebSocketClientServer()->SendUserNotification(User,N); | ||||
|     } | ||||
|  | ||||
| } // namespace OpenWifi | ||||
|  | ||||
|   | ||||
| @@ -133,6 +133,37 @@ namespace ORM { | ||||
|         return R; | ||||
|     } | ||||
|  | ||||
|     inline std::string WHERE_AND_(std::string Result) { | ||||
|         return Result; | ||||
|     } | ||||
|  | ||||
|     template <typename T, typename... Args> std::string WHERE_AND_(std::string Result, const char *fieldName, const T &Value, Args... args) { | ||||
|         if constexpr(std::is_same_v<T,std::string>) | ||||
|         { | ||||
|             if(!Value.empty()) { | ||||
|                 if(!Result.empty()) | ||||
|                     Result += " and "; | ||||
|                 Result += fieldName; | ||||
|                 Result += '='; | ||||
|                 Result += "'"; | ||||
|                 Result += Escape(Value); | ||||
|                 Result += "'"; | ||||
|             } | ||||
|         } else { | ||||
|             if(!Result.empty()) | ||||
|                 Result += " and "; | ||||
|             Result += fieldName ; | ||||
|             Result += '='; | ||||
|             Result += std::to_string(Value); | ||||
|         } | ||||
|         return WHERE_AND_(Result,args...); | ||||
|     } | ||||
|  | ||||
|     template <typename... Args> std::string WHERE_AND(Args... args) { | ||||
|         std::string Result; | ||||
|         return WHERE_AND_(Result, args...); | ||||
|     } | ||||
|  | ||||
|     enum SqlComparison { EQ = 0, NEQ, LT, LTE, GT, GTE }; | ||||
|     enum SqlBinaryOp { AND = 0 , OR }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										937
									
								
								src/libs/croncpp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										937
									
								
								src/libs/croncpp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,937 @@ | ||||
| /* | ||||
|     MIT License | ||||
|  | ||||
|     Copyright (c) 2018 Marius Bancila | ||||
|  | ||||
|     Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|     of this software and associated documentation files (the "Software"), to deal | ||||
|     in the Software without restriction, including without limitation the rights | ||||
|     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|     copies of the Software, and to permit persons to whom the Software is | ||||
|     furnished to do so, subject to the following conditions: | ||||
|  | ||||
|     The above copyright notice and this permission notice shall be included in all | ||||
|     copies or substantial portions of the Software. | ||||
|  | ||||
|     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|     SOFTWARE. | ||||
|  | ||||
|     This file is from https://github.com/mariusbancila/croncpp.git. | ||||
| */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <sstream> | ||||
| #include <bitset> | ||||
| #include <cctype> | ||||
| #include <ctime> | ||||
| #include <iomanip> | ||||
| #include <algorithm> | ||||
| #include <chrono> | ||||
|  | ||||
| #if __cplusplus > 201402L | ||||
| #include <string_view> | ||||
| #define CRONCPP_IS_CPP17 | ||||
| #endif | ||||
|  | ||||
| namespace cron | ||||
| { | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|    #define  CRONCPP_STRING_VIEW       std::string_view | ||||
|    #define  CRONCPP_STRING_VIEW_NPOS  std::string_view::npos | ||||
|    #define  CRONCPP_CONSTEXPTR        constexpr | ||||
| #else | ||||
|    #define  CRONCPP_STRING_VIEW       std::string const & | ||||
|    #define  CRONCPP_STRING_VIEW_NPOS  std::string::npos | ||||
|    #define  CRONCPP_CONSTEXPTR | ||||
| #endif | ||||
|  | ||||
|    using cron_int  = uint8_t; | ||||
|  | ||||
|    constexpr std::time_t INVALID_TIME = static_cast<std::time_t>(-1); | ||||
|  | ||||
|    constexpr size_t INVALID_INDEX = static_cast<size_t>(-1); | ||||
|  | ||||
|    class cronexpr; | ||||
|  | ||||
|    namespace detail | ||||
|    { | ||||
|       enum class cron_field | ||||
|       { | ||||
|          second, | ||||
|          minute, | ||||
|          hour_of_day, | ||||
|          day_of_week, | ||||
|          day_of_month, | ||||
|          month, | ||||
|          year | ||||
|       }; | ||||
|  | ||||
|       template <typename Traits> | ||||
|       static bool find_next(cronexpr const & cex, | ||||
|                             std::tm& date, | ||||
|                             size_t const dot); | ||||
|    } | ||||
|  | ||||
|    struct bad_cronexpr : public std::runtime_error | ||||
|    { | ||||
|    public: | ||||
|       explicit bad_cronexpr(CRONCPP_STRING_VIEW message) : | ||||
|          std::runtime_error(message.data()) | ||||
|       {} | ||||
|    }; | ||||
|  | ||||
|  | ||||
|    struct cron_standard_traits | ||||
|    { | ||||
|       static const cron_int CRON_MIN_SECONDS = 0; | ||||
|       static const cron_int CRON_MAX_SECONDS = 59; | ||||
|  | ||||
|       static const cron_int CRON_MIN_MINUTES = 0; | ||||
|       static const cron_int CRON_MAX_MINUTES = 59; | ||||
|  | ||||
|       static const cron_int CRON_MIN_HOURS = 0; | ||||
|       static const cron_int CRON_MAX_HOURS = 23; | ||||
|  | ||||
|       static const cron_int CRON_MIN_DAYS_OF_WEEK = 0; | ||||
|       static const cron_int CRON_MAX_DAYS_OF_WEEK = 6; | ||||
|  | ||||
|       static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; | ||||
|       static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; | ||||
|  | ||||
|       static const cron_int CRON_MIN_MONTHS = 1; | ||||
|       static const cron_int CRON_MAX_MONTHS = 12; | ||||
|  | ||||
|       static const cron_int CRON_MAX_YEARS_DIFF = 4; | ||||
|  | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|       static const inline std::vector<std::string> DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; | ||||
|       static const inline std::vector<std::string> MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; | ||||
| #else | ||||
|       static std::vector<std::string>& DAYS() | ||||
|       { | ||||
|          static std::vector<std::string> days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; | ||||
|          return days; | ||||
|       } | ||||
|  | ||||
|       static std::vector<std::string>& MONTHS() | ||||
|       { | ||||
|          static std::vector<std::string> months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; | ||||
|          return months; | ||||
|       } | ||||
| #endif | ||||
|    }; | ||||
|  | ||||
|    struct cron_oracle_traits | ||||
|    { | ||||
|       static const cron_int CRON_MIN_SECONDS = 0; | ||||
|       static const cron_int CRON_MAX_SECONDS = 59; | ||||
|  | ||||
|       static const cron_int CRON_MIN_MINUTES = 0; | ||||
|       static const cron_int CRON_MAX_MINUTES = 59; | ||||
|  | ||||
|       static const cron_int CRON_MIN_HOURS = 0; | ||||
|       static const cron_int CRON_MAX_HOURS = 23; | ||||
|  | ||||
|       static const cron_int CRON_MIN_DAYS_OF_WEEK = 1; | ||||
|       static const cron_int CRON_MAX_DAYS_OF_WEEK = 7; | ||||
|  | ||||
|       static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; | ||||
|       static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; | ||||
|  | ||||
|       static const cron_int CRON_MIN_MONTHS = 0; | ||||
|       static const cron_int CRON_MAX_MONTHS = 11; | ||||
|  | ||||
|       static const cron_int CRON_MAX_YEARS_DIFF = 4; | ||||
|  | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|       static const inline std::vector<std::string> DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; | ||||
|       static const inline std::vector<std::string> MONTHS = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; | ||||
| #else | ||||
|  | ||||
|       static std::vector<std::string>& DAYS() | ||||
|       { | ||||
|          static std::vector<std::string> days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; | ||||
|          return days; | ||||
|       } | ||||
|  | ||||
|       static std::vector<std::string>& MONTHS() | ||||
|       { | ||||
|          static std::vector<std::string> months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; | ||||
|          return months; | ||||
|       } | ||||
| #endif | ||||
|    }; | ||||
|  | ||||
|    struct cron_quartz_traits | ||||
|    { | ||||
|       static const cron_int CRON_MIN_SECONDS = 0; | ||||
|       static const cron_int CRON_MAX_SECONDS = 59; | ||||
|  | ||||
|       static const cron_int CRON_MIN_MINUTES = 0; | ||||
|       static const cron_int CRON_MAX_MINUTES = 59; | ||||
|  | ||||
|       static const cron_int CRON_MIN_HOURS = 0; | ||||
|       static const cron_int CRON_MAX_HOURS = 23; | ||||
|  | ||||
|       static const cron_int CRON_MIN_DAYS_OF_WEEK = 1; | ||||
|       static const cron_int CRON_MAX_DAYS_OF_WEEK = 7; | ||||
|  | ||||
|       static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; | ||||
|       static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; | ||||
|  | ||||
|       static const cron_int CRON_MIN_MONTHS = 1; | ||||
|       static const cron_int CRON_MAX_MONTHS = 12; | ||||
|  | ||||
|       static const cron_int CRON_MAX_YEARS_DIFF = 4; | ||||
|  | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|       static const inline std::vector<std::string> DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; | ||||
|       static const inline std::vector<std::string> MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; | ||||
| #else | ||||
|       static std::vector<std::string>& DAYS() | ||||
|       { | ||||
|          static std::vector<std::string> days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; | ||||
|          return days; | ||||
|       } | ||||
|  | ||||
|       static std::vector<std::string>& MONTHS() | ||||
|       { | ||||
|          static std::vector<std::string> months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; | ||||
|          return months; | ||||
|       } | ||||
| #endif | ||||
|    }; | ||||
|  | ||||
|    class cronexpr; | ||||
|  | ||||
|    template <typename Traits = cron_standard_traits> | ||||
|    static cronexpr make_cron(CRONCPP_STRING_VIEW expr); | ||||
|  | ||||
|    class cronexpr | ||||
|    { | ||||
|       std::bitset<60> seconds; | ||||
|       std::bitset<60> minutes; | ||||
|       std::bitset<24> hours; | ||||
|       std::bitset<7>  days_of_week; | ||||
|       std::bitset<31> days_of_month; | ||||
|       std::bitset<12> months; | ||||
|       std::string     expr; | ||||
|  | ||||
|       friend bool operator==(cronexpr const & e1, cronexpr const & e2); | ||||
|       friend bool operator!=(cronexpr const & e1, cronexpr const & e2); | ||||
|  | ||||
|       template <typename Traits> | ||||
|       friend bool detail::find_next(cronexpr const & cex, | ||||
|                                     std::tm& date, | ||||
|                                     size_t const dot); | ||||
|  | ||||
|       friend std::string to_cronstr(cronexpr const& cex); | ||||
|       friend std::string to_string(cronexpr const & cex); | ||||
|  | ||||
|       template <typename Traits> | ||||
|       friend cronexpr make_cron(CRONCPP_STRING_VIEW expr); | ||||
|    }; | ||||
|  | ||||
|    inline bool operator==(cronexpr const & e1, cronexpr const & e2) | ||||
|    { | ||||
|       return | ||||
|          e1.seconds == e2.seconds && | ||||
|          e1.minutes == e2.minutes && | ||||
|          e1.hours == e2.hours && | ||||
|          e1.days_of_week == e2.days_of_week && | ||||
|          e1.days_of_month == e2.days_of_month && | ||||
|          e1.months == e2.months; | ||||
|    } | ||||
|  | ||||
|    inline bool operator!=(cronexpr const & e1, cronexpr const & e2) | ||||
|    { | ||||
|       return !(e1 == e2); | ||||
|    } | ||||
|  | ||||
|    inline std::string to_string(cronexpr const & cex) | ||||
|    { | ||||
|       return | ||||
|          cex.seconds.to_string() + " " + | ||||
|          cex.minutes.to_string() + " " + | ||||
|          cex.hours.to_string() + " " + | ||||
|          cex.days_of_month.to_string() + " " + | ||||
|          cex.months.to_string() + " " + | ||||
|          cex.days_of_week.to_string(); | ||||
|    } | ||||
|  | ||||
|    inline std::string to_cronstr(cronexpr const& cex) | ||||
|    { | ||||
|       return cex.expr; | ||||
|    } | ||||
|  | ||||
|    namespace utils | ||||
|    { | ||||
|       inline std::time_t tm_to_time(std::tm& date) | ||||
|       { | ||||
|          return std::mktime(&date); | ||||
|       } | ||||
|  | ||||
|       inline std::tm* time_to_tm(std::time_t const * date, std::tm* const out) | ||||
|       { | ||||
| #ifdef _WIN32 | ||||
|          errno_t err = localtime_s(out, date); | ||||
|          return 0 == err ? out : nullptr; | ||||
| #else | ||||
|          return localtime_r(date, out); | ||||
| #endif | ||||
|       } | ||||
|  | ||||
|       inline std::tm to_tm(CRONCPP_STRING_VIEW time) | ||||
|       { | ||||
|          std::tm result; | ||||
| #if __cplusplus > 201103L | ||||
|          std::istringstream str(time.data()); | ||||
|          str.imbue(std::locale(setlocale(LC_ALL, nullptr))); | ||||
|  | ||||
|          str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S"); | ||||
|          if (str.fail()) throw std::runtime_error("Parsing date failed!"); | ||||
| #else | ||||
|          int year = 1900; | ||||
|          int month = 1; | ||||
|          int day = 1; | ||||
|          int hour = 0; | ||||
|          int minute = 0; | ||||
|          int second = 0; | ||||
|          sscanf(time.data(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); | ||||
|          result.tm_year = year - 1900; | ||||
|          result.tm_mon = month - 1; | ||||
|          result.tm_mday = day; | ||||
|          result.tm_hour = hour; | ||||
|          result.tm_min = minute; | ||||
|          result.tm_sec = second; | ||||
| #endif | ||||
|          result.tm_isdst = -1; // DST info not available | ||||
|  | ||||
|          return result; | ||||
|       } | ||||
|  | ||||
|       inline std::string to_string(std::tm const & tm) | ||||
|       { | ||||
| #if __cplusplus > 201103L | ||||
|          std::ostringstream str; | ||||
|          str.imbue(std::locale(setlocale(LC_ALL, nullptr))); | ||||
|          str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); | ||||
|          if (str.fail()) throw std::runtime_error("Writing date failed!"); | ||||
|  | ||||
|          return str.str(); | ||||
| #else | ||||
|          char buff[70] = {0}; | ||||
|          strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &tm); | ||||
|          return std::string(buff); | ||||
| #endif | ||||
|       } | ||||
|  | ||||
|       inline std::string to_upper(std::string text) | ||||
|       { | ||||
|          std::transform(std::begin(text), std::end(text), | ||||
|             std::begin(text), [](char const c) { return static_cast<char>(std::toupper(c)); }); | ||||
|  | ||||
|          return text; | ||||
|       } | ||||
|  | ||||
|       static std::vector<std::string> split(CRONCPP_STRING_VIEW text, char const delimiter) | ||||
|       { | ||||
|          std::vector<std::string> tokens; | ||||
|          std::string token; | ||||
|          std::istringstream tokenStream(text.data()); | ||||
|          while (std::getline(tokenStream, token, delimiter)) | ||||
|          { | ||||
|             tokens.push_back(token); | ||||
|          } | ||||
|          return tokens; | ||||
|       } | ||||
|  | ||||
|       CRONCPP_CONSTEXPTR inline bool contains(CRONCPP_STRING_VIEW text, char const ch) noexcept | ||||
|       { | ||||
|          return CRONCPP_STRING_VIEW_NPOS != text.find_first_of(ch); | ||||
|       } | ||||
|    } | ||||
|  | ||||
|    namespace detail | ||||
|    { | ||||
|  | ||||
|       inline cron_int to_cron_int(CRONCPP_STRING_VIEW text) | ||||
|       { | ||||
|          try | ||||
|          { | ||||
|             return static_cast<cron_int>(std::stoul(text.data())); | ||||
|          } | ||||
|          catch (std::exception const & ex) | ||||
|          { | ||||
|             throw bad_cronexpr(ex.what()); | ||||
|          } | ||||
|       } | ||||
|  | ||||
|       static std::string replace_ordinals( | ||||
|          std::string text, | ||||
|          std::vector<std::string> const & replacement) | ||||
|       { | ||||
|          for (size_t i = 0; i < replacement.size(); ++i) | ||||
|          { | ||||
|             auto pos = text.find(replacement[i]); | ||||
|             if (std::string::npos != pos) | ||||
|                text.replace(pos, 3 ,std::to_string(i)); | ||||
|          } | ||||
|  | ||||
|          return text; | ||||
|       } | ||||
|  | ||||
|       static std::pair<cron_int, cron_int> make_range( | ||||
|          CRONCPP_STRING_VIEW field, | ||||
|          cron_int const minval, | ||||
|          cron_int const maxval) | ||||
|       { | ||||
|          cron_int first = 0; | ||||
|          cron_int last = 0; | ||||
|          if (field.size() == 1 && field[0] == '*') | ||||
|          { | ||||
|             first = minval; | ||||
|             last = maxval; | ||||
|          } | ||||
|          else if (!utils::contains(field, '-')) | ||||
|          { | ||||
|             first = to_cron_int(field); | ||||
|             last = first; | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             auto parts = utils::split(field, '-'); | ||||
|             if (parts.size() != 2) | ||||
|                throw bad_cronexpr("Specified range requires two fields"); | ||||
|  | ||||
|             first = to_cron_int(parts[0]); | ||||
|             last = to_cron_int(parts[1]); | ||||
|          } | ||||
|  | ||||
|          if (first > maxval || last > maxval) | ||||
|          { | ||||
|             throw bad_cronexpr("Specified range exceeds maximum"); | ||||
|          } | ||||
|          if (first < minval || last < minval) | ||||
|          { | ||||
|             throw bad_cronexpr("Specified range is less than minimum"); | ||||
|          } | ||||
|          if (first > last) | ||||
|          { | ||||
|             throw bad_cronexpr("Specified range start exceeds range end"); | ||||
|          } | ||||
|  | ||||
|          return { first, last }; | ||||
|       } | ||||
|  | ||||
|       template <size_t N> | ||||
|       static void set_cron_field( | ||||
|          CRONCPP_STRING_VIEW value, | ||||
|          std::bitset<N>& target, | ||||
|          cron_int const minval, | ||||
|          cron_int const maxval) | ||||
|       { | ||||
|          if(value.length() > 0 && value[value.length()-1] == ',') | ||||
|             throw bad_cronexpr("Value cannot end with comma"); | ||||
|  | ||||
|          auto fields = utils::split(value, ','); | ||||
|          if (fields.empty()) | ||||
|             throw bad_cronexpr("Expression parsing error"); | ||||
|  | ||||
|          for (auto const & field : fields) | ||||
|          { | ||||
|             if (!utils::contains(field, '/')) | ||||
|             { | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|                auto[first, last] = detail::make_range(field, minval, maxval); | ||||
| #else | ||||
|                auto range = detail::make_range(field, minval, maxval); | ||||
|                auto first = range.first; | ||||
|                auto last = range.second; | ||||
| #endif | ||||
|                for (cron_int i = first - minval; i <= last - minval; ++i) | ||||
|                { | ||||
|                   target.set(i); | ||||
|                } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                auto parts = utils::split(field, '/'); | ||||
|                if (parts.size() != 2) | ||||
|                   throw bad_cronexpr("Incrementer must have two fields"); | ||||
|  | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|                auto[first, last] = detail::make_range(parts[0], minval, maxval); | ||||
| #else | ||||
|                auto range = detail::make_range(parts[0], minval, maxval); | ||||
|                auto first = range.first; | ||||
|                auto last = range.second; | ||||
| #endif | ||||
|  | ||||
|                if (!utils::contains(parts[0], '-')) | ||||
|                { | ||||
|                   last = maxval; | ||||
|                } | ||||
|  | ||||
|                auto delta = detail::to_cron_int(parts[1]); | ||||
|                if(delta <= 0) | ||||
|                   throw bad_cronexpr("Incrementer must be a positive value"); | ||||
|  | ||||
|                for (cron_int i = first - minval; i <= last - minval; i += delta) | ||||
|                { | ||||
|                   target.set(i); | ||||
|                } | ||||
|             } | ||||
|          } | ||||
|       } | ||||
|  | ||||
|       template <typename Traits> | ||||
|       static void set_cron_days_of_week( | ||||
|          std::string value, | ||||
|          std::bitset<7>& target) | ||||
|       { | ||||
|          auto days = utils::to_upper(value); | ||||
|          auto days_replaced = detail::replace_ordinals( | ||||
|             days, | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|             Traits::DAYS | ||||
| #else | ||||
|             Traits::DAYS() | ||||
| #endif | ||||
|          ); | ||||
|  | ||||
|          if (days_replaced.size() == 1 && days_replaced[0] == '?') | ||||
|             days_replaced[0] = '*'; | ||||
|  | ||||
|          set_cron_field( | ||||
|             days_replaced, | ||||
|             target, | ||||
|             Traits::CRON_MIN_DAYS_OF_WEEK, | ||||
|             Traits::CRON_MAX_DAYS_OF_WEEK); | ||||
|       } | ||||
|  | ||||
|       template <typename Traits> | ||||
|       static void set_cron_days_of_month( | ||||
|          std::string value, | ||||
|          std::bitset<31>& target) | ||||
|       { | ||||
|          if (value.size() == 1 && value[0] == '?') | ||||
|             value[0] = '*'; | ||||
|  | ||||
|          set_cron_field( | ||||
|             value, | ||||
|             target, | ||||
|             Traits::CRON_MIN_DAYS_OF_MONTH, | ||||
|             Traits::CRON_MAX_DAYS_OF_MONTH); | ||||
|       } | ||||
|  | ||||
|       template <typename Traits> | ||||
|       static void set_cron_month( | ||||
|          std::string value, | ||||
|          std::bitset<12>& target) | ||||
|       { | ||||
|          auto month = utils::to_upper(value); | ||||
|          auto month_replaced = replace_ordinals( | ||||
|             month, | ||||
| #ifdef CRONCPP_IS_CPP17 | ||||
|             Traits::MONTHS | ||||
| #else | ||||
|             Traits::MONTHS() | ||||
| #endif | ||||
|          ); | ||||
|  | ||||
|          set_cron_field( | ||||
|             month_replaced, | ||||
|             target, | ||||
|             Traits::CRON_MIN_MONTHS, | ||||
|             Traits::CRON_MAX_MONTHS); | ||||
|       } | ||||
|  | ||||
|       template <size_t N> | ||||
|       inline size_t next_set_bit( | ||||
|          std::bitset<N> const & target, | ||||
|          size_t /*minimum*/, | ||||
|          size_t /*maximum*/, | ||||
|          size_t offset) | ||||
|       { | ||||
|          for (auto i = offset; i < N; ++i) | ||||
|          { | ||||
|             if (target.test(i)) return i; | ||||
|          } | ||||
|  | ||||
|          return INVALID_INDEX; | ||||
|       } | ||||
|  | ||||
|       inline void add_to_field( | ||||
|          std::tm& date, | ||||
|          cron_field const field, | ||||
|          int const val) | ||||
|       { | ||||
|          switch (field) | ||||
|          { | ||||
|          case cron_field::second: | ||||
|             date.tm_sec += val; | ||||
|             break; | ||||
|          case cron_field::minute: | ||||
|             date.tm_min += val; | ||||
|             break; | ||||
|          case cron_field::hour_of_day: | ||||
|             date.tm_hour += val; | ||||
|             break; | ||||
|          case cron_field::day_of_week: | ||||
|          case cron_field::day_of_month: | ||||
|             date.tm_mday += val; | ||||
|             break; | ||||
|          case cron_field::month: | ||||
|             date.tm_mon += val; | ||||
|             break; | ||||
|          case cron_field::year: | ||||
|             date.tm_year += val; | ||||
|             break; | ||||
|          } | ||||
|  | ||||
|          if (INVALID_TIME == utils::tm_to_time(date)) | ||||
|             throw bad_cronexpr("Invalid time expression"); | ||||
|       } | ||||
|  | ||||
|       inline void set_field( | ||||
|          std::tm& date, | ||||
|          cron_field const field, | ||||
|          int const val) | ||||
|       { | ||||
|          switch (field) | ||||
|          { | ||||
|          case cron_field::second: | ||||
|             date.tm_sec = val; | ||||
|             break; | ||||
|          case cron_field::minute: | ||||
|             date.tm_min = val; | ||||
|             break; | ||||
|          case cron_field::hour_of_day: | ||||
|             date.tm_hour = val; | ||||
|             break; | ||||
|          case cron_field::day_of_week: | ||||
|             date.tm_wday = val; | ||||
|             break; | ||||
|          case cron_field::day_of_month: | ||||
|             date.tm_mday = val; | ||||
|             break; | ||||
|          case cron_field::month: | ||||
|             date.tm_mon = val; | ||||
|             break; | ||||
|          case cron_field::year: | ||||
|             date.tm_year = val; | ||||
|             break; | ||||
|          } | ||||
|  | ||||
|          if (INVALID_TIME == utils::tm_to_time(date)) | ||||
|             throw bad_cronexpr("Invalid time expression"); | ||||
|       } | ||||
|  | ||||
|       inline void reset_field( | ||||
|          std::tm& date, | ||||
|          cron_field const field) | ||||
|       { | ||||
|          switch (field) | ||||
|          { | ||||
|          case cron_field::second: | ||||
|             date.tm_sec = 0; | ||||
|             break; | ||||
|          case cron_field::minute: | ||||
|             date.tm_min = 0; | ||||
|             break; | ||||
|          case cron_field::hour_of_day: | ||||
|             date.tm_hour = 0; | ||||
|             break; | ||||
|          case cron_field::day_of_week: | ||||
|             date.tm_wday = 0; | ||||
|             break; | ||||
|          case cron_field::day_of_month: | ||||
|             date.tm_mday = 1; | ||||
|             break; | ||||
|          case cron_field::month: | ||||
|             date.tm_mon = 0; | ||||
|             break; | ||||
|          case cron_field::year: | ||||
|             date.tm_year = 0; | ||||
|             break; | ||||
|          } | ||||
|  | ||||
|          if (INVALID_TIME == utils::tm_to_time(date)) | ||||
|             throw bad_cronexpr("Invalid time expression"); | ||||
|       } | ||||
|  | ||||
|       inline void reset_all_fields( | ||||
|          std::tm& date, | ||||
|          std::bitset<7> const & marked_fields) | ||||
|       { | ||||
|          for (size_t i = 0; i < marked_fields.size(); ++i) | ||||
|          { | ||||
|             if (marked_fields.test(i)) | ||||
|                reset_field(date, static_cast<cron_field>(i)); | ||||
|          } | ||||
|       } | ||||
|  | ||||
|       inline void mark_field( | ||||
|          std::bitset<7> & orders, | ||||
|          cron_field const field) | ||||
|       { | ||||
|          if (!orders.test(static_cast<size_t>(field))) | ||||
|             orders.set(static_cast<size_t>(field)); | ||||
|       } | ||||
|  | ||||
|       template <size_t N> | ||||
|       static size_t find_next( | ||||
|          std::bitset<N> const & target, | ||||
|          std::tm& date, | ||||
|          unsigned int const minimum, | ||||
|          unsigned int const maximum, | ||||
|          unsigned int const value, | ||||
|          cron_field const field, | ||||
|          cron_field const next_field, | ||||
|          std::bitset<7> const & marked_fields) | ||||
|       { | ||||
|          auto next_value = next_set_bit(target, minimum, maximum, value); | ||||
|          if (INVALID_INDEX == next_value) | ||||
|          { | ||||
|             add_to_field(date, next_field, 1); | ||||
|             reset_field(date, field); | ||||
|             next_value = next_set_bit(target, minimum, maximum, 0); | ||||
|          } | ||||
|  | ||||
|          if (INVALID_INDEX == next_value || next_value != value) | ||||
|          { | ||||
|             set_field(date, field, static_cast<int>(next_value)); | ||||
|             reset_all_fields(date, marked_fields); | ||||
|          } | ||||
|  | ||||
|          return next_value; | ||||
|       } | ||||
|  | ||||
|       template <typename Traits> | ||||
|       static size_t find_next_day( | ||||
|          std::tm& date, | ||||
|          std::bitset<31> const & days_of_month, | ||||
|          size_t day_of_month, | ||||
|          std::bitset<7> const & days_of_week, | ||||
|          size_t day_of_week, | ||||
|          std::bitset<7> const & marked_fields) | ||||
|       { | ||||
|          unsigned int count = 0; | ||||
|          unsigned int maximum = 366; | ||||
|          while ( | ||||
|             (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) || | ||||
|             !days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)) | ||||
|             && count++ < maximum) | ||||
|          { | ||||
|             add_to_field(date, cron_field::day_of_month, 1); | ||||
|  | ||||
|             day_of_month = date.tm_mday; | ||||
|             day_of_week = date.tm_wday; | ||||
|  | ||||
|             reset_all_fields(date, marked_fields); | ||||
|          } | ||||
|  | ||||
|          return day_of_month; | ||||
|       } | ||||
|  | ||||
|       template <typename Traits> | ||||
|       static bool find_next(cronexpr const & cex, | ||||
|                             std::tm& date, | ||||
|                             size_t const dot) | ||||
|       { | ||||
|          bool res = true; | ||||
|  | ||||
|          std::bitset<7> marked_fields{ 0 }; | ||||
|          std::bitset<7> empty_list{ 0 }; | ||||
|  | ||||
|          unsigned int second = date.tm_sec; | ||||
|          auto updated_second = find_next( | ||||
|             cex.seconds, | ||||
|             date, | ||||
|             Traits::CRON_MIN_SECONDS, | ||||
|             Traits::CRON_MAX_SECONDS, | ||||
|             second, | ||||
|             cron_field::second, | ||||
|             cron_field::minute, | ||||
|             empty_list); | ||||
|  | ||||
|          if (second == updated_second) | ||||
|          { | ||||
|             mark_field(marked_fields, cron_field::second); | ||||
|          } | ||||
|  | ||||
|          unsigned int minute = date.tm_min; | ||||
|          auto update_minute = find_next( | ||||
|             cex.minutes, | ||||
|             date, | ||||
|             Traits::CRON_MIN_MINUTES, | ||||
|             Traits::CRON_MAX_MINUTES, | ||||
|             minute, | ||||
|             cron_field::minute, | ||||
|             cron_field::hour_of_day, | ||||
|             marked_fields); | ||||
|          if (minute == update_minute) | ||||
|          { | ||||
|             mark_field(marked_fields, cron_field::minute); | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             res = find_next<Traits>(cex, date, dot); | ||||
|             if (!res) return res; | ||||
|          } | ||||
|  | ||||
|          unsigned int hour = date.tm_hour; | ||||
|          auto updated_hour = find_next( | ||||
|             cex.hours, | ||||
|             date, | ||||
|             Traits::CRON_MIN_HOURS, | ||||
|             Traits::CRON_MAX_HOURS, | ||||
|             hour, | ||||
|             cron_field::hour_of_day, | ||||
|             cron_field::day_of_week, | ||||
|             marked_fields); | ||||
|          if (hour == updated_hour) | ||||
|          { | ||||
|             mark_field(marked_fields, cron_field::hour_of_day); | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             res = find_next<Traits>(cex, date, dot); | ||||
|             if (!res) return res; | ||||
|          } | ||||
|  | ||||
|          unsigned int day_of_week = date.tm_wday; | ||||
|          unsigned int day_of_month = date.tm_mday; | ||||
|          auto updated_day_of_month = find_next_day<Traits>( | ||||
|             date, | ||||
|             cex.days_of_month, | ||||
|             day_of_month, | ||||
|             cex.days_of_week, | ||||
|             day_of_week, | ||||
|             marked_fields); | ||||
|          if (day_of_month == updated_day_of_month) | ||||
|          { | ||||
|             mark_field(marked_fields, cron_field::day_of_month); | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             res = find_next<Traits>(cex, date, dot); | ||||
|             if (!res) return res; | ||||
|          } | ||||
|  | ||||
|          unsigned int month = date.tm_mon; | ||||
|          auto updated_month = find_next( | ||||
|             cex.months, | ||||
|             date, | ||||
|             Traits::CRON_MIN_MONTHS, | ||||
|             Traits::CRON_MAX_MONTHS, | ||||
|             month, | ||||
|             cron_field::month, | ||||
|             cron_field::year, | ||||
|             marked_fields); | ||||
|          if (month != updated_month) | ||||
|          { | ||||
|             if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF) | ||||
|                return false; | ||||
|  | ||||
|             res = find_next<Traits>(cex, date, dot); | ||||
|             if (!res) return res; | ||||
|          } | ||||
|  | ||||
|          return res; | ||||
|       } | ||||
|    } | ||||
|  | ||||
|    template <typename Traits> | ||||
|    static cronexpr make_cron(CRONCPP_STRING_VIEW expr) | ||||
|    { | ||||
|       cronexpr cex; | ||||
|  | ||||
|       if (expr.empty()) | ||||
|          throw bad_cronexpr("Invalid empty cron expression"); | ||||
|  | ||||
|       auto fields = utils::split(expr, ' '); | ||||
|       fields.erase( | ||||
|          std::remove_if(std::begin(fields), std::end(fields), | ||||
|             [](CRONCPP_STRING_VIEW s) {return s.empty(); }), | ||||
|          std::end(fields)); | ||||
|       if (fields.size() != 6) | ||||
|          throw bad_cronexpr("cron expression must have six fields"); | ||||
|  | ||||
|       detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS); | ||||
|       detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES); | ||||
|       detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS); | ||||
|  | ||||
|       detail::set_cron_days_of_week<Traits>(fields[5], cex.days_of_week); | ||||
|  | ||||
|       detail::set_cron_days_of_month<Traits>(fields[3], cex.days_of_month); | ||||
|  | ||||
|       detail::set_cron_month<Traits>(fields[4], cex.months); | ||||
|  | ||||
|       cex.expr = expr; | ||||
|  | ||||
|       return cex; | ||||
|    } | ||||
|  | ||||
|    template <typename Traits = cron_standard_traits> | ||||
|    static std::tm cron_next(cronexpr const & cex, std::tm date) | ||||
|    { | ||||
|       time_t original = utils::tm_to_time(date); | ||||
|       if (INVALID_TIME == original) return {}; | ||||
|  | ||||
|       if (!detail::find_next<Traits>(cex, date, date.tm_year)) | ||||
|          return {}; | ||||
|  | ||||
|       time_t calculated = utils::tm_to_time(date); | ||||
|       if (INVALID_TIME == calculated) return {}; | ||||
|  | ||||
|       if (calculated == original) | ||||
|       { | ||||
|          add_to_field(date, detail::cron_field::second, 1); | ||||
|          if (!detail::find_next<Traits>(cex, date, date.tm_year)) | ||||
|             return {}; | ||||
|       } | ||||
|  | ||||
|       return date; | ||||
|    } | ||||
|  | ||||
|    template <typename Traits = cron_standard_traits> | ||||
|    static std::time_t cron_next(cronexpr const & cex, std::time_t const & date) | ||||
|    { | ||||
|       std::tm val; | ||||
|       std::tm* dt = utils::time_to_tm(&date, &val); | ||||
|       if (dt == nullptr) return INVALID_TIME; | ||||
|  | ||||
|       time_t original = utils::tm_to_time(*dt); | ||||
|       if (INVALID_TIME == original) return INVALID_TIME; | ||||
|  | ||||
|       if(!detail::find_next<Traits>(cex, *dt, dt->tm_year)) | ||||
|          return INVALID_TIME; | ||||
|  | ||||
|       time_t calculated = utils::tm_to_time(*dt); | ||||
|       if (INVALID_TIME == calculated) return calculated; | ||||
|  | ||||
|       if (calculated == original) | ||||
|       { | ||||
|          add_to_field(*dt, detail::cron_field::second, 1); | ||||
|          if(!detail::find_next<Traits>(cex, *dt, dt->tm_year)) | ||||
|             return INVALID_TIME; | ||||
|       } | ||||
|  | ||||
|       return utils::tm_to_time(*dt); | ||||
|    } | ||||
|  | ||||
|   template <typename Traits = cron_standard_traits> | ||||
|   static std::chrono::system_clock::time_point cron_next(cronexpr const & cex, std::chrono::system_clock::time_point const & time_point) { | ||||
|      return std::chrono::system_clock::from_time_t(cron_next<Traits>(cex, std::chrono::system_clock::to_time_t(time_point))); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/ow_version.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/ow_version.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-12-06. | ||||
| // | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace OW_VERSION { | ||||
|     inline static const std::string VERSION{"2.6.0"}; | ||||
|     inline static const std::string BUILD{"128"}; | ||||
|     inline static const std::string HASH{"3b0f2a0"}; | ||||
| } | ||||
| @@ -96,40 +96,42 @@ namespace OpenWifi { | ||||
|     void EntityDB::AddVenues(Poco::JSON::Object &Tree, const std::string & Node) { | ||||
|         ProvObjects::Venue E; | ||||
|         // std::cout << "Adding venue:" << Node << std::endl; | ||||
|         StorageService()->VenueDB().GetRecord("id",Node,E); | ||||
|         Poco::JSON::Array   Venues; | ||||
|         for(const auto &i:E.children) { | ||||
|             Poco::JSON::Object Venue; | ||||
|             AddVenues(Venue, i); | ||||
|             Venues.add(Venue); | ||||
|         if(StorageService()->VenueDB().GetRecord("id",Node,E)) { | ||||
|             Poco::JSON::Array Venues; | ||||
|             for (const auto &i: E.children) { | ||||
|                 Poco::JSON::Object Venue; | ||||
|                 AddVenues(Venue, i); | ||||
|                 Venues.add(Venue); | ||||
|             } | ||||
|             Tree.set("type", "venue"); | ||||
|             Tree.set("name", E.info.name); | ||||
|             Tree.set("uuid", E.info.id); | ||||
|             Tree.set("children", Venues); | ||||
|         } | ||||
|         Tree.set("type","venue"); | ||||
|         Tree.set("name",E.info.name); | ||||
|         Tree.set("uuid",E.info.id); | ||||
|         Tree.set("children",Venues); | ||||
|     } | ||||
|  | ||||
|     void EntityDB::BuildTree(Poco::JSON::Object &Tree, const std::string & Node) { | ||||
|         ProvObjects::Entity E; | ||||
|         // std::cout << "Adding node:" << Node << std::endl; | ||||
|         StorageService()->EntityDB().GetRecord("id",Node,E); | ||||
|         Poco::JSON::Array   Children; | ||||
|         for(const auto &i:E.children) { | ||||
|             Poco::JSON::Object  Child; | ||||
|             BuildTree(Child,i); | ||||
|             Children.add(Child); | ||||
|         if(StorageService()->EntityDB().GetRecord("id",Node,E)) { | ||||
|             Poco::JSON::Array Children; | ||||
|             for (const auto &i: E.children) { | ||||
|                 Poco::JSON::Object Child; | ||||
|                 BuildTree(Child, i); | ||||
|                 Children.add(Child); | ||||
|             } | ||||
|             Poco::JSON::Array Venues; | ||||
|             for (const auto &i: E.venues) { | ||||
|                 Poco::JSON::Object Venue; | ||||
|                 AddVenues(Venue, i); | ||||
|                 Venues.add(Venue); | ||||
|             } | ||||
|             Tree.set("type", "entity"); | ||||
|             Tree.set("name", E.info.name); | ||||
|             Tree.set("uuid", E.info.id); | ||||
|             Tree.set("children", Children); | ||||
|             Tree.set("venues", Venues); | ||||
|         } | ||||
|         Poco::JSON::Array   Venues; | ||||
|         for(const auto &i:E.venues) { | ||||
|             Poco::JSON::Object Venue; | ||||
|             AddVenues(Venue, i); | ||||
|             Venues.add(Venue); | ||||
|         } | ||||
|         Tree.set("type","entity"); | ||||
|         Tree.set("name",E.info.name); | ||||
|         Tree.set("uuid",E.info.id); | ||||
|         Tree.set("children",Children); | ||||
|         Tree.set("venues", Venues); | ||||
|     } | ||||
|  | ||||
|     void EntityDB::ImportVenues(const Poco::JSON::Object::Ptr &O, const std::string &Parent) { | ||||
|   | ||||
| @@ -83,12 +83,13 @@ namespace OpenWifi { | ||||
|     InventoryDB::InventoryDB( OpenWifi::DBType T, Poco::Data::SessionPool & P, Poco::Logger &L) : | ||||
|         DB(T, "inventory", InventoryDB_Fields, InventoryDB_Indexes, P, L, "inv") {} | ||||
|  | ||||
|     bool InventoryDB::CreateFromConnection( const std::string &SerialNumber, | ||||
|     bool InventoryDB::CreateFromConnection( const std::string &SerialNumberRaw, | ||||
|                                             const std::string &ConnectionInfo, | ||||
|                                             const std::string &DeviceType, | ||||
|                                             const std::string &Locale) { | ||||
|  | ||||
|         ProvObjects::InventoryTag   ExistingDevice; | ||||
|         auto SerialNumber = Poco::toLower(SerialNumberRaw); | ||||
|         if(!GetRecord("serialNumber",SerialNumber,ExistingDevice)) { | ||||
|             ProvObjects::InventoryTag   NewDevice; | ||||
|             uint64_t Now = OpenWifi::Now(); | ||||
| @@ -223,7 +224,7 @@ namespace OpenWifi { | ||||
|             ProvObjects::DeviceRules    Rules; | ||||
|             std::string     SerialNumber = Utils::IntToSerialNumber(i); | ||||
|             if(EvaluateDeviceSerialNumberRules(SerialNumber,Rules)) { | ||||
|                 if(Rules.rrm=="yes") | ||||
|                 if(Rules.rrm!="no" && Rules.rrm!="inherit") | ||||
|                     DeviceList.push_back(SerialNumber); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -1,51 +0,0 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-10-28. | ||||
| // | ||||
|  | ||||
| #include "storage_jobs.h" | ||||
| #include "framework/OpenWifiTypes.h" | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     static  ORM::FieldVec    JobDB_Fields{ | ||||
|         // object info | ||||
|         ORM::Field{"id",64, true}, | ||||
|         ORM::Field{"name",ORM::FieldType::FT_TEXT}, | ||||
|         ORM::Field{"description",ORM::FieldType::FT_TEXT}, | ||||
|         ORM::Field{"type",ORM::FieldType::FT_TEXT}, | ||||
|         ORM::Field{"progress",ORM::FieldType::FT_BIGINT}, | ||||
|         ORM::Field{"total",ORM::FieldType::FT_BIGINT}, | ||||
|         ORM::Field{"parameters",ORM::FieldType::FT_TEXT} | ||||
|     }; | ||||
|  | ||||
|     static  ORM::IndexVec    JobDB_Indexes{ | ||||
|         { std::string("job_name_index"), | ||||
|           ORM::IndexEntryVec{ | ||||
|             {std::string("name"), | ||||
|              ORM::Indextype::ASC} } } | ||||
|     }; | ||||
|  | ||||
|     JobDB::JobDB( OpenWifi::DBType T, Poco::Data::SessionPool & P, Poco::Logger &L) : | ||||
|     DB(T, "jobs", JobDB_Fields, JobDB_Indexes, P, L, "job") {} | ||||
| } | ||||
|  | ||||
| template<> void ORM::DB<OpenWifi::JobDBRecordType, OpenWifi::JobRecord>::Convert(const OpenWifi::JobDBRecordType &In, OpenWifi::JobRecord &Out) { | ||||
|     Out.id = In.get<0>(); | ||||
|     Out.name = In.get<1>(); | ||||
|     Out.description = In.get<2>(); | ||||
|     Out.type = In.get<3>(); | ||||
|     Out.progress = In.get<4>(); | ||||
|     Out.total = In.get<5>(); | ||||
|     Out.parameters = OpenWifi::RESTAPI_utils::to_array_of_array_of_object<OpenWifi::Job::Parameter>(In.get<3>()); | ||||
| } | ||||
|  | ||||
| template<> void ORM::DB<OpenWifi::JobDBRecordType, OpenWifi::JobRecord>::Convert(const OpenWifi::JobRecord &In, OpenWifi::JobDBRecordType &Out) { | ||||
|     Out.set<0>(In.id); | ||||
|     Out.set<1>(In.name); | ||||
|     Out.set<2>(In.description); | ||||
|     Out.set<3>(In.type); | ||||
|     Out.set<4>(In.progress); | ||||
|     Out.set<5>(In.total); | ||||
|     Out.set<6>(OpenWifi::RESTAPI_utils::to_string(In.parameters)); | ||||
| } | ||||
| @@ -1,40 +0,0 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-10-28. | ||||
| // | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "framework/orm.h" | ||||
| #include "JobController.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|     typedef Poco::Tuple< | ||||
|         std::string, | ||||
|         std::string, | ||||
|         std::string, | ||||
|         std::string, | ||||
|         uint64_t, | ||||
|         uint64_t, | ||||
|         std::string | ||||
|     > JobDBRecordType; | ||||
|  | ||||
|     struct JobRecord { | ||||
|         Types::UUID_t       id; | ||||
|         std::string         name; | ||||
|         std::string         description; | ||||
|         std::string         type; | ||||
|         uint64_t            progress; | ||||
|         uint64_t            total; | ||||
|         Job::ParametersVec  parameters; | ||||
|  | ||||
|         // void from_string(const std::string &S); | ||||
|         // std::string to_string() const; | ||||
|     }; | ||||
|  | ||||
|     class JobDB : public ORM::DB<JobDBRecordType, JobRecord> { | ||||
|     public: | ||||
|         JobDB( OpenWifi::DBType T, Poco::Data::SessionPool & P, Poco::Logger &L); | ||||
|         virtual ~JobDB() {}; | ||||
|     private: | ||||
|     }; | ||||
| } | ||||
| @@ -97,7 +97,7 @@ if [ -z ${OWPROV_OVERRIDE+x} ]; then | ||||
| 		port="$(echo $hostport | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')" | ||||
| 		path="$(echo $url | grep / | cut -d/ -f2-)" | ||||
| 		export OWPROV=${url} | ||||
| 		echo "Using ${OWPROV}..." | ||||
| 		echo "Using PROV=${OWPROV}..." | ||||
| 	else | ||||
| 		echo "OWPROV endpoint is not found:" | ||||
| 		jq < ${result_file} | ||||
| @@ -108,6 +108,33 @@ else | ||||
| fi | ||||
| } | ||||
|  | ||||
| setrrm() { | ||||
|     if [ -z ${OWRRM_OVERRIDE+x} ]; then | ||||
|     curl  ${FLAGS} -X GET "https://${OWSEC}/api/v1/systemEndpoints" \ | ||||
|         -H "Accept: application/json" \ | ||||
|         -H "Content-Type: application/json" \ | ||||
|         -H "Authorization: Bearer ${token}"  > ${result_file} | ||||
|     rawurl="$(cat ${result_file} | jq -r '.endpoints[] | select( .type == "owrrm" ) | .uri')" | ||||
|     if [[ ! -z "${rawurl}" ]]; then | ||||
|             proto="$(echo $rawurl | grep :// | sed -e's,^\(.*://\).*,\1,g')" | ||||
|     url="$(echo ${rawurl/$proto/})" | ||||
|     user="$(echo $url | grep @ | cut -d@ -f1)" | ||||
|     hostport="$(echo ${url/$user@/} | cut -d/ -f1)" | ||||
|     host="$(echo $hostport | sed -e 's,:.*,,g')" | ||||
|     port="$(echo $hostport | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')" | ||||
|     path="$(echo $url | grep / | cut -d/ -f2-)" | ||||
|     export OWRRM=${url} | ||||
|     echo "Using RRM=${OWRRM}..." | ||||
|     else | ||||
|     echo "OWRRM endpoint is not found:" | ||||
|     jq < ${result_file} | ||||
|     exit 1 | ||||
|     fi | ||||
|     else | ||||
|     export OWRRM=${OWRRM_OVERRIDE} | ||||
|     fi | ||||
| } | ||||
|  | ||||
| logout() { | ||||
|   curl  ${FLAGS} -X DELETE "https://${OWSEC}/api/v1/oauth2/${token}" \ | ||||
|         -H "Content-Type: application/json" \ | ||||
| @@ -499,6 +526,32 @@ listvenues() { | ||||
|     jq < ${result_file} | ||||
| } | ||||
|  | ||||
| getvenuedevices() { | ||||
|     curl    ${FLAGS} -X GET "https://${OWPROV}/api/v1/venue/$1?getDevices=true&getChildren=true" \ | ||||
|         -H "Content-Type: application/json" \ | ||||
|         -H "Authorization: Bearer ${token}" \ | ||||
|         -H "Accept: application/json" > ${result_file} | ||||
|     jq < ${result_file} | ||||
| } | ||||
|  | ||||
| listrrmalgos() { | ||||
|     setrrm | ||||
|     curl    ${FLAGS} -X GET "http://${OWRRM}/api/v1/algorithms" \ | ||||
|         -H "Content-Type: application/json" \ | ||||
|         -H "Authorization: Bearer ${token}" \ | ||||
|         -H "Accept: application/json" > ${result_file} | ||||
|     jq < ${result_file} | ||||
| } | ||||
|  | ||||
| rrmprovider() { | ||||
|     setrrm | ||||
|     curl    ${FLAGS} -X GET "http://${OWRRM}/api/v1/provider" \ | ||||
|         -H "Content-Type: application/json" \ | ||||
|         -H "Authorization: Bearer ${token}" \ | ||||
|         -H "Accept: application/json" > ${result_file} | ||||
|     jq < ${result_file} | ||||
| } | ||||
|  | ||||
| shopt -s nocasematch | ||||
| case "$1" in | ||||
|     "login") login; echo "You are logged in..."  ; logout ;; | ||||
| @@ -546,6 +599,9 @@ case "$1" in | ||||
|     "getsignup") login; getsignup $2; logout;; | ||||
|     "getsubdevs") login; getsubdevs $2; logout;; | ||||
|     "listvenues") login; listvenues $2; logout;; | ||||
|     "getvenuedevices") login; getvenuedevices $2; logout;; | ||||
|     "listrrmalgos") login; listrrmalgos; logout;; | ||||
|     "rrmprovider") login; rrmprovider; logout;; | ||||
|     *) help ;; | ||||
| esac | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user