aboutsummaryrefslogtreecommitdiffstats
path: root/simulator-cli/cli
diff options
context:
space:
mode:
authorEdyta Krukowska <edyta.krukowska@nokia.com>2021-03-15 13:30:09 +0100
committerEdyta Krukowska <edyta.krukowska@nokia.com>2021-03-15 14:01:32 +0100
commitc4a6e3114d3614e257350f967eeb58517afff06b (patch)
tree2fa2356db5213249e3146f21328cb4ba1a0461d1 /simulator-cli/cli
parent78af475bcc9022484b16c7d563c93c0e934ff677 (diff)
Move simulator-ci from pnf-simulator to nf-simulator
Issue-ID: INT-1869 Signed-off-by: Edyta Krukowska <edyta.krukowska@nokia.com> Change-Id: I31ca7d48f4ff9438e28b3637bb98fdd391d14030
Diffstat (limited to 'simulator-cli/cli')
-rw-r--r--simulator-cli/cli/__init__.py19
-rw-r--r--simulator-cli/cli/client/__init__.py19
-rw-r--r--simulator-cli/cli/client/tailf_client.py59
-rw-r--r--simulator-cli/cli/data/logging.ini20
-rw-r--r--simulator-cli/cli/netconf_server.py278
-rw-r--r--simulator-cli/cli/nf_simulator.py374
6 files changed, 769 insertions, 0 deletions
diff --git a/simulator-cli/cli/__init__.py b/simulator-cli/cli/__init__.py
new file mode 100644
index 0000000..bc242fe
--- /dev/null
+++ b/simulator-cli/cli/__init__.py
@@ -0,0 +1,19 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2021 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
diff --git a/simulator-cli/cli/client/__init__.py b/simulator-cli/cli/client/__init__.py
new file mode 100644
index 0000000..bc242fe
--- /dev/null
+++ b/simulator-cli/cli/client/__init__.py
@@ -0,0 +1,19 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2021 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
diff --git a/simulator-cli/cli/client/tailf_client.py b/simulator-cli/cli/client/tailf_client.py
new file mode 100644
index 0000000..1f46275
--- /dev/null
+++ b/simulator-cli/cli/client/tailf_client.py
@@ -0,0 +1,59 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2021 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+import logging
+
+import websockets
+import asyncio
+import signal
+import sys
+
+
+class TailfClient(object):
+
+ def __init__(self, url: str, verbose: bool = False) -> None:
+ self._url = url
+ self._is_running = False
+ self._connection = None
+ self.logger = logging.getLogger()
+ self.logger.setLevel(logging.DEBUG if verbose else logging.INFO)
+ signal.signal(signal.SIGINT, self._handle_keyboard_interrupt)
+
+ def tailf_messages(self):
+ self._is_running = True
+ self.logger.debug("Attempting to connect to websocket server on %s", self._url)
+ asyncio.get_event_loop().run_until_complete(
+ self._tailf_messages()
+ )
+
+ async def _tailf_messages(self):
+ try:
+ async with websockets.connect(self._url) as connection:
+ self.logger.debug("Connection with %s established", self._url)
+ self._connection = connection
+ while self._is_running:
+ print(await self._connection.recv(), "\n")
+ except ConnectionRefusedError:
+ self.logger.error("Cannot establish connection with %s", self._url)
+
+ def _handle_keyboard_interrupt(self, sig, frame):
+ self.logger.warning("CTR-C pressed, interrupting.")
+ self._is_running = False
+ sys.exit(0)
diff --git a/simulator-cli/cli/data/logging.ini b/simulator-cli/cli/data/logging.ini
new file mode 100644
index 0000000..8b2b402
--- /dev/null
+++ b/simulator-cli/cli/data/logging.ini
@@ -0,0 +1,20 @@
+[loggers]
+keys=root
+
+[handlers]
+keys=consoleHandler
+
+[formatters]
+keys=simpleFormatter
+
+[logger_root]
+level=DEBUG
+handlers=consoleHandler
+
+[handler_consoleHandler]
+class=StreamHandler
+formatter=simpleFormatter
+args=(sys.stdout,)
+
+[formatter_simpleFormatter]
+format=%(message)s
diff --git a/simulator-cli/cli/netconf_server.py b/simulator-cli/cli/netconf_server.py
new file mode 100644
index 0000000..57196fa
--- /dev/null
+++ b/simulator-cli/cli/netconf_server.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2021 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+import argparse
+import logging
+import logging.config
+import requests
+import os
+import sys
+from requests import Response
+
+from cli.client.tailf_client import TailfClient
+
+TAILF_FUNC_ENDPOINT = "ws://{}:9000/netconf"
+LESS_FUNC_ENDPOINT = "/store/less"
+CM_HISTORY_ENDPOINT = "/store/cm-history"
+GET_CONFIG_ENDPOINT = "/netconf/get"
+MODEL_ENDPOINT = "/netconf/model/{}"
+EDIT_CONFIG_ENDPOINT = "/netconf/edit-config"
+logging.basicConfig()
+
+DEFAULT_EXTERNAL_SIM_PORT = 8080
+DEFAULT_INTERNAL_SIM_PORT = 9000
+
+
+class NetconfSimulatorClient(object):
+ def __init__(self, ip: str, protocol: str = 'http', port: int = DEFAULT_EXTERNAL_SIM_PORT, verbose: bool = False) -> None:
+ self._ip = ip
+ self._protocol = protocol
+ self._port = port
+ self._configure_logger(verbose)
+ self._verbose=verbose
+
+ def tailf_like_func(self) -> None:
+ url = TAILF_FUNC_ENDPOINT.format(self._ip)
+ client = TailfClient(url, self._verbose)
+ client.tailf_messages()
+
+ def get_cm_history(self) -> None:
+ self.logger.info("Attempting to retrieve all netconf configuration changes")
+ simulator_address = "{}://{}:{}{}".format(self._protocol, self._ip, self._port, CM_HISTORY_ENDPOINT)
+ self.logger.debug("Simulator address: %s", simulator_address)
+ try:
+ response = requests.get(simulator_address)
+ self._log_json_response(response)
+ except requests.ConnectionError:
+ self.logger.error("Failed to establish connection with {}".format(simulator_address))
+
+ def less_like_func(self, limit: int) -> None:
+ self.logger.info("Attempting to run less on CM change")
+ simulator_address = "{}://{}:{}{}".format(self._protocol, self._ip, self._port, LESS_FUNC_ENDPOINT)
+ parameters = {"offset": limit} if limit else None
+ self.logger.debug("Simulator address: %s", simulator_address)
+ try:
+ response = requests.get(url = simulator_address, params = parameters)
+ self._log_json_response(response)
+ except requests.ConnectionError:
+ self.logger.error("Failed to establish connection with {}".format(simulator_address))
+
+ def get_config(self, module_name: str=None, container:str=None)-> None:
+ self.logger.info("Attempting to run get-config")
+ simulator_address = self._create_get_endpoint(module_name, container)
+ self.logger.debug("Simulator address: %s", simulator_address)
+ try:
+ response = requests.get(simulator_address)
+ self._log_string_response(response)
+ except requests.ConnectionError:
+ self.logger.error("Failed to establish connection with {}".format(simulator_address))
+
+ def load_yang_model(self, module_name: str, yang_model_path: str, config_path: str) -> None:
+ self.logger.info(
+ "Attempting to load new yang model with its initial configuration")
+ simulator_address = "{}://{}:{}{}".format(self._protocol, self._ip, self._port, MODEL_ENDPOINT.format(module_name))
+ files = {"yangModel": open(yang_model_path, "rb"),
+ "initialConfig": open(config_path, "rb")}
+ self.logger.debug("Simulator address: %s", simulator_address)
+
+ try:
+ response = requests.post(simulator_address, files=files)
+ self._log_string_response(response)
+ except requests.ConnectionError:
+ self.logger.error("Failed to establish connection with {}".format(simulator_address))
+
+ def delete_yang_model(self, model_name: str) -> None:
+ self.logger.info(
+ "Attempting to delete a yang model")
+ simulator_address = "{}://{}:{}{}".format(self._protocol, self._ip, self._port, MODEL_ENDPOINT.format(model_name))
+ self.logger.debug("Simulator address: %s", simulator_address)
+
+ try:
+ response = requests.delete(simulator_address)
+ self._log_string_response(response)
+ except requests.ConnectionError:
+ self.logger.error("Failed to establish connection with {}".format(simulator_address))
+
+ def edit_config(self, new_config_path: str):
+ self.logger.info("Attempting to apply new configuration")
+ simulator_address = "{}://{}:{}{}".format(self._protocol, self._ip, self._port, EDIT_CONFIG_ENDPOINT)
+ files = {"editConfigXml": open(new_config_path,"rb")}
+ self.logger.debug("Simulator address: %s", simulator_address)
+
+ try:
+ response = requests.post(simulator_address, files=files)
+ self._log_string_response(response)
+ except requests.ConnectionError:
+ self.logger.error("Failed to establish connection with {}".format(simulator_address))
+
+ def _log_json_response(self, response: Response) ->None:
+ self.logger.info("Response status: %d", response.status_code)
+ self.logger.info(" ----- HEAD -----")
+ for message in response.json():
+ self.logger.info("{}: {}".format(str(message['timestamp']), message['configuration']))
+ self.logger.info(" ----- END ------")
+ self.logger.debug(response.headers)
+
+ def _configure_logger(self, verbose):
+ logging_conf = os.path.join(sys.prefix, 'logging.ini')
+ if os.path.exists(logging_conf):
+ logging.config.fileConfig(logging_conf)
+ else:
+ print("Couldn't find logging.ini, using default logger config")
+ self.logger = logging.getLogger()
+ self.logger.setLevel(logging.DEBUG if verbose else logging.INFO)
+
+ def _log_string_response(self, response: Response)->None:
+ self.logger.info("Response status: %d", response.status_code)
+ self.logger.info(response.text)
+ self.logger.debug(response.headers)
+
+ def _create_get_endpoint(self, module_name: str, container: str):
+ endpoint = "{}://{}:{}{}".format(self._protocol, self._ip, self._port,
+ GET_CONFIG_ENDPOINT)
+ if module_name and container:
+ endpoint = endpoint + "/{}/{}".format(module_name, container)
+ elif (not module_name and container) or (module_name and not container):
+ raise AttributeError(
+ "Both module_name and container must be present or absent")
+ return endpoint
+
+def create_argument_parser():
+ parser = argparse.ArgumentParser(description="Netconf Simulator Command Line Interface. ")
+ subparsers = parser.add_subparsers(title="Available actions")
+ tailf_parser = subparsers.add_parser("tailf",
+ description="Method which allows user to view N last lines of configuration changes")
+
+ __configure_tailf_like_parser(tailf_parser)
+ less_parser = subparsers.add_parser("less", description="Method which allows user to traverse configuration changes")
+ __configure_less_like_parser(less_parser)
+ cm_history_parser = subparsers.add_parser("cm-history",
+ description="Method which allows user to view all configuration changes")
+ __configure_cm_history_parser(cm_history_parser)
+
+ load_model_parser = subparsers.add_parser("load-model")
+ __configure_load_model_parser(load_model_parser)
+
+ delete_model_parser = subparsers.add_parser("delete-model")
+ __configure_delete_model_parser(delete_model_parser)
+
+ get_config_parser = subparsers.add_parser("get-config")
+ __configure_get_config_parser(get_config_parser)
+ edit_config_parser = subparsers.add_parser("edit-config")
+ __configure_edit_config_parser(edit_config_parser)
+ return parser
+
+
+def run_tailf(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose)
+ client.tailf_like_func()
+
+
+def run_get_cm_history(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose, port=DEFAULT_INTERNAL_SIM_PORT)
+ client.get_cm_history()
+
+
+def run_less(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose, port=DEFAULT_INTERNAL_SIM_PORT)
+ client.less_like_func(args.limit)
+
+
+def run_load_model(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose,
+ port=DEFAULT_INTERNAL_SIM_PORT)
+ client.load_yang_model(args.module_name, args.yang_model, args.config)
+
+
+def run_delete_model(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose,
+ port=DEFAULT_INTERNAL_SIM_PORT)
+ client.delete_yang_model(args.model_name)
+
+
+def run_get_config(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose, port=DEFAULT_INTERNAL_SIM_PORT)
+ client.get_config(args.module_name, args.container)
+
+
+def run_edit_config(args):
+ client = NetconfSimulatorClient(args.address, verbose=args.verbose, port=DEFAULT_INTERNAL_SIM_PORT)
+ client.edit_config(args.config)
+
+
+def __configure_tailf_like_parser(tailf_func_parser):
+ tailf_func_parser.add_argument("--address", required=True, help="IP address of simulator")
+ tailf_func_parser.add_argument("--verbose", action='store_true',
+ help="Displays additional logs")
+ tailf_func_parser.set_defaults(func=run_tailf)
+
+
+def __configure_less_like_parser(less_func_parser):
+ less_func_parser.add_argument("--address", required=True, help="IP address of simulator")
+ less_func_parser.add_argument("--limit", help="Limit of configurations to retrieve")
+ less_func_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ less_func_parser.set_defaults(func=run_less)
+
+
+def __configure_cm_history_parser(cm_history_parser):
+ cm_history_parser.add_argument("--address", required=True, help="IP address of simulator")
+ cm_history_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ cm_history_parser.set_defaults(func=run_get_cm_history)
+
+
+def __configure_load_model_parser(load_model_parser):
+ load_model_parser.add_argument("--address", required=True, help="IP address of simulator")
+ load_model_parser.add_argument("--module-name", required=True, help="Module name corresponding to yang-model")
+ load_model_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ load_model_parser.add_argument("--yang-model", required=True, help="Path to file with yang model")
+ load_model_parser.add_argument("--config", required=True, help="Path to file with initial xml config")
+ load_model_parser.set_defaults(func=run_load_model)
+
+
+def __configure_delete_model_parser(delete_model_parser):
+ delete_model_parser.add_argument("--address", required=True, help="IP address of simulator")
+ delete_model_parser.add_argument("--model-name", required=True, help="YANG model name to delete")
+ delete_model_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ delete_model_parser.set_defaults(func=run_delete_model)
+
+
+def __configure_get_config_parser(get_config_parser):
+ get_config_parser.add_argument("--address", required=True, help="IP address of simulator")
+ get_config_parser.add_argument("--verbose", action='store_true',help="Displays additional logs")
+ get_config_parser.add_argument("--module-name", help="Module name corresponding to yang-model", default=None)
+ get_config_parser.add_argument("--container", help="Container name corresponding to module name", default=None)
+ get_config_parser.set_defaults(func=run_get_config)
+
+
+def __configure_edit_config_parser(edit_config_parser):
+ edit_config_parser.add_argument("--address", required=True, help="IP address of simulator")
+ edit_config_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ edit_config_parser.add_argument("--config", required=True, help="Path to file with xml config to apply")
+ edit_config_parser.set_defaults(func=run_edit_config)
+
+
+if __name__ == "__main__":
+ argument_parser = create_argument_parser()
+ result = argument_parser.parse_args()
+ if hasattr(result, 'func'):
+ result.func(result)
+ else:
+ argument_parser.parse_args(['-h'])
diff --git a/simulator-cli/cli/nf_simulator.py b/simulator-cli/cli/nf_simulator.py
new file mode 100644
index 0000000..1964b6d
--- /dev/null
+++ b/simulator-cli/cli/nf_simulator.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python3
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2021 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+import argparse
+import http.client
+import json
+import logging
+import ntpath
+from typing import Dict
+
+SEND_PERIODIC_EVENT_ENDPOINT = "/simulator/start"
+SEND_ONE_TIME_EVENT_ENDPOINT = "/simulator/event"
+CONFIG_ENDPOINT = "/simulator/config"
+LIST_TEMPLATES_ENDPOINT = "/template/list"
+GET_TEMPLATE_BY_NAME_ENDPOINT = "/template/get"
+UPLOAD_TEMPLATE_NOFORCE = "/template/upload"
+UPLOAD_TEMPLATE_FORCE = "/template/upload?override=true"
+FILTER_TEMPLATES_ENDPOINT = "/template/search"
+
+logging.basicConfig()
+
+
+class Messages(object):
+ OVERRIDE_VALID_ONLY_WITH_UPLOAD = "--override is valid only with --upload parameter"
+
+
+class SimulatorParams(object):
+ def __init__(self, repeats: int = 1, interval: int = 1, ves_server_url: str = None) -> None:
+ self.repeats_count = repeats
+ self.repeats_interval = interval
+ self.ves_server_url = ves_server_url
+
+ def to_json(self) -> Dict:
+ to_return = {"repeatCount": self.repeats_count,
+ "repeatInterval": self.repeats_interval}
+ if self.ves_server_url:
+ to_return["vesServerUrl"] = self.ves_server_url
+ return to_return
+
+ def __repr__(self) -> str:
+ return str(self.to_json())
+
+
+class PersistedEventRequest(object):
+ def __init__(self, simulator_params: SimulatorParams, template: str, patch: Dict = None) -> None:
+ self.params = simulator_params
+ self.template = template
+ self.patch = patch or {}
+
+ def to_json(self) -> Dict:
+ return {"simulatorParams": self.params, "templateName": self.template,
+ "patch": self.patch}
+
+ def __repr__(self) -> str:
+ return str(self.to_json())
+
+
+class FullEventRequest(object):
+ def __init__(self, event_body: Dict, ves_server_url: str = None) -> None:
+ self.event_body = event_body
+ self.ves_server_url = ves_server_url or ""
+
+ def to_json(self) -> Dict:
+ return {"vesServerUrl": self.ves_server_url, "event": self.event_body}
+
+ def __repr__(self) -> str:
+ return str(self.to_json())
+
+
+class TemplateUploadRequest(object):
+ def __init__(self, template_name: str, template_body: Dict) -> None:
+ self.template_name = template_name
+ self.template_body = template_body
+
+ def to_json(self) -> Dict:
+ return {"name": self.template_name, "template": self.template_body}
+
+ def __repr__(self) -> str:
+ return str(self.to_json())
+
+
+class SimulatorClient(object):
+ def __init__(self, ip: str, port: int = 5000, verbose: bool = False) -> None:
+ self._ip = ip
+ self._port = port
+ self.logger = logging.getLogger()
+ self.logger.setLevel(logging.DEBUG if verbose else logging.INFO)
+
+ def send_event(self, request: PersistedEventRequest) -> None:
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ self.logger.info("Attempting to send event")
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, SEND_PERIODIC_EVENT_ENDPOINT)
+ self.logger.debug("REQUEST %s", request)
+
+ connection.request("POST", SEND_PERIODIC_EVENT_ENDPOINT, body=json.dumps(request, cls=RequestSerializer),
+ headers={"Content-Type": "application/json"})
+
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def send_one_time_event(self, request: FullEventRequest) -> None:
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ self.logger.info("Attempting to send one time event")
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, SEND_ONE_TIME_EVENT_ENDPOINT)
+ self.logger.debug("REQUEST %s", request.to_json())
+
+ connection.request("POST", SEND_ONE_TIME_EVENT_ENDPOINT, body=json.dumps(request.to_json()),
+ headers={"Content-Type": "application/json"})
+
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def get_configuration(self) -> None:
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ self.logger.info("Attempting to retrieve Simulator configuration")
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, CONFIG_ENDPOINT)
+ connection.request("GET", CONFIG_ENDPOINT)
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def edit_configuration(self, ves_server_url: str) -> None:
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ self.logger.info("Attempting to update Simulator configuration")
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, CONFIG_ENDPOINT)
+ request = {"vesServerUrl": ves_server_url}
+ self.logger.debug("REQUEST %s", request)
+ connection.request("PUT", CONFIG_ENDPOINT, body=json.dumps(request),
+ headers={"Content-Type": "application/json"})
+
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def _log_response(self, response: http.client.HTTPResponse):
+ self.logger.info("Response status: %s ", response.status)
+ self.logger.info(response.read().decode())
+ self.logger.debug(response.headers)
+
+ def list_templates(self):
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ self.logger.info("Attempting to retrieve all templates")
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, LIST_TEMPLATES_ENDPOINT)
+ connection.request("GET", LIST_TEMPLATES_ENDPOINT)
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def get_template_by_name(self, name):
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ endpoint = GET_TEMPLATE_BY_NAME_ENDPOINT + "/" + name
+ self.logger.info("Attempting to retrieve template by name: '%s'", name)
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, endpoint)
+ connection.request("GET", endpoint)
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def upload_template(self, template_request, force):
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ endpoint = UPLOAD_TEMPLATE_FORCE if force else UPLOAD_TEMPLATE_NOFORCE
+ self.logger.info("Attempting to upload template: '%s'", template_request)
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, endpoint)
+ connection.request("POST", endpoint,
+ body=json.dumps(template_request.to_json()),
+ headers={"Content-Type": "application/json"})
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+ def search_for_templates(self, filter_criteria: str):
+ connection = http.client.HTTPConnection(self._ip, self._port)
+ self.logger.debug("Simulator address: ip %s, port %s, endpoint %s", self._ip, self._port, FILTER_TEMPLATES_ENDPOINT)
+ filter_request = {"searchExpr": json.loads(filter_criteria)}
+ self.logger.debug("Filter criteria: %s", str(filter_criteria))
+ connection.request("POST", FILTER_TEMPLATES_ENDPOINT,
+ body=json.dumps(filter_request),
+ headers={"Content-Type": "application/json"})
+ response = connection.getresponse()
+
+ self._log_response(response)
+ connection.close()
+
+
+class RequestSerializer(json.JSONEncoder):
+ def default(self, o):
+ return o.to_json() if (isinstance(o, SimulatorParams) or isinstance(o, PersistedEventRequest)) else o
+
+
+
+def create_argument_parser():
+ parser = argparse.ArgumentParser(description="NF Simulator Command Line Interface. ")
+ subparsers = parser.add_subparsers(title="Available actions")
+ send_parser = subparsers.add_parser("send",
+ description="Method which allows user to trigger simulator to start sending "
+ "events. Available options: [template, event]")
+
+ send_subparsers = send_parser.add_subparsers()
+ one_time_send_event_parser = send_subparsers.add_parser("event", description="Option for direct, one-time event sending to VES. This option does not require having corresponging template.")
+ __configure_one_time_send_parser(one_time_send_event_parser)
+ persisted_send_event_parser = send_subparsers.add_parser("template")
+ __configure_persisted_send_parser(persisted_send_event_parser)
+
+ configure_parser = subparsers.add_parser("configure", description="Method which allows user to set new default "
+ "value for VES Endpoint")
+ __configure_config_parser(configure_parser)
+
+ get_config_parser = subparsers.add_parser("get-config",
+ description="Method which allows user to view simulator configuration")
+ __configure_get_config_parser(get_config_parser)
+
+ template_config_parser = subparsers.add_parser("template", description="Template management operations")
+ __configure_template_parser(template_config_parser)
+
+ template_filter_parser = subparsers.add_parser("filter", description="Method for searching through templates to find those satisfying given criteria")
+ __configure_template_filter_parser(template_filter_parser)
+
+ return parser
+
+
+def _perform_send_action(args):
+ if (not args.interval and args.repeats) or (args.interval and not args.repeats):
+ raise Exception("Either both repeats and interval must be present or missing")
+
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ client.send_event(_create_scheduled_event_request(args))
+
+
+def _perform_one_time_send_action(args):
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ client.send_one_time_event(_create_one_time_event_request(args.filepath, args.ves_server_url))
+
+
+def get_configuration(args):
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ client.get_configuration()
+
+
+def edit_configuration(args):
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ client.edit_configuration(args.ves_server_url)
+
+
+def perform_template_action(args):
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ if args.list:
+ client.list_templates()
+ elif args.get_content:
+ client.get_template_by_name(args.get_content)
+ elif args.upload:
+ client.upload_template(_create_upload_template_request(args.upload), args.override)
+ elif args.force:
+ raise Exception(Messages.OVERRIDE_VALID_ONLY_WITH_UPLOAD)
+
+
+def list_all_templates(args):
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ client.list_templates()
+
+
+def filter_templates(args):
+ client = SimulatorClient(args.address, verbose=args.verbose)
+ client.search_for_templates(args.criteria)
+
+
+def _create_upload_template_request(template_filename):
+ with open(template_filename) as json_template:
+ template_body = json.load(json_template)
+ return TemplateUploadRequest(path_leaf(template_filename), template_body)
+
+
+def _create_scheduled_event_request(args):
+ simulator_params = SimulatorParams(args.repeats, args.interval, args.ves_server_url)
+ return PersistedEventRequest(simulator_params, args.name, json.loads(args.patch) if args.patch else {})
+
+
+def _create_one_time_event_request(event_filename, ves_server_url):
+ with open(event_filename) as json_event:
+ event_body = json.load(json_event)
+ return FullEventRequest(event_body, ves_server_url)
+
+
+def __configure_persisted_send_parser(send_parser):
+ send_parser.add_argument("--address", required=True, help="IP address of simulator")
+ send_parser.add_argument("--name", required=True, help="Name of template file which should be used as a base for event")
+ send_parser.add_argument("--patch", help="Json which should be merged into template to override parameters")
+ send_parser.add_argument("--repeats", help="Number of events to be send", type=int)
+ send_parser.add_argument("--interval", help="Interval between two consecutive events (in seconds)", type=int)
+ send_parser.add_argument("--ves_server_url",
+ help="Well-formed URL which will override current VES endpoint stored in simulator's DB")
+ send_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ send_parser.set_defaults(func=_perform_send_action)
+
+
+def __configure_one_time_send_parser(send_parser):
+ send_parser.add_argument("--address", required=True, help="IP address of simulator")
+ send_parser.add_argument("--filepath", required=True, help="Name of file with complete event for direct sending.")
+ send_parser.add_argument("--ves_server_url",
+ help="Well-formed URL which will override current VES endpoint stored in simulator's DB")
+ send_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ send_parser.set_defaults(func=_perform_one_time_send_action)
+
+
+def __configure_config_parser(config_parser):
+ config_parser.add_argument("--address", required=True, help="IP address of simulator")
+ config_parser.add_argument("--ves-server-url", required=True,
+ help="Well-formed URL which should be set as a default VES Server URL in simulator")
+ config_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ config_parser.set_defaults(func=edit_configuration)
+
+
+def __configure_get_config_parser(get_config_parser):
+ get_config_parser.add_argument("--address", required=True, help="IP address of simulator")
+ get_config_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ get_config_parser.set_defaults(func=get_configuration)
+
+
+def __configure_template_parser(template_config_parser):
+ group = template_config_parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--list", action='store_true', help="List all templates")
+ group.add_argument("--get-content", help="Gets the template by name")
+ group.add_argument("--upload", help="Uploads the template given in parameter file.")
+
+ template_config_parser.add_argument("--override", action='store_true', help="Overwrites the template in case it exists.")
+ template_config_parser.add_argument("--address", required=True, help="IP address of simulator")
+ template_config_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ template_config_parser.set_defaults(func=perform_template_action)
+
+
+def __configure_template_filter_parser(template_filter_parser):
+ template_filter_parser.add_argument("--criteria", required=True, help="Json string with key-value search criteria")
+ template_filter_parser.add_argument("--address", required=True, help="IP address of simulator")
+ template_filter_parser.add_argument("--verbose", action='store_true', help="Displays additional logs")
+ template_filter_parser.set_defaults(func=filter_templates)
+
+
+def path_leaf(path):
+ head, tail = ntpath.split(path)
+ return tail or ntpath.basename(head)
+
+
+if __name__ == "__main__":
+ argument_parser = create_argument_parser()
+ result = argument_parser.parse_args()
+ if hasattr(result, 'func'):
+ result.func(result)
+ else:
+ argument_parser.parse_args(['-h'])