diff --git a/src/forecaster/dataset.csv b/data/forecaster_data/dataset.csv similarity index 100% rename from src/forecaster/dataset.csv rename to data/forecaster_data/dataset.csv diff --git a/src/forecaster/dataset2.csv b/data/forecaster_data/dataset2.csv similarity index 100% rename from src/forecaster/dataset2.csv rename to data/forecaster_data/dataset2.csv diff --git a/proto/forecaster.proto b/proto/forecaster.proto index 5a4403b01c7f85d6d5b33548d0eaf463e39558cc..dc2de75eaad355a833348687b62686ad9637c93d 100644 --- a/proto/forecaster.proto +++ b/proto/forecaster.proto @@ -18,28 +18,52 @@ package forecaster; import "context.proto"; service ForecasterService { - rpc GetForecastOfTopology (context.TopologyId) returns (Forecast) {} - rpc GetForecastOfLink(context.LinkId) returns (Forecast) {} - rpc CheckService (context.ServiceId) returns (ForecastPrediction) {} + rpc ComputeTopologyForecast(ForecastTopology) returns (ListLinkCapacity) {} + rpc ComputeLinkForecast (context.LinkId ) returns (LinkCapacity ) {} } +message ForecastRequest { + oneof uuid { + context.TopologyId topology_id = 1; + context.LinkId link_id = 2; + } + + context.TopogyId topology_id = 1; + float forecast_window_seconds = 2; +} + + message SingleForecast { context.Timestamp timestamp= 1; double value = 2; } message Forecast { - oneof uuid { - context.TopologyId topologyId= 1; - context.LinkId linkId = 2; - } - repeated SingleForecast forecast = 3; + oneof uuid { + context.TopologyId topologyId= 1; + context.LinkId linkId = 2; + } + repeated SingleForecast forecast = 3; } enum AvailabilityPredictionEnum { - FORECASTED_AVAILABILITY = 0; - FORECASTED_UNAVAILABILITY = 1; + FORECASTED_AVAILABILITY = 0; + FORECASTED_UNAVAILABILITY = 1; } + message ForecastPrediction { - AvailabilityPredictionEnum prediction = 1; + AvailabilityPredictionEnum prediction = 1; +} + + + +message LinkCapacity { + context.LinkId link_id = 1; + float total_capacity_gbps = 2; + float current_capacity_gbps = 3; + float forecasted_capacity_gbps = 4; +} + +message ListLinkCapacity { + repeated LinkCapacity forecasted_link_capacities = 1; } diff --git a/src/forecaster/.gitkeep b/src/forecaster/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/forecaster/.gitlab-ci.yml b/src/forecaster/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..5fed57be6b7963f776425c2196e232aacddee04f --- /dev/null +++ b/src/forecaster/.gitlab-ci.yml @@ -0,0 +1,106 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build, tag, and push the Docker image to the GitLab Docker registry +build device: + variables: + IMAGE_NAME: 'device' # name of the microservice + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: build + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + script: + - docker build -t "$IMAGE_NAME:$IMAGE_TAG" -f ./src/$IMAGE_NAME/Dockerfile . + - docker tag "$IMAGE_NAME:$IMAGE_TAG" "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + - docker push "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + after_script: + - docker images --filter="dangling=true" --quiet | xargs -r docker rmi + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + - changes: + - src/common/**/*.py + - proto/*.proto + - src/$IMAGE_NAME/**/*.{py,in,yml} + - src/$IMAGE_NAME/Dockerfile + - src/$IMAGE_NAME/tests/*.py + - manifests/${IMAGE_NAME}service.yaml + - .gitlab-ci.yml + +# Apply unit test to the component +unit_test device: + variables: + IMAGE_NAME: 'device' # name of the microservice + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: unit_test + needs: + - build device + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + - if docker network list | grep teraflowbridge; then echo "teraflowbridge is already created"; else docker network create -d bridge teraflowbridge; fi + - if docker container ls | grep $IMAGE_NAME; then docker rm -f $IMAGE_NAME; else echo "$IMAGE_NAME image is not in the system"; fi + script: + - docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + - docker run --name $IMAGE_NAME -d -p 2020:2020 -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG + - sleep 5 + - docker ps -a + - docker logs $IMAGE_NAME + - docker exec -i $IMAGE_NAME bash -c "coverage run -m pytest --log-level=INFO --verbose $IMAGE_NAME/tests/test_unitary_emulated.py --junitxml=/opt/results/${IMAGE_NAME}_report.xml" + - docker exec -i $IMAGE_NAME bash -c "coverage report --include='${IMAGE_NAME}/*' --show-missing" + coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' + after_script: + - docker rm -f $IMAGE_NAME + - docker network rm teraflowbridge + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + - changes: + - src/common/**/*.py + - proto/*.proto + - src/$IMAGE_NAME/**/*.{py,in,yml} + - src/$IMAGE_NAME/Dockerfile + - src/$IMAGE_NAME/tests/*.py + - src/$IMAGE_NAME/tests/Dockerfile + - manifests/${IMAGE_NAME}service.yaml + - .gitlab-ci.yml + artifacts: + when: always + reports: + junit: src/$IMAGE_NAME/tests/${IMAGE_NAME}_report.xml + +## Deployment of the service in Kubernetes Cluster +#deploy device: +# variables: +# IMAGE_NAME: 'device' # name of the microservice +# IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) +# stage: deploy +# needs: +# - unit test device +# # - integ_test execute +# script: +# - 'sed -i "s/$IMAGE_NAME:.*/$IMAGE_NAME:$IMAGE_TAG/" manifests/${IMAGE_NAME}service.yaml' +# - kubectl version +# - kubectl get all +# - kubectl apply -f "manifests/${IMAGE_NAME}service.yaml" +# - kubectl get all +# # environment: +# # name: test +# # url: https://example.com +# # kubernetes: +# # namespace: test +# rules: +# - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' +# when: manual +# - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' +# when: manual diff --git a/src/forecaster/Config.py b/src/forecaster/Config.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/forecaster/Config.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/forecaster/Dockerfile b/src/forecaster/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6566625527f8ceaa8de4639d558c92572c4835cb --- /dev/null +++ b/src/forecaster/Dockerfile @@ -0,0 +1,70 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM python:3.9-slim + +# Install dependencies +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install wget g++ git && \ + rm -rf /var/lib/apt/lists/* + +# Set Python to show logs as they occur +ENV PYTHONUNBUFFERED=0 + +# Download the gRPC health probe +RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe + +# Get generic Python packages +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install --upgrade setuptools wheel +RUN python3 -m pip install --upgrade pip-tools + +# Get common Python packages +# Note: this step enables sharing the previous Docker build steps among all the Python components +WORKDIR /var/teraflow +COPY common_requirements.in common_requirements.in +RUN pip-compile --quiet --output-file=common_requirements.txt common_requirements.in +RUN python3 -m pip install -r common_requirements.txt + +# Add common files into working directory +WORKDIR /var/teraflow/common +COPY src/common/. ./ +RUN rm -rf proto + +# Create proto sub-folder, copy .proto files, and generate Python code +RUN mkdir -p /var/teraflow/common/proto +WORKDIR /var/teraflow/common/proto +RUN touch __init__.py +COPY proto/*.proto ./ +RUN python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. *.proto +RUN rm *.proto +RUN find . -type f -exec sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' {} \; + +# Create component sub-folders, get specific Python packages +RUN mkdir -p /var/teraflow/device +WORKDIR /var/teraflow/device +COPY src/device/requirements.in requirements.in +RUN pip-compile --quiet --output-file=requirements.txt requirements.in +RUN python3 -m pip install -r requirements.txt + +# Add component files into working directory +WORKDIR /var/teraflow +COPY src/context/. context/ +COPY src/device/. device/ +COPY src/monitoring/. monitoring/ + +# Start the service +ENTRYPOINT ["python", "-m", "device.service"] diff --git a/src/forecaster/__init__.py b/src/forecaster/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..38d04994fb0fa1951fb465bc127eb72659dc2eaf 100644 --- a/src/forecaster/__init__.py +++ b/src/forecaster/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/forecaster/client/ForecasterClient.py b/src/forecaster/client/ForecasterClient.py new file mode 100644 index 0000000000000000000000000000000000000000..ed33650689547c2ded4e48ebb442215d2887f542 --- /dev/null +++ b/src/forecaster/client/ForecasterClient.py @@ -0,0 +1,82 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, logging +from common.Constants import ServiceNameEnum +from common.Settings import get_service_host, get_service_port_grpc +from common.proto.context_pb2 import Device, DeviceConfig, DeviceId, Empty +from common.proto.device_pb2 import MonitoringSettings +from common.proto.device_pb2_grpc import DeviceServiceStub +from common.tools.client.RetryDecorator import retry, delay_exponential +from common.tools.grpc.Tools import grpc_message_to_json_string + +LOGGER = logging.getLogger(__name__) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') + +class ForecasterClient: + def __init__(self, host=None, port=None): + if not host: host = get_service_host(ServiceNameEnum.DEVICE) + if not port: port = get_service_port_grpc(ServiceNameEnum.DEVICE) + self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) + LOGGER.debug('Creating channel to {:s}...'.format(str(self.endpoint))) + self.channel = None + self.stub = None + self.connect() + LOGGER.debug('Channel created') + + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) + self.stub = DeviceServiceStub(self.channel) + + def close(self): + if self.channel is not None: self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + def AddDevice(self, request : Device) -> DeviceId: + LOGGER.debug('AddDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.AddDevice(request) + LOGGER.debug('AddDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def ConfigureDevice(self, request : Device) -> DeviceId: + LOGGER.debug('ConfigureDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.ConfigureDevice(request) + LOGGER.debug('ConfigureDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def DeleteDevice(self, request : DeviceId) -> Empty: + LOGGER.debug('DeleteDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteDevice(request) + LOGGER.debug('DeleteDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetInitialConfig(self, request : DeviceId) -> DeviceConfig: + LOGGER.debug('GetInitialConfig request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetInitialConfig(request) + LOGGER.debug('GetInitialConfig result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def MonitorDeviceKpi(self, request : MonitoringSettings) -> Empty: + LOGGER.debug('MonitorDeviceKpi request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.MonitorDeviceKpi(request) + LOGGER.debug('MonitorDeviceKpi result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/forecaster/client/__init__.py b/src/forecaster/client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/forecaster/client/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/forecaster/requirements.in b/src/forecaster/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..c81e814603d4c84e0211e3b433fc916b616ecd04 --- /dev/null +++ b/src/forecaster/requirements.in @@ -0,0 +1,42 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +anytree==2.8.0 +APScheduler==3.10.1 +cryptography==36.0.2 +#fastcache==1.1.0 +Jinja2==3.0.3 +ncclient==0.6.13 +p4runtime==1.3.0 +paramiko==2.9.2 +python-json-logger==2.0.2 +#pytz==2021.3 +#redis==4.1.2 +requests==2.27.1 +requests-mock==1.9.3 +xmltodict==0.12.0 +tabulate +ipaddress +macaddress +yattag +pyang +git+https://github.com/robshakir/pyangbind.git +websockets==10.4 + +# pip's dependency resolver does not take into account installed packages. +# p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one +# adding here again grpcio==1.47.* and protobuf==3.20.* with explicit versions to prevent collisions +grpcio==1.47.* +protobuf==3.20.* diff --git a/src/forecaster/service/DeviceService.py b/src/forecaster/service/DeviceService.py new file mode 100644 index 0000000000000000000000000000000000000000..6d27ef96eef4b93fa7d6ca294d1fd645e815af03 --- /dev/null +++ b/src/forecaster/service/DeviceService.py @@ -0,0 +1,41 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common.Constants import ServiceNameEnum +from common.Settings import get_service_port_grpc +from common.proto.device_pb2_grpc import add_DeviceServiceServicer_to_server +from common.tools.service.GenericGrpcService import GenericGrpcService +from .driver_api.DriverInstanceCache import DriverInstanceCache +from .DeviceServiceServicerImpl import DeviceServiceServicerImpl +from .monitoring.MonitoringLoops import MonitoringLoops + +# Custom gRPC settings +# Multiple clients might keep connections alive waiting for RPC methods to be executed. +# Requests needs to be serialized to ensure correct device configurations +GRPC_MAX_WORKERS = 200 + +class DeviceService(GenericGrpcService): + def __init__(self, driver_instance_cache : DriverInstanceCache, cls_name: str = __name__) -> None: + port = get_service_port_grpc(ServiceNameEnum.DEVICE) + super().__init__(port, max_workers=GRPC_MAX_WORKERS, cls_name=cls_name) + self.monitoring_loops = MonitoringLoops() + self.device_servicer = DeviceServiceServicerImpl(driver_instance_cache, self.monitoring_loops) + + def install_servicers(self): + self.monitoring_loops.start() + add_DeviceServiceServicer_to_server(self.device_servicer, self.server) + + def stop(self): + super().stop() + self.monitoring_loops.stop() diff --git a/src/forecaster/service/DeviceServiceServicerImpl.py b/src/forecaster/service/DeviceServiceServicerImpl.py new file mode 100644 index 0000000000000000000000000000000000000000..8b7855be177bbb11b46e8d21809b7f83a0a64c86 --- /dev/null +++ b/src/forecaster/service/DeviceServiceServicerImpl.py @@ -0,0 +1,56 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, logging, os, time +from typing import Dict +from prometheus_client import Histogram +from common.Constants import ServiceNameEnum +from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, get_env_var_name +from common.method_wrappers.Decorator import MetricTypeEnum, MetricsPool, safe_and_metered_rpc_method +from common.method_wrappers.ServiceExceptions import NotFoundException, OperationFailedException +from common.proto.context_pb2 import ( + Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link) +from common.proto.device_pb2 import MonitoringSettings +from common.proto.device_pb2_grpc import DeviceServiceServicer +from common.tools.context_queries.Device import get_device +from common.tools.mutex_queues.MutexQueues import MutexQueues +from context.client.ContextClient import ContextClient + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Device', 'RPC') + +METRICS_POOL_DETAILS = MetricsPool('Device', 'execution', labels={ + 'driver': '', 'operation': '', 'step': '', +}) + +class DeviceServiceServicerImpl(DeviceServiceServicer): + def __init__(self, driver_instance_cache : DriverInstanceCache, monitoring_loops : MonitoringLoops) -> None: + LOGGER.debug('Creating Servicer...') + self.driver_instance_cache = driver_instance_cache + self.monitoring_loops = monitoring_loops + self.mutex_queues = MutexQueues() + LOGGER.debug('Servicer Created') + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def AddDevice(self, request : Device, context : grpc.ServicerContext) -> DeviceId: + return DeviceId() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def ConfigureDevice(self, request : Device, context : grpc.ServicerContext) -> DeviceId: + return DeviceId() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Empty: + return Empty() diff --git a/src/forecaster/service/__init__.py b/src/forecaster/service/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/forecaster/service/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/forecaster/service/__main__.py b/src/forecaster/service/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..84dee26905fc404cf1de96f7a1e11e4a59a034b6 --- /dev/null +++ b/src/forecaster/service/__main__.py @@ -0,0 +1,80 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, signal, sys, threading +from prometheus_client import start_http_server +from common.Constants import ServiceNameEnum +from common.Settings import ( + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, + wait_for_environment_variables) +from .DeviceService import DeviceService +from .driver_api.DriverFactory import DriverFactory +from .driver_api.DriverInstanceCache import DriverInstanceCache, preload_drivers +from .drivers import DRIVERSfff + +terminate = threading.Event() +LOGGER : logging.Logger = None + +def signal_handler(signal, frame): # pylint: disable=redefined-outer-name + LOGGER.warning('Terminate signal received') + terminate.set() + +def main(): + global LOGGER # pylint: disable=global-statement + + log_level = get_log_level() + logging.basicConfig(level=log_level, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") + logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING) + logging.getLogger('apscheduler.scheduler').setLevel(logging.WARNING) + logging.getLogger('monitoring-client').setLevel(logging.WARNING) + LOGGER = logging.getLogger(__name__) + + wait_for_environment_variables([ + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + ]) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + LOGGER.info('Starting...') + + # Start metrics server + metrics_port = get_metrics_port() + start_http_server(metrics_port) + + # Initialize Driver framework + driver_factory = DriverFactory(DRIVERS) + driver_instance_cache = DriverInstanceCache(driver_factory) + + # Starting device service + grpc_service = DeviceService(driver_instance_cache) + grpc_service.start() + + # Initialize drivers with existing devices in context + LOGGER.info('Pre-loading drivers...') + preload_drivers(driver_instance_cache) + + # Wait for Ctrl+C or termination signal + while not terminate.wait(timeout=1.0): pass + + LOGGER.info('Terminating...') + grpc_service.stop() + driver_instance_cache.terminate() + + LOGGER.info('Bye') + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/forecaster/tests/__init__.py b/src/forecaster/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/forecaster/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/forecaster/tests/test_unitary.py b/src/forecaster/tests/test_unitary.py new file mode 100644 index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf --- /dev/null +++ b/src/forecaster/tests/test_unitary.py @@ -0,0 +1,13 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License.