Feature 11016: Service KPI Metric Based Scaling of VNF using exporter endpoint in...
[osm/NG-SA.git] / src / osm_ngsa / osm_mon / vim_connectors / azure.py
index 23086c4..4cf20b7 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #######################################################################################
+import datetime
 import logging
 from typing import Dict, List
 
 from azure.identity import ClientSecretCredential
 from azure.mgmt.compute import ComputeManagementClient
+from azure.mgmt.monitor import MonitorManagementClient
 from azure.profiles import ProfileDefinition
 from osm_mon.vim_connectors.base_vim import VIMConnector
 
@@ -26,8 +28,39 @@ from osm_mon.vim_connectors.base_vim import VIMConnector
 log = logging.getLogger(__name__)
 
 
-class AzureCollector(VIMConnector):
+METRIC_MAPPINGS = {
+    "cpu_utilization": {
+        "metricname": "Percentage CPU",
+        "aggregation": "Average",
+    },
+    "average_memory_utilization": {
+        "metricname": "Available Memory Bytes",
+        "aggregation": "Average",
+    },
+    "disk_read_ops": {
+        "metricname": "Disk Read Operations/Sec",
+        "aggregation": "Average",
+    },
+    "disk_write_ops": {
+        "metricname": "Disk Write Operations/Sec",
+        "aggregation": "Average",
+    },
+    "disk_read_bytes": {
+        "metricname": "Disk Read Bytes",
+        "aggregation": "Total",
+    },
+    "disk_write_bytes": {
+        "metricname": "Disk Write Bytes",
+        "aggregation": "Total",
+    },
+    # "packets_in_dropped": {},
+    # "packets_out_dropped": {},
+    # "packets_received": {},
+    # "packets_sent": {},
+}
+
 
+class AzureCollector(VIMConnector):
     # Translate azure provisioning state to OSM provision state.
     # The first three ones are the transitional status once a user initiated
     # action has been requested. Once the operation is complete, it will
@@ -103,8 +136,8 @@ class AzureCollector(VIMConnector):
     def __init__(self, vim_account: Dict):
         self.vim_account = vim_account
         self.reload_client = True
-        logger = logging.getLogger("azure")
-        logger.setLevel(logging.ERROR)
+        self.vm_sizes = {}
+
         # Store config to create azure subscription later
         self._config = {
             "user": vim_account["vim_user"],
@@ -128,6 +161,13 @@ class AzureCollector(VIMConnector):
             log.error("Azure resource_group is not specified at config")
             return
 
+        # REGION_NAME
+        if "region_name" in config:
+            self.region = config.get("region_name")
+        else:
+            log.error("Azure region_name is not specified at config")
+            return
+
     def _reload_connection(self):
         if self.reload_client:
             log.debug("reloading azure client")
@@ -142,11 +182,25 @@ class AzureCollector(VIMConnector):
                     self._config["subscription_id"],
                     profile=self.AZURE_COMPUTE_MGMT_PROFILE,
                 )
+                # create client
+                self.conn_monitor = MonitorManagementClient(
+                    self.credentials,
+                    self._config["subscription_id"],
+                )
                 # Set to client created
                 self.reload_client = False
             except Exception as e:
                 log.error(e)
 
+    def _get_region_vm_sizes(self):
+        if len(self.vm_sizes) == 0:
+            log.debug("getting VM sizes available in region")
+            try:
+                for size in self.conn_compute.virtual_machine_sizes.list(self.region):
+                    self.vm_sizes[size.name] = size
+            except Exception as e:
+                log.error(e)
+
     def collect_servers_status(self) -> List[Dict]:
         servers = []
         log.debug("collect_servers_status")
@@ -171,7 +225,6 @@ class AzureCollector(VIMConnector):
                             status = self.power_state2osm.get(
                                 splitted_status[1], "OTHER"
                             )
-                # log.info(f'id: {id}, name: {name}, status: {status}')
                 vm = {
                     "id": id,
                     "name": name,
@@ -180,6 +233,7 @@ class AzureCollector(VIMConnector):
                 servers.append(vm)
         except Exception as e:
             log.error(e)
+
         return servers
 
     def is_vim_ok(self) -> bool:
@@ -191,3 +245,81 @@ class AzureCollector(VIMConnector):
         except Exception as e:
             log.error(e)
         return status
+
+    def collect_metrics(self, metric_list: List[Dict]) -> List[Dict]:
+        log.debug("collect_metrics")
+        self._reload_connection()
+
+        metric_results = []
+        # VMs RAM cache for calculating "average_memory_utilization" metric
+        cache = {}
+        for metric in metric_list:
+            server = metric["vm_id"]
+            metric_name = metric["metric"]
+            metric_mapping = METRIC_MAPPINGS.get(metric_name)
+            if not metric_mapping:
+                continue
+            if metric_name == "average_memory_utilization" and len(cache) == 0:
+                # storing VMs RAM sizes in cache
+                self._get_region_vm_sizes()
+                try:
+                    for vm in self.conn_compute.virtual_machines.list(
+                        self.resource_group
+                    ):
+                        id = vm.id
+                        size_name = vm.hardware_profile.vm_size
+                        vm_size = self.vm_sizes.get(size_name)
+                        if vm_size:
+                            ram = vm_size.memory_in_mb
+                            cache[id] = ram
+                except Exception as e:
+                    log.error(e)
+            azure_metric_name = metric_mapping["metricname"]
+            azure_aggregation = metric_mapping["aggregation"]
+            end = datetime.datetime.now()
+            init = end - datetime.timedelta(minutes=5)
+            try:
+                metrics_data = self.conn_monitor.metrics.list(
+                    server,
+                    timespan="{}/{}".format(init, end),
+                    interval="PT1M",
+                    metricnames=azure_metric_name,
+                    aggregation=azure_aggregation,
+                )
+            except Exception as e:
+                log.error(e)
+                continue
+            total = 0
+            n_metrics = 0
+            for item in metrics_data.value:
+                log.info("{} ({})".format(item.name.localized_value, item.unit))
+                for timeserie in item.timeseries:
+                    for data in timeserie.data:
+                        if azure_aggregation == "Average":
+                            val = data.average
+                        elif azure_aggregation == "Total":
+                            val = data.total
+                        else:
+                            val = None
+                        log.info("{}: {}".format(data.time_stamp, val))
+                        if val is not None:
+                            total += val
+                            n_metrics += 1
+            if n_metrics > 0:
+                value = total / n_metrics
+                if metric_name == "average_memory_utilization":
+                    ram = cache.get(server)
+                    if ram:
+                        log.info(f"VM RAM = {ram}")
+                        value = ram - (value / 1048576)
+                    else:
+                        log.error(f"Not found RAM value for server {server}")
+                        value = None
+                if value is not None:
+                    log.info(f"value = {value}")
+                    metric["value"] = value
+                    metric_results.append(metric)
+            else:
+                log.info("No metric available")
+
+        return metric_results