Skip to content

Commit c3c3f83

Browse files
committed
Export Metrics to Object Storage
1 parent 0e063bb commit c3c3f83

File tree

6 files changed

+337
-0
lines changed

6 files changed

+337
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
## Export Metrics to Object Storage
2+
3+
This sample Python function demonstrates how to submit a Monitoring Query Language (MQL) query to OCI Monitoring Service which will extract a collection of historical metrics and then push this package to Object Storage as a json file.
4+
5+
![Export Process](./images/Export-Function.png)
6+
7+
This sample is intended to help you get up and running quickly by using default values for most parameters. In fact, there is only one required parameter: **compartmentId** --> this specifies the Object Storage Compartment to create the storage bucket (if it does not already exist). Supply values for optional parameters to select target resources, specific metrics to collect, define time ranges, etc., per your requirements. Advanced users may also replace the export function within the code to push metrics to your downstream application api.
8+
9+
The full list of *optional* parameters and their default values are presented below in the **Deploy** and **Test** sections. But first let's cover Prerequisites and the Function Development Environment setup.
10+
11+
12+
As you make your way through this tutorial, look out for this icon ![user input icon](./images/userinput.png).
13+
Whenever you see it, it's time for you to perform an action.
14+
15+
16+
## Prerequisites
17+
18+
Before you deploy this sample function, make sure you have run steps A, B
19+
and C of the [Oracle Functions Quick Start Guide for Cloud Shell](https://www.oracle.com/webfolder/technetwork/tutorials/infographics/oci_functions_cloudshell_quickview/functions_quickview_top/functions_quickview/index.html)
20+
* A - Set up your tenancy
21+
* B - Create application
22+
* C - Set up your Cloud Shell dev environment
23+
24+
25+
## List Applications
26+
27+
Assuming you have successfully completed the prerequisites, you should see your
28+
application in the list of applications.
29+
30+
```
31+
fn ls apps
32+
```
33+
34+
35+
## Create or Update your Dynamic Group
36+
37+
In order to use other OCI Services, your function must be part of a dynamic
38+
group. For information on how to create a dynamic group, refer to the
39+
[documentation](https://docs.cloud.oracle.com/iaas/Content/Identity/Tasks/managingdynamicgroups.htm#To).
40+
41+
When specifying the *Matching Rules*, we suggest matching all functions in a compartment with:
42+
43+
```
44+
ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaxxxxx'}
45+
```
46+
47+
48+
## Create or Update IAM Policies
49+
50+
Create a new policy that allows the dynamic group to read metrics and manage object storage.
51+
52+
53+
![user input icon](./images/userinput.png)
54+
55+
Your policy should look something like this:
56+
```
57+
Allow dynamic group <group-name> to read metrics in compartment <compartment-name>
58+
Allow dynamic group <group-name> to manage buckets in compartment <compartment-name>
59+
Allow dynamic group <group-name> to manage objects in compartment <compartment-name>
60+
```
61+
62+
For more information on how to create policies, go [here](https://docs.cloud.oracle.com/iaas/Content/Identity/Concepts/policysyntax.htm).
63+
64+
65+
## Review and customize the function
66+
67+
Review the following files in the current folder:
68+
* the code of the function, [func.py](./func.py)
69+
* its dependencies, [requirements.txt](./requirements.txt)
70+
* the function metadata, [func.yaml](./func.yaml)
71+
72+
73+
## Deploy the function
74+
75+
In Cloud Shell, run the `fn deploy` command to build *this* function and its dependencies as a Docker image,
76+
push the image to the specified Docker registry, and deploy *this* function to Oracle Functions
77+
in the application created earlier:
78+
79+
![user input icon](./images/userinput.png)
80+
```
81+
fn -v deploy --app <app-name>
82+
```
83+
For example,
84+
```
85+
fn -v deploy --app myapp
86+
```
87+
88+
### Test
89+
90+
You may test the function using default values by executing the following command, specifying the only mandatory parameter (compartmentId). This will create an Object Storage bucket named metrics-export, if it does not already exist, and upload a json file containing per-minute avg cpu levels during the preceding hour for all VMs in the specified compartment.
91+
92+
![user input icon](./images/userinput.png)
93+
```
94+
echo '{"compartmentId":"<compartment-ocid>"}' | fn invoke <app-name> <function-name>
95+
```
96+
For example:
97+
```
98+
echo '{"compartmentId":"<your ocid1.compartment.oc1..aaaaxxxxxxx>"}' | fn invoke myapp oci-monitoring-metric-export-python
99+
```
100+
101+
There are several optional parameters to customize the query and time range. In addition to compartmentId, use any combination of additional parameters in the following format:
102+
103+
```
104+
echo '{"compartmentId":"ocid1.compartment.oc1..aaaaxxxxxxx", "parameter_name_1":"<your parameter_value_1>", "parameter_name_2":"<your parameter_value_2>"}' | fn invoke myapp oci-monitoring-metric-export-python
105+
```
106+
107+
For example, to select a specific namespace and custom query use this format:
108+
109+
```
110+
echo '{"compartmentId":"ocid1.compartment.oc1..aaaaxxxxxxx", "namespace":"<your namespace>", "query":"<your query>"}' | fn invoke myapp oci-monitoring-metric-export-python
111+
```
112+
113+
The following `parameters` are supported, along with default values if not specified.
114+
115+
116+
117+
**namespace**
118+
* Description: Category of metrics (see OCI Monitoring Documentation)
119+
* Default: *oci_computeagent*
120+
121+
**resource_group**
122+
* Description: Subcategory of metrics (see OCI Monitoring Documentation)
123+
* Default: *none*
124+
125+
**query**
126+
* Description: MQL query to filter metrics (see OCI Monitoring Documentation)
127+
* Default: *CpuUtilization[1m].mean()*
128+
129+
**startdtm**
130+
* Description: Timestamp defining start of collection period
131+
* Default: *1 hour prior to current time*
132+
133+
**enddtm**
134+
* Description: Timestamp defining end of collection period
135+
* Default: *current time*
136+
137+
**resolution**
138+
* Description: Aggregation resolution (see OCI Monitoring Documentation)
139+
* Default: *1m*
140+
141+
142+
## Monitoring Functions
143+
Learn how to configure basic observability for your function using metrics, alarms and email alerts:
144+
* [Basic Guidance for Monitoring your Functions](../basic-observability/functions.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#
2+
# oci-monitoring-metric-export-python version 1.0.
3+
#
4+
# Copyright (c) 2021 Oracle, Inc. All rights reserved.
5+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
6+
#
7+
8+
9+
import io
10+
import oci
11+
import requests
12+
import logging
13+
import json
14+
from datetime import datetime, timedelta
15+
from fdk import response
16+
17+
18+
19+
"""
20+
Create Object Storage bucket 'metrics-export' in the specified Compartent, if it does not already exist.
21+
Default access: Private
22+
"""
23+
def createBucketIfNotExists(_source_namespace, _compartmentId, _bucketname, _object_storage_client, logger):
24+
try:
25+
LiveBucketList = set()
26+
LiveBucketResponse = _object_storage_client.list_buckets(_source_namespace, _compartmentId)
27+
for bucket in LiveBucketResponse.data:
28+
LiveBucketList.add(bucket.name)
29+
if _bucketname not in LiveBucketList:
30+
request = oci.object_storage.models.CreateBucketDetails()
31+
request.compartment_id = _compartmentId
32+
request.name = _bucketname
33+
bucket = _object_storage_client.create_bucket(_source_namespace, request)
34+
except Exception as e:
35+
logger.error("Error in createBucketIfNotExists(): {}".format(str(e)))
36+
raise
37+
38+
"""
39+
Delete target objectname if it already exists.
40+
Due to filenames containing embedded timestamps this scenario is rare, but could occur if re-executing a previous export with specific start/end timestamps.
41+
"""
42+
def deleteObjectIfExists(_source_namespace, _bucketname, _objectname, _object_storage_client, logger):
43+
liveObjects = set()
44+
try:
45+
response = _object_storage_client.list_objects(namespace_name=_source_namespace, delimiter='/', bucket_name=_bucketname, fields='name,timeCreated,size')
46+
for obj in response.data.objects:
47+
if (obj.name == _objectname):
48+
_object_storage_client.delete_object(_source_namespace, _bucketname, _objectname)
49+
except Exception as e:
50+
logger.error("Error in deleteObjectIfExists(): {}".format(str(e)))
51+
raise
52+
53+
"""
54+
Perform api call to pull metrics
55+
"""
56+
def export_metrics(monitoring_client, _compartmentId, _namespace, _resource_group, _query, _startdtm, _enddtm, _resolution, logger):
57+
try:
58+
_dataDetails = oci.monitoring.models.SummarizeMetricsDataDetails ()
59+
_dataDetails.namespace=_namespace
60+
_dataDetails.query=_query
61+
if (_resource_group.strip() != ""):
62+
_dataDetails.resource_group=_resource_group
63+
_dataDetails.start_time=_startdtm
64+
_dataDetails.end_time=_enddtm
65+
_dataDetails.resolution=_resolution
66+
67+
print(_resource_group)
68+
print(_dataDetails)
69+
70+
summarize_metrics_data_response = monitoring_client.summarize_metrics_data (
71+
compartment_id=_compartmentId, summarize_metrics_data_details =_dataDetails)
72+
return summarize_metrics_data_response.data
73+
except Exception as e:
74+
logger.error("Error in export_metrics(): {}".format(str(e)))
75+
raise
76+
"""
77+
Upload (put) metrics json file to bucket 'metrics-export'
78+
"""
79+
def putObject(_source_namespace, _bucketname, _objectname, _content, _object_storage_client, logger):
80+
try:
81+
put_object_response = _object_storage_client.put_object(_source_namespace, _bucketname, _objectname, _content)
82+
except Exception as e:
83+
logger.error("Error in putObject(): {}".format(str(e)))
84+
raise
85+
86+
87+
"""
88+
Entrypoint and initialization
89+
"""
90+
def handler(ctx, data: io.BytesIO=None):
91+
logger = logging.getLogger()
92+
logger.info("function start")
93+
signer = oci.auth.signers.get_resource_principals_signer()
94+
configinfo = {'region': signer.region, 'tenancy': signer.tenancy_id}
95+
monitoring_client = oci.monitoring.MonitoringClient(config={}, signer=signer)
96+
object_storage_client = oci.object_storage.ObjectStorageClient(config={}, signer=signer)
97+
source_namespace = object_storage_client.get_namespace(retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY).data
98+
99+
# Retrieve the Function configuration values
100+
# Parse input parameters and assign default values as needed
101+
try:
102+
cfg = dict(ctx.Config())
103+
try:
104+
_compartmentId = cfg["compartmentId"]
105+
except Exception as e:
106+
logger.error('Mandatory key compartmentId not defined')
107+
raise
108+
109+
try:
110+
_namespace = cfg["namespace"]
111+
except:
112+
logger.info('Optional configuration key namespace unavailable. Will assign default value')
113+
_namespace = "oci_computeagent"
114+
115+
try:
116+
_resource_group = cfg["resource_group"]
117+
except:
118+
logger.info('Optional configuration key resource_group unavailable. Will assign default value')
119+
_resource_group = ""
120+
121+
try:
122+
_query = cfg["query"]
123+
except:
124+
logger.info('Optional configuration key query unavailable. Will assign default value')
125+
_query = "CpuUtilization[1m].mean()"
126+
127+
try:
128+
_startdtm = cfg["startdtm"]
129+
except:
130+
logger.info('Optional configuration key startdtm unavailable. Will assign default value')
131+
_startdtm = (datetime.now() + timedelta(hours=-1)).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
132+
133+
try:
134+
_enddtm = cfg["enddtm"]
135+
except:
136+
logger.info('Optional configuration key enddtm unavailable. Will assign default value')
137+
_enddtm = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")
138+
139+
try:
140+
_resolution = cfg["resolution"]
141+
except:
142+
logger.info('Optional configuration key resolution unavailable. Will assign default value')
143+
_resolution = "1m"
144+
145+
146+
bucketname = "metrics-export"
147+
dt_string = _enddtm
148+
149+
if (_resource_group.strip()):
150+
objectname = _namespace + "-" + _resource_group + "-" + dt_string[0:19] + ".json"
151+
else:
152+
objectname = _namespace + "-" + dt_string[0:19] + ".json"
153+
154+
logger.info("compartmentId: {}".format(str(_compartmentId)))
155+
logger.info("namespace: {}".format(str(_namespace)))
156+
logger.info("resource_group: {}".format(str(_resource_group)))
157+
logger.info("query: {}".format(str(_query)))
158+
logger.info("startdtm: {}".format(str(_startdtm)))
159+
logger.info("enddtm: {}".format(str(_enddtm)))
160+
logger.info("resolution: {}".format(str(_resolution)))
161+
logger.info("source_namespace: {}".format(str(source_namespace)))
162+
163+
except Exception as e:
164+
logger.error("Error in retrieving and assigning configuration values")
165+
raise
166+
167+
# Main tasks
168+
try:
169+
createBucketIfNotExists(source_namespace, _compartmentId, bucketname, object_storage_client, logger)
170+
deleteObjectIfExists(source_namespace, bucketname, objectname, object_storage_client, logger)
171+
listContent = export_metrics(monitoring_client, _compartmentId, _namespace, _resource_group, _query, _startdtm, _enddtm, _resolution, logger)
172+
putObject(source_namespace, bucketname, objectname, str(listContent), object_storage_client, logger)
173+
except Exception as e:
174+
logger.error("Error in main process: {}".format(str(e)))
175+
raise
176+
177+
# The function is complete, return info tbd
178+
logger.info("function end")
179+
return response.Response(
180+
ctx,
181+
response_data="",
182+
headers={"Content-Type": "application/json"}
183+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
schema_version: 20180708
2+
name: oci-monitoring-metric-export-python
3+
version: 0.0.8
4+
runtime: python
5+
entrypoint: /python/bin/fdk /function/func.py handler
6+
memory: 256
7+
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fdk
2+
requests
3+
oci

0 commit comments

Comments
 (0)