diff --git a/operators/o2ims-operator/README.md b/operators/o2ims-operator/README.md index fe9be96a..99fc4f9e 100644 --- a/operators/o2ims-operator/README.md +++ b/operators/o2ims-operator/README.md @@ -126,6 +126,20 @@ kind delete cluster -n edge O2IMS operator listens for ProvisioningRequest CR and once it is created it goes through different stages +Following are the Provisioning Request Phases: + + + +| Status | Description | +| --- | --- | +| `PENDING` | The ProvisioningRequest is waiting to be processed by the O-Cloud (IMS). | +| `PROGRESSING` | The O-Cloud (IMS) is processing the ProvisioningRequest and executing the actions to fulfill it. | +| `FULFILLED` | The ProvisioningRequest has been successfully processed and completed by the O-Cloud (IMS). | +| `FAILED` | The ProvisioningRequest could not be fully processed by the O-Cloud (IMS). | +| `DELETING` | The ProvisioningRequest is in the process of being deleted by the O-Cloud (IMS). | + + + 1. `ProvisioningRequest validation`: The controller [provisioning_request_validation_controller.py](./controllers/provisioning_request_validation_controller.py) validates the provisioning requests. Currently it checks if the field `clusterName` and `clusterProvisioner`. At the moment only `capi` handled clusters are support 2. `ProvisioningRequest creation`: The controller [provisioning_request_controller.py](./controllers/provisioning_request_controller.py) takes care of creating the a package variant for Porch which can be applied to the cluster where porch is running. After applying package variant it waits for the cluster to be created and it follows the creation via querying `clusters.cluster.x-k8s.io` endpoint. Later we will add querying of packageRevisions also but at the moment their is a problem with querying packageRevisions because sometimes Porch is not able to process the request @@ -278,3 +292,4 @@ do kpt alpha rpkg delete $pkg -ndefault done ``` + diff --git a/operators/o2ims-operator/api/app.py b/operators/o2ims-operator/api/app.py new file mode 100644 index 00000000..cef5b5e6 --- /dev/null +++ b/operators/o2ims-operator/api/app.py @@ -0,0 +1,91 @@ +from flask import Flask, request, jsonify +from kubernetes import client, config +from kubernetes.client.rest import ApiException +import os +import logging +from datetime import datetime +from controllers.utils import validate_cluster_creation_request +app = Flask(__name__) + +@app.route('/O2ims_infrastructureProvisioning/v1/provisioningRequests ', methods=['POST']) +def trigger_action(): + data = request.json + logging.info("O2IMS API Received Request Payload Is:", data) + # add validation logic here + now = datetime.now() + dt_string = now.strftime("%Y-%m-%d %H:%M:%S") + try: + validate_cluster_creation_request(params=data) + o2ims_cr={ + 'apiVersion': 'o2ims.provisioning.oran.org/v1alpha1', + 'kind': 'ProvisioningRequest', + 'metadata': { + 'name': data.get('name'), + 'labels':{ + 'provisioningRequestId': data.get('provisioningRequestId') + } + }, + 'spec':{ + 'description': data.get('description'), + 'name': data.get('name'), + 'templateName': data.get('templateName'), + 'templateParameters':data.get('templateParameters'), + 'templateVersion': data.get('templateVersion') + } + } + logging.debug("O2IMS CR Payload Is:", o2ims_cr) + config.load_incluster_config() + api = client.CustomObjectsApi() + response = api.create_cluster_custom_object( + group='o2ims.provisioning.oran.org', + version='v1alpha1', + plural='provisioningrequest', + body=o2ims_cr + ) + except Exception as e: + logging.error(f"Caught Exception while deploying O2IMS CR ,{e}") + return jsonify({"status":{"updateTime":dt_string,"message":f"O2IMS Deployment Failed,{e}","provisioningPhase":"FAILED"}}),500 + return jsonify({"provisioningRequestData": data, "status": {"updateTime":dt_string,"message":"In-Progress","provisioningPhase":"PROGRESSING"},"ProvisionedResourceSet":{"nodeClusterId":"test","infrastructureResourceIds":"sample"}}), 200 + +@app.route('/O2ims_infrastructureProvisioning/v1/provisioningRequests ', methods=['GET']) +def fetch_status(): + now = datetime.now() + dt_string = now.strftime("%Y-%m-%d %H:%M:%S") + logging.info("Received O2IMS GET STATUS API CALL At %s:",dt_string) + + try: + config.load_incluster_config() + api = client.CustomObjectsApi() + response = api.list_cluster_custom_object( + group='o2ims.provisioning.oran.org', + version='v1alpha1', + plural='provisioningrequest' + ) + data=response.get('items') + if len(data)==0: + status=jsonify({"status":{"updateTime":dt_string,"message":"No ProvisioningRequest Found","provisioningPhase":"FAILED"}}) + response_data={"provisioningRequestData": {}, "status": status,"ProvisionedResourceSet":{}} + return response_data, 200 + #read all the provisioning requests and create response array from it + + for o2ims_cr in data: + status={} + #read o2ims_cr status and update message accordingly + if 'status' in o2ims_cr.keys(): + if o2ims_cr['status'].get('provisioningState')=='failed': + status= jsonify({"status":{"updateTime":dt_string,"message":o2ims_cr['status'].get('provisioningMessage'),"provisioningPhase":"FAILED"}}) + elif o2ims_cr['status'].get('provisioningState')=='progressing': + status= jsonify({"status":{"updateTime":dt_string,"message":o2ims_cr['status'].get('provisioningMessage'),"provisioningPhase":"PROGRESSING"}}) + elif o2ims_cr['status'].get('provisioningState')=='fulfilled': + status= jsonify({"status":{"updateTime":dt_string,"message":o2ims_cr['status'].get('provisioningMessage'),"provisioningPhase":"FULFILLED"}}) + else: + status=({"status":{"updateTime":dt_string,"message":"In-Progress","provisioningPhase":"PROGRESSING"}}) + # read o2ims_cr spec and update response accordingly + spec=o2ims_cr.get('spec') + provisionedresourceset={"nodeClusterId":"test","infrastructureResourceIds":"sample"} + response_data={"provisioningRequestData": spec, "status": status,"ProvisionedResourceSet":provisionedresourceset} + return response_data, 200 + except client.exceptions.ApiException as e: + logging.error(f"Caught Exception while fetching O2IMS CR Status ,{e}") + return jsonify({"status":{"updateTime":dt_string,"message":f"O2IMS Deployment Failed,{e}","provisioningPhase":"FAILED"}}),500 + \ No newline at end of file diff --git a/operators/o2ims-operator/controllers/manager.py b/operators/o2ims-operator/controllers/manager.py index 4652e5b1..8466ac44 100644 --- a/operators/o2ims-operator/controllers/manager.py +++ b/operators/o2ims-operator/controllers/manager.py @@ -22,6 +22,17 @@ import kopf import os +from flask import Flask +import threading +from api import app # Import the Flask app + + + +# Start Flask in a separate thread +def run_flask(): + app.run(host='0.0.0.0', port=5000) + +threading.Thread(target=run_flask, daemon=True).star @kopf.on.startup() def configure(settings: kopf.OperatorSettings, memo: kopf.Memo, **_): diff --git a/operators/o2ims-operator/controllers/utils.py b/operators/o2ims-operator/controllers/utils.py index e7d0b38a..5db114ef 100644 --- a/operators/o2ims-operator/controllers/utils.py +++ b/operators/o2ims-operator/controllers/utils.py @@ -272,3 +272,23 @@ def get_capi_cluster(name: str = None, namespace: str = None, logger=None): if logger: logger.debug(f"get_capi_cluster response: {r.json()}") return response + +def validate_cluster_creation_request(params: dict = None): + """ + :param params: parameters of cluster creation request + :type params: dict + :return: None + :rtype: None + """ + + # Validate templateName + if not params.get('templateName'): + raise ValueError("Parameter 'templateName' is empty or missing.") + + # Validate templateVersion + if not params.get('templateVersion'): + raise ValueError("Parameter 'templateVersion' is empty or missing.") + + # Validate templateParameters + if not params.get('templateParameters'): + raise ValueError("Parameter 'templateParameters' is empty or missing.") diff --git a/operators/o2ims-operator/requirements.txt b/operators/o2ims-operator/requirements.txt index d4ea816a..ed8afb43 100644 --- a/operators/o2ims-operator/requirements.txt +++ b/operators/o2ims-operator/requirements.txt @@ -2,4 +2,5 @@ jinja2==3.1.6 kopf==1.36.0 requests==2.32.4 -python-dateutil \ No newline at end of file +python-dateutil==2.8.2 +flask==2.3.3 \ No newline at end of file