--- /dev/null
+*.py[cod]
+
+# C extensions
+*.so
+
+# log files
+*.log
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+.eggs
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+nohup.out
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+.testrepository
+.venv
+.cache
+
+# Translations
+*.mo
+
+# Complexity
+output/*.html
+output/*/index.html
+
+# Sphinx
+doc/build
+
+# pbr generates these
+AUTHORS
+ChangeLog
+
+# Editors
+*~
+.*.swp
+.*sw?
+.settings/
+__pycache__/
+.idea
+
+*.db
+test.config
\ No newline at end of file
--- /dev/null
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+
+FROM ubuntu:16.04
+
+LABEL authors="Benjamín Díaz"
+
+RUN apt-get --yes update \
+ && apt-get --yes install python3 python3-pip libmysqlclient-dev git \
+ && pip3 install pip==9.0.3
+
+COPY requirements.txt /policy_module/requirements.txt
+
+RUN pip3 install -r /policy_module/requirements.txt
+
+COPY . /policy_module
+
+RUN pip3 install /policy_module
+
+CMD bash /policy_module/scripts/gen_config_from_env.sh && osm-policy-agent --config osm_policy_agent.cfg
--- /dev/null
+include requirements.txt
+include README.rst
+recursive-include osm_policy_module
\ No newline at end of file
--- /dev/null
+Install
+------------------------
+ ::
+
+ git clone https://osm.etsi.org/gerrit/osm/MON.git
+ cd MON/policy_module
+ pip install .
+
+Run
+------------------------
+ ::
+
+ osm-policy-agent
+
--- /dev/null
+[policy_module]
+kafka_server_host=localhost
+kafka_server_port=9092
+log_dir=
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import argparse
+import logging
+import sys
+import logstash
+
+from osm_policy_module.core.agent import PolicyModuleAgent
+from osm_policy_module.core.config import Config
+from osm_policy_module.core.database import DatabaseManager
+
+
+def main():
+ cfg = Config.instance()
+ parser = argparse.ArgumentParser(prog='pm-scaling-config-agent')
+ parser.add_argument('--config-file', nargs='?', help='Policy module agent configuration file')
+ args = parser.parse_args()
+ if args.config_file:
+ cfg.load_file(args.config_file)
+ # TODO: Handle different log levels in config
+ if cfg.get('policy_module', 'log_dir') == 'stdout':
+ logging.basicConfig(stream=sys.stdout,
+ format='%(asctime)s %(message)s',
+ datefmt='%m/%d/%Y %I:%M:%S %p',
+ level=logging.INFO)
+ else:
+ logging.basicConfig(filename=cfg.get('policy_module', 'log_dir') + 'pm_agent.log',
+ format='%(asctime)s %(message)s',
+ datefmt='%m/%d/%Y %I:%M:%S %p', filemode='a',
+ level=logging.INFO)
+ if cfg.get('policy_module', 'enable_logstash_handler') == 'true':
+ logstash_host = cfg.get('policy_module', 'logstash_host')
+ logstash_port = int(cfg.get('policy_module', 'logstash_port'))
+ root_logger = logging.getLogger()
+ root_logger.addHandler(logstash.TCPLogstashHandler(logstash_host, logstash_port, version=1))
+ root_logger.info("Logstash handler configured.")
+ kafka_logger = logging.getLogger('kafka')
+ kafka_logger.setLevel(logging.WARN)
+ kafka_formatter = logging.Formatter(
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ kafka_handler = logging.StreamHandler(sys.stdout)
+ kafka_handler.setFormatter(kafka_formatter)
+ kafka_logger.addHandler(kafka_handler)
+ log = logging.getLogger(__name__)
+ log.info("Config: %s", cfg)
+ log.info("Syncing database...")
+ db_manager = DatabaseManager()
+ db_manager.create_tables()
+ log.info("Database synced correctly.")
+ log.info("Starting policy module agent...")
+ agent = PolicyModuleAgent()
+ agent.run()
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the Lcompletoicense at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+
+
+class AlarmConfig:
+ def __init__(self, metric_name, vdu_name, vnf_member_index, threshold, operation, statistic, action):
+ self.metric_name = metric_name
+ self.vdu_name = vdu_name
+ self.vnf_member_index = vnf_member_index
+ self.threshold = threshold
+ self.operation = operation
+ self.statistic = statistic
+ self.action = action
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import json
+import logging
+
+from kafka import KafkaProducer
+
+from osm_policy_module.core.config import Config
+
+log = logging.getLogger(__name__)
+
+
+class LcmClient:
+ def __init__(self):
+ cfg = Config.instance()
+ self.kafka_server = '{}:{}'.format(cfg.get('policy_module', 'kafka_server_host'),
+ cfg.get('policy_module', 'kafka_server_port'))
+ self.producer = KafkaProducer(bootstrap_servers=self.kafka_server,
+ key_serializer=str.encode,
+ value_serializer=str.encode)
+
+ def scale(self, nsr_id: str, name: str, action: str):
+ msg = self._create_scale_action_payload(nsr_id, name, action)
+ log.info("Sending scale action message: %s", json.dumps(msg))
+ self.producer.send(topic='lcm_pm', key='trigger_scaling', value=json.dumps(msg))
+ self.producer.flush()
+
+ def _create_scale_action_payload(self, nsr_id: str, name: str, action: str):
+ msg = {
+ "ns_id": nsr_id,
+ "scaling_group_descriptor": {
+ "name": name,
+ "action": action
+ }
+ }
+ return msg
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import json
+import logging
+import random
+import uuid
+
+from kafka import KafkaProducer, KafkaConsumer
+
+from osm_policy_module.core.config import Config
+
+log = logging.getLogger(__name__)
+
+
+class MonClient:
+ def __init__(self):
+ cfg = Config.instance()
+ self.kafka_server = '{}:{}'.format(cfg.get('policy_module', 'kafka_server_host'),
+ cfg.get('policy_module', 'kafka_server_port'))
+ self.producer = KafkaProducer(bootstrap_servers=self.kafka_server,
+ key_serializer=str.encode,
+ value_serializer=str.encode)
+
+ def create_alarm(self, metric_name: str, ns_id: str, vdu_name: str, vnf_member_index: str, threshold: int,
+ statistic: str, operation: str):
+ cor_id = random.randint(1, 1000000)
+ msg = self._create_alarm_payload(cor_id, metric_name, ns_id, vdu_name, vnf_member_index, threshold, statistic,
+ operation)
+ log.info("Sending create_alarm_request %s", msg)
+ future = self.producer.send(topic='alarm_request', key='create_alarm_request', value=json.dumps(msg))
+ future.get(timeout=60)
+ consumer = KafkaConsumer(bootstrap_servers=self.kafka_server,
+ key_deserializer=bytes.decode,
+ value_deserializer=bytes.decode,
+ consumer_timeout_ms=10000)
+ consumer.subscribe(['alarm_response'])
+ for message in consumer:
+ if message.key == 'create_alarm_response':
+ content = json.loads(message.value)
+ log.info("Received create_alarm_response %s", content)
+ if self._is_alarm_response_correlation_id_eq(cor_id, content):
+ alarm_uuid = content['alarm_create_response']['alarm_uuid']
+ # TODO Handle error response
+ return alarm_uuid
+
+ raise ValueError('Timeout: No alarm creation response from MON. Is MON up?')
+
+ def _create_alarm_payload(self, cor_id: int, metric_name: str, ns_id: str, vdu_name: str, vnf_member_index: str,
+ threshold: int, statistic: str, operation: str):
+ alarm_create_request = {
+ 'correlation_id': cor_id,
+ 'alarm_name': str(uuid.uuid4()),
+ 'metric_name': metric_name,
+ 'ns_id': ns_id,
+ 'vdu_name': vdu_name,
+ 'vnf_member_index': vnf_member_index,
+ 'operation': operation,
+ 'severity': 'critical',
+ 'threshold_value': threshold,
+ 'statistic': statistic
+ }
+ msg = {
+ 'alarm_create_request': alarm_create_request,
+ }
+ return msg
+
+ def _is_alarm_response_correlation_id_eq(self, cor_id, message_content):
+ return message_content['alarm_create_response']['correlation_id'] == cor_id
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import json
+import logging
+from typing import Dict, List
+
+import yaml
+from kafka import KafkaConsumer
+from osm_policy_module.common.alarm_config import AlarmConfig
+from osm_policy_module.common.lcm_client import LcmClient
+from osm_policy_module.common.mon_client import MonClient
+from osm_policy_module.core.config import Config
+from osm_policy_module.core.database import ScalingRecord, ScalingAlarm
+
+log = logging.getLogger(__name__)
+
+
+class PolicyModuleAgent:
+ def run(self):
+ cfg = Config.instance()
+ # Initialize servers
+ kafka_server = '{}:{}'.format(cfg.get('policy_module', 'kafka_server_host'),
+ cfg.get('policy_module', 'kafka_server_port'))
+
+ # Initialize Kafka consumer
+ log.info("Connecting to Kafka server at %s", kafka_server)
+ consumer = KafkaConsumer(bootstrap_servers=kafka_server,
+ key_deserializer=bytes.decode,
+ value_deserializer=bytes.decode,
+ group_id="pm-consumer")
+ consumer.subscribe(['lcm_pm', 'alarm_response'])
+
+ for message in consumer:
+ log.info("Message arrived: %s", message)
+ try:
+ if message.key == 'configure_scaling':
+ try:
+ content = json.loads(message.value)
+ except:
+ content = yaml.safe_load(message.value)
+ log.info("Creating scaling record in DB")
+ # TODO: Use transactions: http://docs.peewee-orm.com/en/latest/peewee/transactions.html
+ scaling_record = ScalingRecord.create(
+ nsr_id=content['ns_id'],
+ name=content['scaling_group_descriptor']['name'],
+ content=json.dumps(content)
+ )
+ log.info("Created scaling record in DB : nsr_id=%s, name=%s, content=%s",
+ scaling_record.nsr_id,
+ scaling_record.name,
+ scaling_record.content)
+ alarm_configs = self._get_alarm_configs(content)
+ for config in alarm_configs:
+ mon_client = MonClient()
+ log.info("Creating alarm record in DB")
+ alarm_uuid = mon_client.create_alarm(
+ metric_name=config.metric_name,
+ ns_id=scaling_record.nsr_id,
+ vdu_name=config.vdu_name,
+ vnf_member_index=config.vnf_member_index,
+ threshold=config.threshold,
+ operation=config.operation,
+ statistic=config.statistic
+ )
+ ScalingAlarm.create(
+ alarm_id=alarm_uuid,
+ action=config.action,
+ scaling_record=scaling_record
+ )
+ if message.key == 'notify_alarm':
+ content = json.loads(message.value)
+ alarm_id = content['notify_details']['alarm_uuid']
+ metric_name = content['notify_details']['metric_name']
+ operation = content['notify_details']['operation']
+ threshold = content['notify_details']['threshold_value']
+ vdu_name = content['notify_details']['vdu_name']
+ vnf_member_index = content['notify_details']['vnf_member_index']
+ ns_id = content['notify_details']['ns_id']
+ log.info(
+ "Received alarm notification for alarm %s, \
+ metric %s, \
+ operation %s, \
+ threshold %s, \
+ vdu_name %s, \
+ vnf_member_index %s, \
+ ns_id %s ",
+ alarm_id, metric_name, operation, threshold, vdu_name, vnf_member_index, ns_id)
+ try:
+ alarm = ScalingAlarm.select().where(ScalingAlarm.alarm_id == alarm_id).get()
+ lcm_client = LcmClient()
+ log.info("Sending scaling action message for ns: %s", alarm_id)
+ lcm_client.scale(alarm.scaling_record.nsr_id, alarm.scaling_record.name, alarm.action)
+ except ScalingAlarm.DoesNotExist:
+ log.info("There is no action configured for alarm %s.", alarm_id)
+ except Exception:
+ log.exception("Error consuming message: ")
+
+ def _get_alarm_configs(self, message_content: Dict) -> List[AlarmConfig]:
+ scaling_criterias = message_content['scaling_group_descriptor']['scaling_policy']['scaling_criteria']
+ alarm_configs = []
+ for criteria in scaling_criterias:
+ metric_name = ''
+ scale_out_threshold = criteria['scale_out_threshold']
+ scale_in_threshold = criteria['scale_in_threshold']
+ scale_out_operation = criteria['scale_out_relational_operation']
+ scale_in_operation = criteria['scale_in_relational_operation']
+ statistic = criteria['monitoring_param']['aggregation_type']
+ vdu_name = ''
+ vnf_member_index = ''
+ if 'vdu_monitoring_param' in criteria['monitoring_param']:
+ vdu_name = criteria['monitoring_param']['vdu_monitoring_param']['vdu_name']
+ vnf_member_index = criteria['monitoring_param']['vdu_monitoring_param']['vnf_member_index']
+ metric_name = criteria['monitoring_param']['vdu_monitoring_param']['name']
+ if 'vnf_metric' in criteria['monitoring_param']:
+ # TODO vnf_metric
+ continue
+ if 'vdu_metric' in criteria['monitoring_param']:
+ # TODO vdu_metric
+ continue
+ scale_out_alarm_config = AlarmConfig(metric_name,
+ vdu_name,
+ vnf_member_index,
+ scale_out_threshold,
+ scale_out_operation,
+ statistic,
+ 'scale_out')
+ scale_in_alarm_config = AlarmConfig(metric_name,
+ vdu_name,
+ vnf_member_index,
+ scale_in_threshold,
+ scale_in_operation,
+ statistic,
+ 'scale_in')
+ alarm_configs.append(scale_in_alarm_config)
+ alarm_configs.append(scale_out_alarm_config)
+ return alarm_configs
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##"""Global Configuration."""
+
+import logging
+
+from osm_policy_module.core.singleton import Singleton
+
+try:
+ from configparser import ConfigParser
+except ImportError:
+ from ConfigParser import ConfigParser
+
+log = logging.getLogger(__name__)
+
+
+@Singleton
+class Config(object):
+ """Global configuration."""
+
+ def __init__(self):
+ # Default config values
+ self.config = {
+ 'policy_module': {
+ 'kafka_server_host': '127.0.0.1',
+ 'kafka_server_port': '9092',
+ 'log_dir': 'stdout',
+ 'log_level': 'INFO',
+ 'enable_logstash_handler': 'false',
+ 'logstash_host': 'logstash',
+ 'logstash_port': '5000'
+ },
+ }
+
+ def load_file(self, config_file_path):
+ if config_file_path:
+ config_parser = ConfigParser()
+ config_parser.read(config_file_path)
+ for section in config_parser.sections():
+ for key, value in config_parser.items(section):
+ if section not in self.config:
+ self.config[section] = {}
+ self.config[section][key] = value
+
+ def get(self, group, name=None, default=None):
+ if group in self.config:
+ if name is None:
+ return self.config[group]
+ return self.config[group].get(name, default)
+ return default
+
+ def __str__(self):
+ return str(self.config)
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import logging
+
+from peewee import *
+from playhouse.sqlite_ext import SqliteExtDatabase
+
+from osm_policy_module.core.config import Config
+
+log = logging.getLogger(__name__)
+cfg = Config.instance()
+
+db = SqliteExtDatabase('policy_module.db')
+
+
+class BaseModel(Model):
+ class Meta:
+ database = db
+
+
+class ScalingRecord(BaseModel):
+ nsr_id = CharField()
+ name = CharField()
+ content = TextField()
+
+
+class ScalingAlarm(BaseModel):
+ alarm_id = CharField()
+ action = CharField()
+ scaling_record = ForeignKeyField(ScalingRecord, related_name='scaling_alarms')
+
+
+class DatabaseManager:
+ def create_tables(self):
+ try:
+ db.connect()
+ db.create_tables([ScalingRecord, ScalingAlarm])
+ db.close()
+ except Exception as e:
+ log.exception("Error creating tables: ")
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##"""Simple singleton class."""
+
+from __future__ import unicode_literals
+
+
+class Singleton(object):
+ """Simple singleton class."""
+
+ def __init__(self, decorated):
+ """Initialize singleton instance."""
+ self._decorated = decorated
+
+ def instance(self):
+ """Return singleton instance."""
+ try:
+ return self._instance
+ except AttributeError:
+ self._instance = self._decorated()
+ return self._instance
--- /dev/null
+{
+ "$schema": "http://json-schema.org/schema#",
+ "type": "object",
+ "properties": {
+ "ns_id": {
+ "type": "string"
+ },
+ "scaling_group_descriptor": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "scaling_policy": {
+ "type": "object",
+ "properties": {
+ "scale_in_operation_type": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ]
+ },
+ "scale_out_operation_type": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ]
+ },
+ "threshold_time": {
+ "type": "number"
+ },
+ "cooldown_time": {
+ "type": "number"
+ },
+ "scaling_criteria": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "scale_in_threshold": {
+ "type": "number"
+ },
+ "scale_out_threshold": {
+ "type": "number"
+ },
+ "scale_in_relational_operation": {
+ "type": "string",
+ "enum": [
+ "lt",
+ "gt",
+ "le",
+ "ge",
+ "eq"
+ ]
+ },
+ "scale_out_relational_operation": {
+ "type": "string",
+ "enum": [
+ "lt",
+ "gt",
+ "le",
+ "ge",
+ "eq"
+ ]
+ },
+ "monitoring_param": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "aggregation_type": {
+ "type": "string",
+ "enum": [
+ "average",
+ "maximum",
+ "minimum",
+ "count",
+ "sum"
+ ]
+ },
+ "vdu_monitoring_param": {
+ "type": "object",
+ "properties": {
+ "vnf_member_index": {
+ "type": "string"
+ },
+ "vdu_name": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+{
+ "ns_id": "360b400b-86dc-4b8e-a139-b7fc3987cf69",
+ "scaling_group_descriptor": {
+ "name": "test",
+ "scaling_policy": {
+ "scale_in_operation_type": "or",
+ "scale_out_operation_type": "or",
+ "threshold_time": 10,
+ "cooldown_time": 10,
+ "scaling_criteria": [
+ {
+ "scale_in_threshold": 50,
+ "scale_out_threshold": 50,
+ "scale_in_relational_operation": "lt",
+ "scale_out_relational_operation": "gt",
+ "monitoring_param": {
+ "id": "test_param_id",
+ "name": "test_param",
+ "aggregation_type": "average",
+ "vdu_monitoring_param": {
+ "vnf_member_index": "1",
+ "vdu_name": "2d8d5355-acf7-42be-9f34-a10d02f9df39",
+ "name": "cpu_utilization"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import json
+import logging
+import os
+import unittest
+
+from kafka import KafkaProducer, KafkaConsumer
+from kafka.errors import KafkaError
+
+log = logging.getLogger(__name__)
+
+
+class ScalingConfigTest(unittest.TestCase):
+ def setUp(self):
+ try:
+ kafka_server = '{}:{}'.format(os.getenv("KAFKA_SERVER_HOST", "localhost"),
+ os.getenv("KAFKA_SERVER_PORT", "9092"))
+ self.producer = KafkaProducer(bootstrap_servers=kafka_server,
+ key_serializer=str.encode,
+ value_serializer=str.encode)
+ self.consumer = KafkaConsumer(bootstrap_servers='localhost:9092',
+ group_id='osm_mon')
+ self.consumer.subscribe(['lcm_pm'])
+ except KafkaError:
+ self.skipTest('Kafka server not present.')
+
+ def test_send_scaling_config_msg(self):
+ try:
+ with open(
+ os.path.join(os.path.dirname(__file__), '../examples/configure_scaling_full_example.json')) as file:
+ payload = json.load(file)
+ future = self.producer.send('lcm_pm', json.dumps(payload), key="configure_scaling")
+ result = future.get(timeout=60)
+ log.info('Result: %s', result)
+
+ self.producer.flush()
+ # TODO: Improve assertions
+ self.assertIsNotNone(result)
+ except Exception as e:
+ self.fail(e)
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import json
+import unittest
+
+import os
+
+from jsonschema import validate
+
+
+class ExamplesTest(unittest.TestCase):
+ def test_examples_schema(self):
+ example_file_path = os.path.join(os.path.dirname(__file__), '../examples/configure_scaling_full_example.json')
+ schema_file_path = os.path.join(os.path.dirname(__file__), '../../models/configure_scaling.json')
+ with open(example_file_path) as example_file, open(schema_file_path) as schema_file:
+ validate(json.load(example_file), json.load(schema_file))
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import json
+import os
+import unittest
+
+from osm_policy_module.core.agent import PolicyModuleAgent
+
+
+class PolicyAgentTest(unittest.TestCase):
+ def setUp(self):
+ self.agent = PolicyModuleAgent()
+
+ def test_get_alarm_configs(self):
+ with open(os.path.join(os.path.dirname(__file__), '../examples/configure_scaling_full_example.json')) as file:
+ example = json.load(file)
+ alarm_configs = self.agent._get_alarm_configs(example)
+ # TODO Improve assertions
+ self.assertEqual(len(alarm_configs), 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+kafka==1.3.*
+peewee==3.1.*
+jsonschema==2.6.*
+six==1.11.*
+pyyaml==3.*
+python-logstash==0.4.*
+git+https://osm.etsi.org/gerrit/osm/common.git@v4.0.1#egg=osm-common
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+CONFIG_FILENAME="osm_policy_agent.cfg"
+rm $CONFIG_FILENAME 2> /dev/null
+touch $CONFIG_FILENAME
+echo "[policy_module]" >> $CONFIG_FILENAME
+if ! [[ -z "${BROKER_URI}" ]]; then
+ HOST=$(echo $BROKER_URI | cut -d: -f1)
+ PORT=$(echo $BROKER_URI | cut -d: -f2)
+ echo "kafka_server_host=$HOST" >> $CONFIG_FILENAME
+ echo "kafka_server_port=$PORT" >> $CONFIG_FILENAME
+fi
+if ! [[ -z "${LOGSTASH_URI}" ]]; then
+ HOST=$(echo $LOGSTASH_URI | cut -d: -f1)
+ PORT=$(echo $LOGSTASH_URI | cut -d: -f2)
+ echo "enable_logstash_handler=true" >> $CONFIG_FILENAME
+ echo "logstash_host=$HOST" >> $CONFIG_FILENAME
+ echo "logstash_port=$PORT" >> $CONFIG_FILENAME
+fi
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Whitestack, LLC
+# *************************************************************
+
+# This file is part of OSM Monitoring module
+# All Rights Reserved to Whitestack, LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: bdiaz@whitestack.com or glavado@whitestack.com
+##
+import setuptools
+
+
+def parse_requirements(requirements):
+ with open(requirements) as f:
+ return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#') and '://' not in l]
+
+
+_author = "Benjamín Díaz"
+_name = 'osm_policy_module'
+_author_email = 'bdiaz@whitestack.com'
+_version = '1.0'
+_description = 'OSM Policy Module'
+_maintainer = 'Benjamín Díaz'
+_maintainer_email = 'bdiaz@whitestack.com'
+_license = 'Apache 2.0'
+_url = 'https://osm.etsi.org/gitweb/?p=osm/MON.git;a=tree'
+
+setuptools.setup(
+ name=_name,
+ version=_version,
+ description=_description,
+ long_description=open('README.rst').read(),
+ author=_author,
+ author_email=_author_email,
+ maintainer=_maintainer,
+ maintainer_email=_maintainer_email,
+ url=_url,
+ license=_license,
+ packages=setuptools.find_packages(),
+ include_package_data=True,
+ install_requires=[
+ 'kafka==1.3.*',
+ 'peewee==3.1.*',
+ 'jsonschema==2.6.*',
+ 'six==1.11.*',
+ 'pyyaml==3.*',
+ 'python-logstash==0.4.*',
+ 'osm-common==4.*'
+ ],
+ entry_points={
+ "console_scripts": [
+ "osm-policy-agent = osm_policy_module.cmd.policy_module_agent:main",
+ ]
+ },
+ dependency_links=[
+ 'git+https://osm.etsi.org/gerrit/osm/common.git@v4.0.1#egg=osm-common'
+ ]
+)