[NaverCloud] metric connector구현&테스트

김미현·2024년 4월 10일

네이버 클라우드의 insight(구 monitoring)에 있는 metric서비스를 spaceone의 멀티클라우드 플랫폼인 cloudforet에 추가하는 코드를 작성한다.

네이버 클라우드 서비스를 spaceone에 가져오는 형태는 connetor->manager->spaceone에 plugin 하는 단계로 구성되어 있다.
오늘은 네이버 클라우드의 api를 사용해서 metric 서비스 중 metric list와 list의 data를 connect 작업을 한다.

경로는 3가지가 있다.
src/spaceone/monitoring/connector/naver_cloud_connector/init.py
src/spaceone/monitoring/connector/naver_cloud_connector/metric.py
test/connector/test_naver_cloud_connector.py

test에서는 테스트를 하고,
init에서는 네이버 클라우드 api를 이용해서 연결하고,
metric에서는 가져오려는 데이터의 구조에 맞게 변형한다.

코드를 보는 순서는 test->init->metric->test 이다.

test_connector.py

AKI = os.environ.get('NCLOUD_ACCESS_KEY_ID', None)
SK = os.environ.get('NCLOUD_SECRET_KEY', None)

class TestNaverCloudConnector(unittest.TestCase):
    secret_data = {
        'ncloud_access_key_id': AKI,
        'ncloud_secret_key': SK,
    }

    @classmethod
    def setUpClass(cls):
        config.init_conf(package='spaceone.inventory')
        cls.schema = 'naver_client_secret'
        
        super().setUpClass()

    @classmethod
    def tearDownClass(cls) -> None:
        super().tearDownClass()

    def test_list_metrics(self):
        options = {
            "prodKey": "460438727509020672"
        }
        secret_data = self.secret_data
        endpoint = '/cw_fea/real/cw/api/rule/group/metric/search'

        self.metric_connector = NaverCloudConnector(secret_data=secret_data, options=options)		#init으로 가서 connect를 하게 된다.
        self.metric_connector.set_connnect({}, options, secret_data, endpoint)
        metrics_info = self.metric_connector.list_metrics(options)		#options를 통해 prod_key를 전달한다. 받을 땐 딕셔너리 형태인 **kwargs로 받는다.

    def test_get_metric_data(self):
        # end = datetime.utcnow()
        # start = end - timedelta(minutes=60*60*7*30)
        # 위와 같은 방식으로 나중에 시간 변경 다 해줘야 한다.

        options = {
            "cw_key": "460438727509020672",
            "metric": "concurrent_session",
            "dimensions": {
                "loadBalancerPort": "80",
                "layerType": "APPLICATION",
                "loadBalancerName": "ing-spaceone-ncloudspace-1423c",
                "networkType": "PUBLIC"
            },
            "timeStart": '1712759400000',
            "timeEnd": '1712762999000'
        }

        secret_data = self.secret_data
        endpoint = '/cw_fea/real/cw/api/data/query'
        self.metric_connector = NaverCloudConnector(secret_data=secret_data, options=options)		#init으로 가서 connect를 하게 된다. metric_connector라는 객체 형성.
        self.metric_connector.set_connnect({}, options, secret_data, endpoint)

        metrics_info = self.metric_connector.get_metric_data(
            options.get('cw_key'),
            options.get('metric'),
            options.get('dimensions'),
            options.get('timeStart'),
            options.get('timeEnd'),
        ) 										#이 5개 값이 순서대로 init의 *args에 튜플형태로 들어간다.

        print("metrics_info: ", metrics_info)

if __name__ == "__main__":
    unittest.main(testRunner=RichTestRunner)

다음은 네이버 클라우드의 api를 이용해서 직접적으로 연결되는 부분이다.

__init__.py

