diff --git a/manifests/ztp_server.yaml b/manifests/ztp_server.yaml new file mode 100644 index 0000000000000000000000000000000000000000..398aaa71eaf2915a4ef5da42c86b677e6c381648 --- /dev/null +++ b/manifests/ztp_server.yaml @@ -0,0 +1,77 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ztp_serverservice +spec: + selector: + matchLabels: + app: ztp_serverservice + #replicas: 1 + template: + metadata: + labels: + app: ztp_serverservice + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: labs.etsi.org:5050/tfs/controller/ztp_server:latest + imagePullPolicy: Always + ports: + - containerPort: 8005 + - containerPort: 5051 + - containerPort: 9192 + env: + - name: LOG_LEVEL + value: "INFO" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5051"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5051"] + resources: + requests: + cpu: 250m + memory: 128Mi + limits: + cpu: 1000m + memory: 1024Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: ztp_serverservice + labels: + app: ztp_serverservice +spec: + type: ClusterIP + selector: + app: ztp_serverservice + ports: + - name: http + protocol: TCP + port: 8005 + targetPort: 8005 + - name: grpc + protocol: TCP + port: 5051 + targetPort: 5051 + - name: metrics + protocol: TCP + port: 9192 + targetPort: 9192 diff --git a/proto/ztp_server.proto b/proto/ztp_server.proto new file mode 100755 index 0000000000000000000000000000000000000000..31e568d3a833c534b9fa7b4531700bffb35505b0 --- /dev/null +++ b/proto/ztp_server.proto @@ -0,0 +1,41 @@ +// Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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. + +syntax = "proto3"; +package ztpServer; + +//import "context.proto"; + +service ZtpServerService { + rpc GetProvisioningScript (ProvisioningScriptName ) returns (ProvisioningScript ) {} + rpc GetZtpProvisioning (ZtpFileName ) returns (ZtpFile ) {} +} + + +// Define the request message for both methods +message ProvisioningScriptName { + string scriptname = 1; +} + +message ZtpFileName { + string filename = 1; +} + +message ProvisioningScript { + string script = 1; +} + +message ZtpFile { + string json = 1; +} diff --git a/src/common/Constants.py b/src/common/Constants.py index 8b5b714b572d84a7a2c57df0047818d058218a9a..a1ace0f065f6a5f84837921897fc8158de27a902 100644 --- a/src/common/Constants.py +++ b/src/common/Constants.py @@ -42,6 +42,7 @@ class ServiceNameEnum(Enum): SERVICE = 'service' SLICE = 'slice' ZTP = 'ztp' + ZTP_SERVER = 'ztp-server' POLICY = 'policy' MONITORING = 'monitoring' DLT = 'dlt' @@ -85,6 +86,7 @@ DEFAULT_SERVICE_GRPC_PORTS = { ServiceNameEnum.SERVICE .value : 3030, ServiceNameEnum.SLICE .value : 4040, ServiceNameEnum.ZTP .value : 5050, + ServiceNameEnum.ZTP_SERVER .value : 5051, ServiceNameEnum.POLICY .value : 6060, ServiceNameEnum.MONITORING .value : 7070, ServiceNameEnum.DLT .value : 8080, @@ -122,8 +124,9 @@ DEFAULT_SERVICE_GRPC_PORTS = { # Default HTTP/REST-API service ports DEFAULT_SERVICE_HTTP_PORTS = { - ServiceNameEnum.NBI .value : 8080, - ServiceNameEnum.WEBUI.value : 8004, + ServiceNameEnum.NBI .value : 8080, + ServiceNameEnum.WEBUI.value : 8004, + ServiceNameEnum.ZTP_SERVER.value : 8005, } # Default HTTP/REST-API service base URLs diff --git a/src/ztp_server/Config.py b/src/ztp_server/Config.py new file mode 100755 index 0000000000000000000000000000000000000000..83a35005855d60b714259c6b671c7210cf14331d --- /dev/null +++ b/src/ztp_server/Config.py @@ -0,0 +1,20 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 werkzeug.security import generate_password_hash + +# REST-API users +RESTAPI_USERS = { # TODO: implement a database of credentials and permissions + 'admin': generate_password_hash('admin'), +} diff --git a/src/ztp_server/Dockerfile b/src/ztp_server/Dockerfile new file mode 100755 index 0000000000000000000000000000000000000000..bbcc282bd27c26959f08f7a8c55477a68f35994f --- /dev/null +++ b/src/ztp_server/Dockerfile @@ -0,0 +1,72 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 build-essential cmake libpcre2-dev python3-dev python3-cffi && \ + 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/ztp_server +WORKDIR /var/teraflow/ztp_server +COPY src/ztp_server/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/ztp_server/. ztp_server/ + +#ToDo Implement Test +#RUN mkdir -p /var/teraflow/tests/tools +#COPY src/tests/tools/mock_osm/. tests/tools/mock_osm/ + +# Start the service +ENTRYPOINT ["python", "-m", "ztp_server.service"] diff --git a/src/ztp_server/__init__.py b/src/ztp_server/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14 --- /dev/null +++ b/src/ztp_server/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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/ztp_server/client/ZtpClient.py b/src/ztp_server/client/ZtpClient.py new file mode 100755 index 0000000000000000000000000000000000000000..9e14bd694c6789828c515ec0f339ad2bbb35957c --- /dev/null +++ b/src/ztp_server/client/ZtpClient.py @@ -0,0 +1,60 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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.ztp_server_pb2_grpc import ZtpServerServiceStub +from common.proto.ztp_server_pb2 import ProvisioningScriptName, ProvisioningScript, ZtpFileName, ZtpFile +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 ZtpClient: + def __init__(self, host=None, port=None): + if not host: host = get_service_host(ServiceNameEnum.ZTP_SERVER) + if not port: port = get_service_port_grpc(ServiceNameEnum.ZTP_SERVER) + 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 = ZtpServerServiceStub(self.channel) + + def close(self): + if self.channel is not None: self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + def GetProvisioningScript(self, request : ProvisioningScriptName) -> ProvisioningScript: + LOGGER.debug('GetProvisioningScript request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetProvisioningScript(request) + LOGGER.debug('GetProvisioningScript result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetZtpProvisioning(self, request : ZtpFileName) -> ZtpFile: + LOGGER.debug('GetZtpProvisioning request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetZtpProvisioning(request) + LOGGER.debug('GetZtpProvisioning result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/ztp_server/client/__init__.py b/src/ztp_server/client/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14 --- /dev/null +++ b/src/ztp_server/client/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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/ztp_server/data/provisioning_script_sonic.sh b/src/ztp_server/data/provisioning_script_sonic.sh new file mode 100644 index 0000000000000000000000000000000000000000..24b1eb8a669c47d1ab40c847c110609c32087dbe --- /dev/null +++ b/src/ztp_server/data/provisioning_script_sonic.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 + + +PRODUCT_NAME=$(show version | grep 'Platform' | awk '{print $2}') +SERIAL_NUMBER=$(show version | grep 'Serial Number' | awk '{print $3}') +BASE_MAC_ADDRESS=$(show platform syseeprom | grep 'Base MAC Address' | awk '{print $6}') +SONIC_VERSION=$(show version | grep 'SONiC Software Version' | awk '{print $4}') + +# URL of the config_db.json file +CONFIG_DB_URL="http://10.1.1.119:9001/config/config_db.json" + +# Directory where the file will be saved +DEST_DIR="/etc/sonic" +DEST_FILE="$DEST_DIR/config_db.json" + +# Download the config_db.json file +curl -o $DEST_FILE -H "User-Agent: SONiC-ZTP/0.1" \ + -H "PRODUCT-NAME: $PRODUCT_NAME" \ + -H "SERIAL-NUMBER: $SERIAL_NUMBER" \ + -H "BASE-MAC-ADDRESS: $BASE_MAC_ADDRESS" \ + -H "SONiC-VERSION: $SONIC_VERSION" \ + $CONFIG_DB_URL +if [ $? -ne 0 ]; then + logger "Error: Failed to download the file from $CONFIG_DB_URL" + exit 1 +fi + +# Reload the configuration database +sudo config reload -y + +# Check if the reload was successful +if [ $? -eq 0 ]; then + logger "The configuration database reloaded successfully." +else + logger "Error: Failed to reload the configuration database." + exit 1 +fi + +logger "Plugin executed successfully." diff --git a/src/ztp_server/data/ztp.json b/src/ztp_server/data/ztp.json new file mode 100644 index 0000000000000000000000000000000000000000..4a4cdc3cf032fbe4ab3a174391da9b45fae72de5 --- /dev/null +++ b/src/ztp_server/data/ztp.json @@ -0,0 +1,10 @@ +{ + "ztp": { + "01-provisioning-script": { + "plugin": { + "url": "http://localhost:9001/provisioning/provisioning_script_sonic.sh" + }, + "reboot-on-success": true + } + } + } diff --git a/src/ztp_server/requirements.in b/src/ztp_server/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..d56ee821b19894e435368cd6250b25d3bdc33c10 --- /dev/null +++ b/src/ztp_server/requirements.in @@ -0,0 +1,28 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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. + +deepdiff==6.7.* +deepmerge==1.1.* +Flask==2.1.3 +Flask-HTTPAuth==4.5.0 +Flask-RESTful==0.3.9 +jsonschema==4.4.0 +libyang==2.8.4 +netaddr==0.9.0 +pyang==2.6.0 +git+https://github.com/robshakir/pyangbind.git +pydantic==2.6.3 +requests==2.27.1 +werkzeug==2.3.7 +websockets==12.0 diff --git a/src/ztp_server/service/ZtpServerService.py b/src/ztp_server/service/ZtpServerService.py new file mode 100755 index 0000000000000000000000000000000000000000..c099cf7f6579b7f885c3a7cdc863170888252efa --- /dev/null +++ b/src/ztp_server/service/ZtpServerService.py @@ -0,0 +1,28 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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.ztp_server_pb2_grpc import add_ZtpServerServiceServicer_to_server +from common.tools.service.GenericGrpcService import GenericGrpcService +from ztp_server.service.ZtpServerServiceServicerImpl import ZtpServerServiceServicerImpl + +class ZtpServerService(GenericGrpcService): + def __init__(self, cls_name: str = __name__) -> None: + port = get_service_port_grpc(ServiceNameEnum.ZTP_SERVER) + super().__init__(port, cls_name=cls_name) + self.ztp_servicer = ZtpServerServiceServicerImpl() + + def install_servicers(self): + add_ZtpServerServiceServicer_to_server(self.ztp_servicer, self.server) diff --git a/src/ztp_server/service/ZtpServerServiceServicerImpl.py b/src/ztp_server/service/ZtpServerServiceServicerImpl.py new file mode 100755 index 0000000000000000000000000000000000000000..62ddb856ff57a0d20689fb532f59883c833086c4 --- /dev/null +++ b/src/ztp_server/service/ZtpServerServiceServicerImpl.py @@ -0,0 +1,40 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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, json, os +from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method +from common.proto.ztp_server_pb2 import ProvisioningScriptName, ProvisioningScript, ZtpFileName, ZtpFile +from common.proto.ztp_server_pb2_grpc import ZtpServerServiceServicer + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('ZTP_SERVER', 'RPC') + + +class ZtpServerServiceServicerImpl(ZtpServerServiceServicer): + def __init__(self): + LOGGER.info('Creating Servicer...') + LOGGER.info('Servicer Created') + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetZtpProvisioning(self, request : ProvisioningScriptName, context : grpc.ServicerContext) -> ProvisioningScript: + try: + provisioningPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'data', request.scriptname) + with open(provisioningPath, 'r') as provisioning_file: + provisioning_content = provisioning_file.read() + return ProvisioningScript(script=provisioning_content) + except FileNotFoundError: + context.set_code(grpc.StatusCode.NOT_FOUND) + context.set_details('File not found') + return ProvisioningScript() diff --git a/src/ztp_server/service/__init__.py b/src/ztp_server/service/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14 --- /dev/null +++ b/src/ztp_server/service/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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/ztp_server/service/__main__.py b/src/ztp_server/service/__main__.py new file mode 100755 index 0000000000000000000000000000000000000000..ae8a95d21089f4193185febe8e23b1019e2b6aa4 --- /dev/null +++ b/src/ztp_server/service/__main__.py @@ -0,0 +1,77 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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, +) +from .ZtpServerService import ZtpServerService +from .rest_server.RestServer import RestServer +from .rest_server.ztpServer_plugins.ztp_provisioning_api import register_ztp_provisioning + +terminate = threading.Event() +LOGGER = None + +def signal_handler(signal, frame): # pylint: disable=redefined-outer-name, unused-argument + 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) + LOGGER = logging.getLogger(__name__) + + 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) + + # Starting ZtpServer service + grpc_service = ZtpServerService() + grpc_service.start() + + rest_server = RestServer() + register_ztp_provisioning(rest_server) + rest_server.start() + + LOGGER.debug('Configured Resources:') + for resource in rest_server.api.resources: + LOGGER.debug(' - {:s}'.format(str(resource))) + + LOGGER.debug('Configured Rules:') + for rule in rest_server.app.url_map.iter_rules(): + LOGGER.debug(' - {:s}'.format(str(rule))) + + # Wait for Ctrl+C or termination signal + while not terminate.wait(timeout=1.0): pass + + LOGGER.info('Terminating...') + grpc_service.stop() + rest_server.shutdown() + rest_server.join() + + LOGGER.info('Bye') + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/ztp_server/service/rest_server/RestServer.py b/src/ztp_server/service/rest_server/RestServer.py new file mode 100755 index 0000000000000000000000000000000000000000..0c4e45a8ca7db6ab4b8cf0cd6ac9c5618153a10a --- /dev/null +++ b/src/ztp_server/service/rest_server/RestServer.py @@ -0,0 +1,23 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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_baseurl_http, get_service_port_http +from common.tools.service.GenericRestServer import GenericRestServer + +class RestServer(GenericRestServer): + def __init__(self, cls_name: str = __name__) -> None: + bind_port = get_service_port_http(ServiceNameEnum.ZTP_SERVER) + base_url = get_service_baseurl_http(ServiceNameEnum.ZTP_SERVER) + super().__init__(bind_port, base_url, cls_name=cls_name) diff --git a/src/ztp_server/service/rest_server/__init__.py b/src/ztp_server/service/rest_server/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14 --- /dev/null +++ b/src/ztp_server/service/rest_server/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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/ztp_server/service/rest_server/ztpServer_plugins/__init__.py b/src/ztp_server/service/rest_server/ztpServer_plugins/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14 --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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/ztp_server/service/rest_server/ztpServer_plugins/tools/Authentication.py b/src/ztp_server/service/rest_server/ztpServer_plugins/tools/Authentication.py new file mode 100755 index 0000000000000000000000000000000000000000..aab239f365bd3450aa363eb098e64e1c2c8fd8d6 --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/tools/Authentication.py @@ -0,0 +1,25 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 flask_httpauth import HTTPBasicAuth +from werkzeug.security import check_password_hash +from ztp_server.Config import RESTAPI_USERS + +HTTP_AUTH = HTTPBasicAuth() + +@HTTP_AUTH.verify_password +def verify_password(username, password): + if username not in RESTAPI_USERS: return None + if not check_password_hash(RESTAPI_USERS[username], password): return None + return username diff --git a/src/ztp_server/service/rest_server/ztpServer_plugins/tools/HttpStatusCodes.py b/src/ztp_server/service/rest_server/ztpServer_plugins/tools/HttpStatusCodes.py new file mode 100755 index 0000000000000000000000000000000000000000..f9d749613b8f9fa2350d9118f32c4e83a130893d --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/tools/HttpStatusCodes.py @@ -0,0 +1,20 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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. + +HTTP_OK = 200 +HTTP_CREATED = 201 +HTTP_NOCONTENT = 204 +HTTP_BADREQUEST = 400 +HTTP_SERVERERROR = 500 +HTTP_GATEWAYTIMEOUT = 504 diff --git a/src/ztp_server/service/rest_server/ztpServer_plugins/tools/__init__.py b/src/ztp_server/service/rest_server/ztpServer_plugins/tools/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..53d5157f750bfb085125cbd33faff1cec5924e14 --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/tools/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py new file mode 100755 index 0000000000000000000000000000000000000000..f4169742aabc0f5bf9d1b9af4acbd0f545b62e5a --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Resources.py @@ -0,0 +1,37 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 json +import logging +from flask.json import jsonify +from flask_restful import Resource, request +from common.tools.grpc.Tools import grpc_message_to_json +from ztp_server.service.rest_server.ztpServer_plugins.tools.Authentication import HTTP_AUTH + +from .Tools import ( + returnConfigFile +) + +LOGGER = logging.getLogger(__name__) + +class _Resource(Resource): + def __init__(self) -> None: + super().__init__() + +class config(_Resource): + @HTTP_AUTH.login_required + def get(self, config_db : str): + content = returnConfigFile(config_db) + + return content diff --git a/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Tools.py b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Tools.py new file mode 100755 index 0000000000000000000000000000000000000000..92573fd2e0f13e7982856a31d20345a28724adca --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/Tools.py @@ -0,0 +1,25 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 os + +def returnConfigFile(config_db): + try: + configFilePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..','..','..','..','..', 'data', config_db) + with open(configFilePath, 'r', encoding='utf-8') as configFile: + content = configFile.read() + except FileNotFoundError: + return None + + return content diff --git a/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..ab06fd7bdac53a3441e8d873626932eee0a58a95 --- /dev/null +++ b/src/ztp_server/service/rest_server/ztpServer_plugins/ztp_provisioning_api/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (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 ztp_server.service.rest_server.RestServer import RestServer +from .Resources import ( + Config +) + +URL_PREFIX = '/provisioning' + +# Use 'path' type since some identifiers might contain char '/' and Flask is unable to recognize them in 'string' type. +RESOURCES = [ + # (endpoint_name, resource_class, resource_url) + ('api.config', Config, '/config/<path:config_db>'), +] + +def register_ztp_provisioning(rest_server : RestServer): + for endpoint_name, resource_class, resource_url in RESOURCES: + rest_server.add_resource(resource_class, URL_PREFIX + resource_url, endpoint=endpoint_name)