Skip to content

Commit 1a8bec4

Browse files
author
D.Kang
committed
initial commit
1 parent 48ebced commit 1a8bec4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1299
-0
lines changed

.dockerignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
*.swp
2+
*.bak
3+
.git
4+
.gitignore
5+
.dockerignore
6+
dist
7+
build
8+
test
9+
*.egg-info
10+
__pycache__
11+
.idea

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
*.swp
2+
*.bak
3+
disk
4+
build
5+
*.egg-info
6+
*.egg
7+
*.whl
8+
*.log
9+
.idea
10+
__pycache__
11+
venv
12+
local-conf.yml
13+
test/reports
14+
test/_trial_temp

Dockerfile

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM python:3
2+
3+
ENV PYTHONUNBUFFERED 1
4+
ENV SPACEONE_PORT 50051
5+
ENV SERVER_TYPE grpc
6+
ENV PKG_DIR /tmp/pkg
7+
ENV SRC_DIR /tmp/src
8+
9+
COPY src ${SRC_DIR}
10+
ARG CACHEBUST=1
11+
WORKDIR ${SRC_DIR}
12+
RUN python3 setup.py install && \
13+
rm -rf /tmp/*
14+
15+
EXPOSE ${SPACEONE_PORT}
16+
17+
ENTRYPOINT ["spaceone"]
18+
CMD ["grpc", "monitoring"]

bin/build.sh

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#! /bin/bash
2+
# Build a docker image
3+
cd ..
4+
docker build -t pyengine/aws-cloudwatch . --no-cache
5+
docker tag pyengine/aws-cloudwatch pyengine/google-cloud-stackdriver:1.0
6+
docker tag pyengine/aws-cloudwatch spaceone/google-cloud-stackdriver:1.0

bin/push.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
# How to upload
3+
./build.sh
4+
docker push pyengine/google-cloud-stackdriver:1.0
5+
docker push spaceone/google-cloud-stackdriver:1.0

pkg/pip_requirements.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
spaceone-core
2+
spaceone-api
3+
spaceone-tester
4+
google-auth
5+
google-api-python-client
6+
schematics

src/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.0.0

src/setup.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#
2+
# Copyright 2020 The SpaceONE Authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
from setuptools import setup, find_packages
18+
19+
with open('VERSION', 'r') as f:
20+
VERSION = f.read().strip()
21+
f.close()
22+
23+
setup(
24+
name='plugin-googlecloud-stackdriver',
25+
version=VERSION,
26+
description='Google Cloud Stackdriver monitoring plugin',
27+
long_description='',
28+
url='https://www.spaceone.dev/',
29+
author='MEGAZONE SpaceONE Team',
30+
author_email='[email protected]',
31+
license='Apache License 2.0',
32+
packages=find_packages(),
33+
install_requires=[
34+
'spaceone-core',
35+
'spaceone-api',
36+
'spaceone-tester',
37+
'google-auth',
38+
'google-api-python-client',
39+
'schematics'
40+
],
41+
zip_safe=False,
42+
)

src/spaceone/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)

src/spaceone/monitoring/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
name = 'monitoring'

src/spaceone/monitoring/api/__init__.py

Whitespace-only changes.

src/spaceone/monitoring/api/plugin/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from spaceone.api.monitoring.plugin import data_source_pb2, data_source_pb2_grpc
2+
from spaceone.core.pygrpc import BaseAPI
3+
4+
5+
class DataSource(BaseAPI, data_source_pb2_grpc.DataSourceServicer):
6+
7+
pb2 = data_source_pb2
8+
pb2_grpc = data_source_pb2_grpc
9+
10+
def verify(self, request, context):
11+
params, metadata = self.parse_request(request, context)
12+
13+
with self.locator.get_service('DataSourceService', metadata) as data_source_service:
14+
response_stream = data_source_service.verify(params)
15+
for response in response_stream:
16+
yield self.locator.get_info('PluginVerifyResponse', response)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from spaceone.api.monitoring.plugin import log_pb2, log_pb2_grpc
2+
from spaceone.core.pygrpc import BaseAPI
3+
4+
5+
class Log(BaseAPI, log_pb2_grpc.LogServicer):
6+
7+
pb2 = log_pb2
8+
pb2_grpc = log_pb2_grpc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from spaceone.api.monitoring.plugin import metric_pb2, metric_pb2_grpc
2+
from spaceone.core.pygrpc import BaseAPI
3+
4+
5+
class Metric(BaseAPI, metric_pb2_grpc.MetricServicer):
6+
7+
pb2 = metric_pb2
8+
pb2_grpc = metric_pb2_grpc
9+
10+
def list(self, request, context):
11+
params, metadata = self.parse_request(request, context)
12+
13+
with self.locator.get_service('MetricService', metadata) as metric_service:
14+
response_stream = metric_service.list(params)
15+
for response in response_stream:
16+
yield self.locator.get_info('PluginMetricsResponse', response)
17+
18+
def get_data(self, request, context):
19+
params, metadata = self.parse_request(request, context)
20+
21+
with self.locator.get_service('MetricService', metadata) as metric_service:
22+
response_stream = metric_service.get_data(params)
23+
for response in response_stream:
24+
yield self.locator.get_info('PluginMetricDataResponse', response)

src/spaceone/monitoring/conf/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CONNECTORS = {
2+
'AWSBotoConnector': {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PROTO = {
2+
'spaceone.monitoring.api.plugin.data_source': ['DataSource'],
3+
'spaceone.monitoring.api.plugin.metric': ['Metric'],
4+
'spaceone.monitoring.api.plugin.log': ['Log'],
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from spaceone.monitoring.connector.aws_boto_connector import AWSBotoConnector
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import logging
2+
import boto3
3+
4+
from spaceone.core import utils
5+
from spaceone.core.connector import BaseConnector
6+
from spaceone.monitoring.error import *
7+
from spaceone.monitoring.connector.aws_boto_connector.cloud_watch import CloudWatch
8+
9+
__all__ = ['AWSBotoConnector']
10+
11+
_LOGGER = logging.getLogger(__name__)
12+
13+
14+
class AWSBotoConnector(BaseConnector):
15+
16+
def __init__(self, transaction, config):
17+
super().__init__(transaction, config)
18+
19+
def create_session(self, options: dict, secret_data: dict):
20+
self._check_secret_data(secret_data)
21+
22+
aws_access_key_id = secret_data['aws_access_key_id']
23+
aws_secret_access_key = secret_data['aws_secret_access_key']
24+
region_name = secret_data.get('region_name')
25+
role_arn = secret_data.get('role_arn')
26+
27+
try:
28+
if role_arn:
29+
self._create_session_with_assume_role(aws_access_key_id, aws_secret_access_key, region_name, role_arn)
30+
else:
31+
self._create_session_with_access_key(aws_access_key_id, aws_secret_access_key, region_name)
32+
except Exception as e:
33+
raise ERROR_INVALID_CREDENTIALS()
34+
35+
@staticmethod
36+
def _check_secret_data(secret_data):
37+
if 'aws_access_key_id' not in secret_data:
38+
raise ERROR_REQUIRED_PARAMETER(key='secret.aws_access_key_id')
39+
40+
if 'aws_secret_access_key' not in secret_data:
41+
raise ERROR_REQUIRED_PARAMETER(key='secret.aws_secret_access_key')
42+
43+
def _create_session_with_access_key(self, aws_access_key_id, aws_secret_access_key, region_name):
44+
self.session = boto3.Session(aws_access_key_id=aws_access_key_id,
45+
aws_secret_access_key=aws_secret_access_key,
46+
region_name=region_name)
47+
48+
sts = self.session.client('sts')
49+
sts.get_caller_identity()
50+
51+
def _create_session_with_assume_role(self, aws_access_key_id, aws_secret_access_key, region_name, role_arn):
52+
self._create_session_with_access_key(aws_access_key_id, aws_secret_access_key, region_name)
53+
54+
sts = self.session.client('sts')
55+
assume_role_object = sts.assume_role(RoleArn=role_arn, RoleSessionName=utils.generate_id('AssumeRoleSession'))
56+
credentials = assume_role_object['Credentials']
57+
58+
self.session = boto3.Session(aws_access_key_id=credentials['AccessKeyId'],
59+
aws_secret_access_key=credentials['SecretAccessKey'],
60+
region_name=region_name,
61+
aws_session_token=credentials['SessionToken'])
62+
63+
def list_metrics(self, *args, **kwargs):
64+
cw = CloudWatch(self.session)
65+
return cw.list_metrics(*args, **kwargs)
66+
67+
def get_metric_data(self, *args, **kwargs):
68+
cw = CloudWatch(self.session)
69+
return cw.get_metric_data(*args, **kwargs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import logging
2+
import time
3+
from datetime import datetime, timedelta
4+
5+
from spaceone.core import utils
6+
from spaceone.monitoring.error import *
7+
8+
__all__ = ['CloudWatch']
9+
10+
_LOGGER = logging.getLogger(__name__)
11+
12+
13+
class CloudWatch(object):
14+
15+
def __init__(self, session):
16+
self.session = session
17+
self.client = self.session.client('cloudwatch')
18+
19+
def list_metrics(self, namespace, dimensions):
20+
paginator = self.client.get_paginator('list_metrics')
21+
22+
responses = paginator.paginate(Namespace=namespace, Dimensions=dimensions)
23+
24+
metrics_info = []
25+
26+
for response in responses:
27+
for metric in response['Metrics']:
28+
metric_name = metric['MetricName']
29+
unit = self._get_metric_unit(namespace, dimensions, metric_name)
30+
chart_type, chart_option = self._get_chart_info(namespace, dimensions, metric_name)
31+
32+
metric_info = {
33+
'key': metric_name,
34+
'name': metric_name,
35+
'unit': unit,
36+
'chart_type': chart_type,
37+
'chart_options': chart_option
38+
}
39+
40+
metrics_info.append(metric_info)
41+
42+
return {
43+
'metrics': metrics_info
44+
}
45+
46+
def get_metric_data(self, namespace, dimensions, metric_name, start, end, period, stat, limit=None):
47+
metric_id = f'metric_{utils.random_string()[:12]}'
48+
49+
extra_opts = {}
50+
51+
if limit:
52+
extra_opts['MaxDatapoints'] = limit
53+
54+
response = self.client.get_metric_data(
55+
MetricDataQueries=[{
56+
'Id': metric_id,
57+
'MetricStat': {
58+
'Metric': {
59+
'Namespace': namespace,
60+
'MetricName': metric_name,
61+
'Dimensions': dimensions
62+
},
63+
'Period': period,
64+
'Stat': stat
65+
}
66+
}],
67+
StartTime=start,
68+
EndTime=end,
69+
ScanBy='TimestampAscending',
70+
**extra_opts
71+
)
72+
73+
metric_data_info = {
74+
'labels': [],
75+
'values': []
76+
}
77+
78+
for metric_data in response.get('MetricDataResults', []):
79+
metric_data_info['labels'] = list(map(self._convert_timestamp, metric_data['Timestamps']))
80+
metric_data_info['values'] += metric_data['Values']
81+
82+
return metric_data_info
83+
84+
@staticmethod
85+
def _convert_timestamp(metric_datetime):
86+
timestamp = int(time.mktime(metric_datetime.timetuple()))
87+
return {
88+
'seconds': timestamp
89+
}
90+
91+
def _get_metric_unit(self, namespace, dimensions, metric_name):
92+
end = datetime.utcnow()
93+
start = end - timedelta(minutes=60)
94+
95+
response = self.client.get_metric_statistics(
96+
Namespace=namespace,
97+
Dimensions=dimensions,
98+
MetricName=metric_name,
99+
StartTime=start,
100+
EndTime=end,
101+
Period=600,
102+
Statistics=['SampleCount']
103+
)
104+
105+
return_dict = {
106+
'x': 'Timestamp',
107+
'y': ''
108+
}
109+
110+
for data_point in response.get('Datapoints', []):
111+
unit = data_point['Unit']
112+
113+
if unit != 'None':
114+
return_dict['y'] = unit
115+
return return_dict
116+
117+
return return_dict
118+
119+
@staticmethod
120+
def _get_chart_info(namespace, dimensions, metric_name):
121+
return 'line', {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from spaceone.monitoring.error.google_cloud import *
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from spaceone.core.error import *
2+
3+
class ERROR_INVALID_CREDENTIALS(ERROR_INVALID_ARGUMENT):
4+
_message = 'Google Cloud credentials is invalid.'
5+
6+
7+
class ERROR_INVALID_RESOURCE_FORMAT(ERROR_INVALID_ARGUMENT):
8+
_message = 'Resource format is invalid. (format = ARN).'
9+
10+
11+
class ERROR_NOT_SUPPORT_RESOURCE(ERROR_INVALID_ARGUMENT):
12+
_message = 'This Resource is not supported by Google Cloud Stackdriver. (resource = {resource})'
13+
14+
15+
class ERROR_NOT_SUPPORT_STAT(ERROR_INVALID_ARGUMENT):
16+
_message = 'Statistics option is invalid. (supported_stat = {supported_stat})'
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from spaceone.monitoring.info.metric_info import *
2+
from spaceone.monitoring.info.data_source_info import *

0 commit comments

Comments
 (0)