class NaverCloudConnector(BaseConnector):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.metric_client = None
        self.base_url = 'https://cw.apigw.ntruss.com'

    def make_signature(self, access_key, secret_key, method, uri, timestamp):
        message = method + " " + uri + "\n" + timestamp + "\n" + access_key
        message = bytes(message, 'UTF-8')
        secret_key = bytes(secret_key, 'UTF-8')
        signingKey = base64.b64encode(hmac.new(secret_key, message, digestmod=hashlib.sha256).digest())
        return signingKey.decode()

    def set_connnect(self, options: dict, secret_data: dict, endpoint):
        self.url = f"{self.base_url}{endpoint}"
        self.endpoint = endpoint
        method = 'POST'
        timestamp = str(int(time.time() * 1000))
        metric_access_key = secret_data['ncloud_access_key_id']
        metric_secret_key = secret_data['ncloud_secret_key']
        headers = {
            'x-ncp-apigw-signature-v2': self.make_signature(metric_access_key, metric_secret_key, method,
                                                       endpoint,
                                                       timestamp),
            'x-ncp-apigw-timestamp': timestamp,
            'x-ncp-iam-access-key': metric_access_key,
            'Content-Type': 'application/json'
        }
        
        if options is None:
            options = {}

        self.metric_client = requests.post(self.url, headers=headers, json=options).json()		
        #list_metric의 경우, client는 {'metrics': [{'dataType': 'FLOAT', 'desc': 'Concurrent Connection', 'dimensions': [{'dim': 'layerType', 'val': 'APPLICATION'}, {'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ing-spaceone-ncloudspace-1423c'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'concurrent_session', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Concurrent Connection', 'dimensions': [{'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'loadBalancerPort', 'val': '443'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'concurrent_session', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Concurrent Connection', 'dimensions': [{'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'concurrent_session', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Connection per Second', 'dimensions': [{'dim': 'layerType', 'val': 'APPLICATION'}, {'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ing-spaceone-ncloudspace-1423c'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'connections_per_second', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Connection per Second', 'dimensions': [{'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'loadBalancerPort', 'val': '443'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'connections_per_second', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Connection per Second', 'dimensions': [{'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'connections_per_second', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'dimensions': [{'dim': 'layerType', 'val': 'APPLICATION'}, {'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ing-spaceone-ncloudspace-1423c'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'traffic_in_byte', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'dimensions': [{'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'loadBalancerPort', 'val': '443'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'traffic_in_byte', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'dimensions': [{'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'traffic_in_byte', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Traffic Out', 'dimensions': [{'dim': 'layerType', 'val': 'APPLICATION'}, {'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ing-spaceone-ncloudspace-1423c'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'traffic_out_byte', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Traffic Out', 'dimensions': [{'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'loadBalancerPort', 'val': '443'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'traffic_out_byte', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}, {'dataType': 'FLOAT', 'desc': 'Traffic Out', 'dimensions': [{'dim': 'loadBalancerPort', 'val': '80'}, {'dim': 'loadBalancerName', 'val': 'ingress-ngi-ingress-ngin-7e287'}, {'dim': 'layerType', 'val': 'NETWORK_PROXY'}, {'dim': 'networkType', 'val': 'PUBLIC'}], 'idDimension': 'instanceId', 'metric': 'traffic_out_byte', 'options': {'Min1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min5': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Min30': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Hour2': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG'], 'Day1': ['COUNT', 'SUM', 'MAX', 'MIN', 'AVG']}, 'prodKey': '460438727509020672'}], 'prodKey': '460438727509020672'}
        #get_metric_data의 경우, client는 [[1712759400000, 0.0], [1712759460000, 1.0], [1712759520000, 0.0], [1712759580000, 0.0], [1712759640000, 0.0], [1712759700000, 0.0], [1712759760000, 0.0], [1712759820000, 0.0], [1712759880000, 0.0], [1712759940000, 0.0], [1712760000000, 0.0], [1712760060000, 0.0], [1712760120000, 1.0], [1712760180000, 1.0], [1712760240000, 0.0], [1712760300000, 0.0], [1712760360000, 1.0], [1712760420000, 0.0], [1712760480000, 0.0], [1712760540000, 0.0], [1712760600000, 0.0], [1712760660000, 0.0], [1712760720000, 0.0], [1712760780000, 0.0], [1712760840000, 0.0], [1712760900000, 2.0], [1712760960000, 0.0], [1712761020000, 0.0], [1712761080000, 0.0], [1712761140000, 0.0], [1712761200000, 1.0], [1712761260000, 0.0], [1712761320000, 0.0], [1712761380000, 0.0], [1712761440000, 0.0], [1712761500000, 0.0], [1712761560000, 0.0], [1712761620000, 0.0], [1712761680000, 0.0], [1712761740000, 0.0], [1712761800000, 0.0], [1712761860000, 0.0], [1712761920000, 0.0], [1712761980000, 0.0], [1712762040000, 0.0], [1712762100000, 0.0], [1712762160000, 0.0], [1712762220000, 0.0], [1712762280000, 0.0], [1712762340000, 0.0], [1712762400000, 0.0], [1712762460000, 0.0], [1712762520000, 0.0], [1712762580000, 0.0], [1712762640000, 0.0], [1712762700000, 0.0], [1712762760000, 0.0], [1712762820000, 0.0], [1712762880000, 0.0], [1712762940000, 0.0]]

    def list_metrics(self, **kwargs):	#prodkey 딕셔너리 형태
        return NaverCloudMetric(self.metric_client, self.url).list_metrics(**kwargs)

    def get_metric_data(self, *args):	#options보면 튜플 형태
        return NaverCloudMetric(self.metric_client, self.url).get_metric_data(*args)

init에서 connect를 하나면 metirc.py에서는 각 함수의 값들을 가져온다.

metric.py

class NaverCloudMetric(object):

    def __init__(self, client, url):
        self.client = client
        self.url = url

    def list_metrics(self):

        metric_lists = []
        metric_info_lists = self.client['metrics']
        for metric_info in metric_info_lists:
            metric_lists.append(metric_info.get('metric'))
        return metric_lists

    def get_metric_data(self, cw_key, metric, dimensions, timeStart, timeEnd):
        metrics_data = self.client
        metric_data = []
        for data in metrics_data:
            metric_data.append(data[1])
        return metric_data
profile
안녕하세여

1개의 댓글

안녕하세요, 네이버 클라우드 플랫폼입니다.
네이버클라우드의 기술 콘텐츠 리워드 프로그램 ‘이달의 Nclouder(4월)’ 도전자로 초대합니다 🙂

네이버 클라우드 플랫폼 서비스와 관련된 모든 주제로 5/2(목) 23시까지 신청 가능합니다. (*4월 작성 콘텐츠 한정 신청 가능)

Ncloud 크레딧을 포함한 다양한 리워드가 준비되어 있으니 많은 관심 부탁드립니다!

자세한 내용은 아래 링크에서 확인부탁드립니다.
https://blog.naver.com/n_cloudplatform/223410617146

신청 링크
https://navercloud.typeform.com/to/lF8NUaCF

답글 달기