├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING ├── NOTICE ├── README.md ├── azure-pipelines.yml ├── build.sh ├── iofog ├── __init__.py ├── microservices │ ├── __init__.py │ ├── client.py │ ├── definitions.py │ ├── exception.py │ ├── httpclient.py │ ├── iomessage.py │ ├── listener.py │ ├── log.py │ ├── util.py │ └── wsclient.py └── rest │ ├── __init__.py │ └── controller │ ├── __init__.py │ ├── client.py │ └── request.py ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── bootstrap.bash ├── conf ├── app.yaml ├── ecn.yaml └── vars.bash ├── conftest.py ├── log.py ├── microservices.py ├── requirements.txt ├── rest.py ├── run.bash ├── setup.bash └── teardown.bash /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask instance folder 59 | instance/ 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v3.0.0-alpha1] - 23 March 2021 4 | 5 | * Remove deploy module 6 | * Add rest.controller module 7 | * Rename client module to microservices 8 | * Add microservices.log module 9 | 10 | ## [v1.3.0] 11 | 12 | * Ability to deploy ioFog microservices, and connections through SDK are now available. 13 | * This can be imported through iofog.deploy options. 14 | * Previous standard iofog can be imported through iofog.microservices 15 | * Added standards for rest calls made to ioFog under deploy.create_rest_call 16 | * Fixed issue with python3 object handling 17 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | # Contributing to Eclipse ioFog 2 | 3 | Thanks for your interest in this project. 4 | 5 | ## Project description 6 | 7 | The Eclipse ioFog set of technologies is a fog computing layer that can be 8 | installed on any hardware running Linux. Once installed, it provides a universal 9 | runtime for microservices to run on the edge. In addition to a common runtime, 10 | ioFog also provides a set of useful services including a message bus, dynamic 11 | configuration of the microservices, and remote debugging. 12 | 13 | * https://projects.eclipse.org/projects/iot.iofog 14 | 15 | ## Developer resources 16 | 17 | Information regarding source code management, builds, coding standards, and 18 | more. 19 | 20 | * https://projects.eclipse.org/projects/iot.iofog/developer 21 | 22 | The project maintains the following source code repositories 23 | 24 | * https://github.com/ioFog 25 | * http://git.eclipse.org/c/iofog/org.eclipse.iofog.git 26 | 27 | This project uses Bugzilla to track ongoing development and issues. 28 | 29 | * Search for issues: https://eclipse.org/bugs/buglist.cgi?product=IoFog 30 | * Create a new report: https://eclipse.org/bugs/enter_bug.cgi?product=IoFog 31 | 32 | Be sure to search for existing bugs before you create another one. Remember that 33 | contributions are always welcome! 34 | 35 | ## Eclipse Contributor Agreement 36 | 37 | Before your contribution can be accepted by the project team contributors must 38 | electronically sign the Eclipse Contributor Agreement (ECA). 39 | 40 | * http://www.eclipse.org/legal/ECA.php 41 | 42 | Commits that are provided by non-committers must have a Signed-off-by field in 43 | the footer indicating that the author is aware of the terms by which the 44 | contribution has been provided to the project. The non-committer must 45 | additionally have an Eclipse Foundation account and must have a signed Eclipse 46 | Contributor Agreement (ECA) on file. 47 | 48 | For more information, please see the Eclipse Committer Handbook: 49 | https://www.eclipse.org/projects/handbook/#resources-commit 50 | 51 | ## Contact 52 | 53 | Contact the project developers via the project's "dev" list. 54 | 55 | * https://dev.eclipse.org/mailman/listinfo/iofog-dev 56 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | # Notices for Eclipse ioFog 2 | 3 | This content is produced and maintained by the Eclipse ioFog project. 4 | 5 | * Project home: https://projects.eclipse.org/projects/iot.iofog 6 | 7 | ## Trademarks 8 | 9 | Eclipse ioFog is a trademark of the Eclipse Foundation. 10 | 11 | ## Copyright 12 | 13 | All content is the property of the respective authors or their employers. For 14 | more information regarding authorship of content, please consult the listed 15 | source code repository logs. 16 | 17 | ## Declared Project Licenses 18 | 19 | This program and the accompanying materials are made available under the terms 20 | of the Eclipse Public License v. 2.0 which is available at 21 | http://www.eclipse.org/legal/epl-2.0. 22 | 23 | SPDX-License-Identifier: EPL-2.0 24 | 25 | ## Source Code 26 | 27 | The project maintains the following source code repositories: 28 | 29 | * https://github.com/ioFog 30 | * http://git.eclipse.org/c/iofog/org.eclipse.iofog.git 31 | 32 | ## Third-party Content 33 | 34 | ## Cryptography 35 | 36 | Content may contain encryption software. The country in which you are currently 37 | may have restrictions on the import, possession, and use, and/or re-export to 38 | another country, of encryption software. BEFORE using any encryption software, 39 | please check the country's laws, regulations and policies concerning the import, 40 | possession, or use, and re-export of encryption software, to see if this is 41 | permitted. 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ioFog Python SDK 2 | 3 | This SDK provides the following modules: 4 | * microservices: Clients for Microservices to talk to ioFog (e.g. ioMessage and Web Socket clients) 5 | * rest.controller: Client for Controller REST API 6 | 7 | ## Installation 8 | 9 | Install python package: 10 | ```bash 11 | sudo python3 -m pip install iofog 12 | ``` 13 | 14 | ## Microservices 15 | 16 | This module lets you easily build an ioElement. It gives you all the functionality to interact with ioFog via Local API. It contains all necessary methods for IoMessage transformation as well. 17 | 18 | - send new message to ioFog (post_message) 19 | - fetch next unread messages from ioFog (get_next_messages) 20 | - fetch messages for time period and list of accessible publishers (get_next_messages_from_publishers_within_timeframe) 21 | - get config options (get_config) 22 | - get Agent's Edge Resources (get_edge_resources) 23 | - create IoMessage, encode(decode) to(from) raw bytes, marshall(unmarshall) into(from) JSON object (IoMessage class methods) 24 | - connect to ioFog Control Channel via WebSocket (establish_control_ws_connection) 25 | - connect to ioFog Message Channel via WebSocket (establish_message_ws_connection) and publish new message via this channel (post_message_via_socket) 26 | 27 | ### Usage 28 | 29 | Import iofog client and additional classes to your project: 30 | ```python 31 | from iofog.microservices.client import Client 32 | from iofog.microservices.exception import IoFogException 33 | from iofog.microservices.iomessage import IoMessage 34 | from iofog.microservices.listener import * 35 | ``` 36 | 37 | Create IoFog client with default settings: 38 | ```python 39 | try: 40 | client = Client() 41 | except IoFogException as e: 42 | # client creation failed, e contains description 43 | ``` 44 | 45 | Or specify host, port, and container id explicitly: 46 | ```python 47 | try: 48 | client = Client(id='container_id', host='iofog_host', port=6666) 49 | except IoFogException as e: 50 | # client creation failed, e contains description 51 | ``` 52 | 53 | ##### REST calls 54 | 55 | Get list of next unread IoMessages: 56 | ```python 57 | try: 58 | messages = client.get_next_messages() 59 | except IoFogException as e: 60 | # some error occurred, e contains description 61 | ``` 62 | 63 | Post new IoMessage to ioFog via REST call: 64 | ```python 65 | msg=IoMessage() 66 | msg.infotype="infotype" 67 | msg.infoformat="infoformat" 68 | msg.contentdata="sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr" 69 | msg.contextdata="" 70 | msg.tag="tag" 71 | msg.groupid="groupid" 72 | msg.authid="authid" 73 | msg.authgroup="authgroup" 74 | msg.hash="hash" 75 | msg.previoushash="previoushash" 76 | msg.nonce="nonce" 77 | 78 | try: 79 | receipt = client.post_message(msg) 80 | except IoFogException, e: 81 | # some error occurred, e contains description 82 | ``` 83 | 84 | Get an array of IoMessages from specified publishers within given timeframe: 85 | ```python 86 | query = { 87 | 'timeframestart': 1234567890123, 88 | 'timeframeend': 1234567890123, 89 | 'publishers': ['sefhuiw4984twefsdoiuhsdf', 'd895y459rwdsifuhSDFKukuewf', 'SESD984wtsdidsiusidsufgsdfkh'] 90 | } 91 | try: 92 | query_response = client.get_next_messages_from_publishers_within_timeframe(query) 93 | except IoFogException, e: 94 | # some error occurred, e contains description 95 | ``` 96 | 97 | Get container's config: 98 | ```python 99 | try: 100 | config = client.get_config() 101 | except IoFogException, ex: 102 | # some error occurred, ex contains description 103 | ``` 104 | 105 | Get Agent's Edge Resources: 106 | ```python 107 | try: 108 | edge_resources = client.get_edge_resources() 109 | except IoFogException, ex: 110 | # some error occurred, ex contains description 111 | ``` 112 | 113 | ##### WebSocket calls 114 | 115 | To use websocket connections you should implement listeners as follows: 116 | ```python 117 | class MyControlListener(IoFogControlWsListener): 118 | def on_control_signal(self): 119 | # do smth on control signal 120 | def on_edge_resources_signal(self): 121 | # Agent's Edge Resource list has been updated 122 | 123 | 124 | class MyMessageListener(IoFogMessageWsListener): 125 | def on_receipt(self, message_id, timestamp): 126 | # do smth with message receipt 127 | 128 | def on_message(self, io_msg): 129 | # do smth with new message 130 | 131 | ``` 132 | 133 | After that you can establish websocket connections: 134 | ```python 135 | client.establish_message_ws_connection(MyMessageListener()) 136 | client.establish_control_ws_connection(MyControlListener()) 137 | ``` 138 | Each of those connections will be managed in a separate thread. 139 | 140 | 141 | After successful connection to message websocket you can send to it: 142 | ```python 143 | client.post_message_via_socket(io_msg_instance) 144 | ``` 145 | 146 | 147 | ##### Message utils 148 | 149 | Construct IoMessage from JSON(both json string and python dictionary are acceptable): 150 | ```python 151 | msg = IoMessage.from_json(json_msg) 152 | ``` 153 | 154 | IoMessage to JSON: 155 | ```python 156 | json_str = io_msg_instance.to_json() 157 | ``` 158 | 159 | Construct IoMessage from raw bytes: 160 | ```python 161 | msg = IoMessage.from_bytearray([0, 4, ...]) 162 | ``` 163 | 164 | Pack IoMessage into bytearray: 165 | ```python 166 | msg_bytes = io_msg_instance.to_bytearray() 167 | ``` 168 | 169 | #### Logging 170 | 171 | ```python 172 | from iofog.microservices.log import Logger 173 | 174 | log = Logger("logger1") 175 | log.debug("hello") 176 | log.info("world") 177 | log.warning("good") 178 | log.error("bye") 179 | ``` 180 | 181 | ## Controller REST API Client 182 | 183 | This module provides a client for talking to [ioFog Controller's REST API](https://iofog.org/docs/2/reference-controller/rest-api.html). 184 | 185 | ### Usage 186 | 187 | ```python 188 | # Import Client class 189 | from iofog.rest.controller.client import Client 190 | 191 | # Instantiate Client 192 | client = Client( 193 | host="123.123.123.123", 194 | port=51121, 195 | email="hello@world.io" 196 | password="2uhi40ghas9") 197 | 198 | # Get Controller status and API version 199 | status = client.get_status() 200 | print(status['versions']['controller']) 201 | 202 | # Create an Agent 203 | name = "agent1" 204 | host = "123.123.123.124" 205 | resp = client.create_agent(name, host) 206 | 207 | # Upgrade an Agent 208 | resp = client.upgrade_agent(name) 209 | 210 | # Create an Application 211 | yaml_file = "/tmp/app.yaml" 212 | resp = client.create_app_from_yaml(yaml_file) 213 | 214 | # Get an Application 215 | app_name = "app1" 216 | resp = client.get_app(app_name) 217 | 218 | # Delete an Application 219 | resp = client.delete_app(app_name) 220 | ``` 221 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | tags: 3 | include: 4 | - v* 5 | branches: 6 | include: 7 | - develop 8 | - release* 9 | paths: 10 | exclude: 11 | - README.md 12 | - CHANGELOG.md 13 | - CONTRIBUTING 14 | - NOTICE 15 | 16 | variables: 17 | group: 'pipelines' 18 | 19 | jobs: 20 | - job: Linux 21 | pool: 22 | vmImage: 'ubuntu-16.04' 23 | steps: 24 | - task: UsePythonVersion@0 25 | inputs: 26 | versionSpec: '3.7' 27 | addToPath: true 28 | architecture: 'x64' 29 | 30 | - script: | 31 | set -e 32 | test/bootstrap.bash 33 | displayName: 'Bootstrap tests' 34 | 35 | - script: | 36 | set -e 37 | test/setup.bash 38 | displayName: 'Setup test environment' 39 | 40 | - script: | 41 | set -e 42 | test/run.bash 43 | displayName: 'Run tests' 44 | 45 | - script: | 46 | test/teardown.bash 47 | displayName: 'Teardown test environment' 48 | 49 | - task: CopyFiles@2 50 | inputs: 51 | SourceFolder: $(System.DefaultWorkingDirectory) 52 | TargetFolder: $(Build.ArtifactStagingDirectory) 53 | Contents: | 54 | setup.py 55 | requirements.txt 56 | README.md 57 | iofog/**/* 58 | OverWrite: true 59 | displayName: 'Copy artifacts' 60 | 61 | - task: PublishBuildArtifacts@1 62 | inputs: 63 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 64 | ArtifactName: 'sdk' 65 | displayName: 'Publish artefacts' 66 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PYTHON_VERSION=$1 4 | if [[ ${PYTHON_VERSION} == "python3" ]]; then 5 | PYTHON_TAG="3.7-stretch" 6 | else 7 | PYTHON_TAG="2.7-stretch" 8 | fi 9 | 10 | # Build Python Images 11 | docker build --build-arg TAG_NAME=${PYTHON_TAG} -t iofog/test-python-sdk-send:${PYTHON_VERSION} -f ./Docker/Dockerfile.send . 12 | docker build --build-arg TAG_NAME=${PYTHON_TAG} -t iofog/test-python-sdk-recieve:${PYTHON_VERSION} -f ./Docker/Dockerfile.recieve . 13 | -------------------------------------------------------------------------------- /iofog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-iofog/iofog-python-sdk/647c767e54d6fb535a18886b61354f7f0dbbf29a/iofog/__init__.py -------------------------------------------------------------------------------- /iofog/microservices/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-iofog/iofog-python-sdk/647c767e54d6fb535a18886b61354f7f0dbbf29a/iofog/microservices/__init__.py -------------------------------------------------------------------------------- /iofog/microservices/client.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | import logging 12 | import os 13 | 14 | import subprocess 15 | 16 | from iofog.microservices.httpclient import IoFogHttpClient 17 | from iofog.microservices.definitions import * 18 | from iofog.microservices.wsclient import IoFogControlWsClient, IoFogMessageWsClient 19 | from iofog.microservices.listener import * 20 | from iofog.microservices.exception import * 21 | 22 | class Client: 23 | def __init__(self, id=None, ssl=None, host=None, port=None): 24 | self.logger = logging.getLogger(IOFOG_LOGGER) 25 | ch = logging.StreamHandler() 26 | formatter = logging.Formatter( 27 | '%(levelname)5s [%(asctime)-15s] %(module)10s - - %(message)s') 28 | ch.setFormatter(formatter) 29 | self.logger.addHandler(ch) 30 | if id: 31 | self.id = id 32 | else: 33 | self.id = os.getenv(SELFNAME) 34 | if not self.id: 35 | raise IoFogException('Cannot create client with empty id: ' + 36 | SELFNAME + ' environment variable is not set') 37 | 38 | if ssl: 39 | self.ssl = ssl 40 | else: 41 | self.ssl = os.getenv(SSL) 42 | if not self.ssl: 43 | self.logger.info('Empty or malformed ' + SSL 44 | + ' environment variable. Using default value of ' + str(SSL_DEFAULT)) 45 | self.ssl = SSL_DEFAULT 46 | 47 | if host: 48 | self.host = host 49 | else: 50 | self.host = IOFOG 51 | with open(os.devnull, 'w') as FNULL: 52 | resp = subprocess.call(['ping', '-c', '3', self.host], stdout=FNULL, stderr=FNULL) 53 | if resp: 54 | self.logger.info('Host ' + IOFOG + ' is unreachable. Switching to ' + HOST_DEFAULT) 55 | self.host = HOST_DEFAULT 56 | 57 | self.port = port if port else PORT_IOFOG 58 | 59 | self.message_ws_client = None 60 | self.control_ws_client = None 61 | self.http_client = IoFogHttpClient(self.id, self.ssl, self.host, self.port) 62 | 63 | def establish_message_ws_connection(self, listener): 64 | if listener is not None and not isinstance(listener, IoFogMessageWsListener): 65 | raise IoFogException('Invalid listener instance') 66 | if self.message_ws_client: 67 | raise IoFogException('Connection has been already established') 68 | self.message_ws_client = IoFogMessageWsClient(self.id, self.ssl, self.host, 69 | self.port, URL_GET_MESSAGE_WS, listener) 70 | self.message_ws_client.connect() 71 | 72 | def establish_control_ws_connection(self, listener): 73 | if listener is not None and not isinstance(listener, IoFogControlWsListener): 74 | raise IoFogException('Invalid listener instance') 75 | if self.control_ws_client: 76 | raise IoFogException('Connection has been already established') 77 | self.control_ws_client = IoFogControlWsClient(self.id, self.ssl, self.host, 78 | self.port, URL_GET_CONTROL_WS, listener) 79 | self.control_ws_client.connect() 80 | 81 | def get_config(self): 82 | try: 83 | return self.http_client.get_config() 84 | except Exception as e: 85 | raise IoFogException(e) 86 | 87 | def get_edge_resources(self): 88 | try: 89 | return self.http_client.get_edge_resources() 90 | except Exception as e: 91 | raise IoFogException(e) 92 | 93 | def get_next_messages(self): 94 | try: 95 | return self.http_client.get_next_messages() 96 | except Exception as e: 97 | raise IoFogException(e) 98 | 99 | def get_next_messages_from_publishers_within_timeframe(self, query): 100 | assert TIME_FRAME_START in query and TIME_FRAME_END in query and PUBLISHERS in query, 'Wrong query parameters' 101 | query[ID] = self.id 102 | try: 103 | return self.http_client.get_next_messages_from_publishers_within_timeframe(query) 104 | except Exception as e: 105 | raise IoFogException(e) 106 | 107 | def post_message(self, io_msg): 108 | if not io_msg.version: 109 | io_msg.version = IO_MESSAGE_VERSION 110 | io_msg.publisher = self.id 111 | try: 112 | return self.http_client.post_message(io_msg) 113 | except Exception as e: 114 | raise IoFogException(e) 115 | 116 | def post_message_via_socket(self, io_msg): 117 | if not self.message_ws_client: 118 | raise IoFogException('Establish message websocket connection first!') 119 | while not self.message_ws_client.is_open: 120 | pass 121 | io_msg.id = '' 122 | io_msg.timestamp = 0 123 | io_msg.publisher = self.id 124 | if io_msg.version == 0: 125 | io_msg.version = IO_MESSAGE_VERSION 126 | try: 127 | self.message_ws_client.send_message(io_msg) 128 | except Exception as e: 129 | raise IoFogException(e) 130 | -------------------------------------------------------------------------------- /iofog/microservices/definitions.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | IOFOG = 'iofog' 12 | 13 | PORT_IOFOG = 54321 14 | SELFNAME = 'SELFNAME' 15 | SSL = 'SSL' 16 | SSL_DEFAULT = False 17 | HOST_DEFAULT = '127.0.0.1' 18 | IOFOG_LOGGER = 'iofog_logger' 19 | 20 | URL_GET_CONFIG = '/v2/config/get' 21 | URL_GET_EDGE_RESOURCES = '/v2/edgeResources' 22 | URL_GET_NEXT_MESSAGES = '/v2/messages/next' 23 | URL_GET_PUBLISHERS_MESSAGES = '/v2/messages/query' 24 | URL_POST_MESSAGE = '/v2/messages/new' 25 | URL_GET_CONTROL_WS = '/v2/control/socket/id/' 26 | URL_GET_MESSAGE_WS = '/v2/message/socket/id/' 27 | 28 | APPLICATION_JSON = 'application/json' 29 | HTTP = 'http' 30 | HTTPS = 'https' 31 | WS = 'ws' 32 | WSS = 'wss' 33 | 34 | CODE_ACK = 0xB 35 | CODE_CONTROL_SIGNAL = 0xC 36 | CODE_MSG = 0xD 37 | CODE_RECEIPT = 0xE 38 | CODE_EDGE_RESOURCE_SIGNAL = 0xF 39 | 40 | WS_ATTEMPT_LIMIT = 5 41 | IO_MESSAGE_VERSION = 4 42 | 43 | WS_CONNECT_TIMEOUT = 1 44 | PING_INTERVAL_SECONDS = 5 45 | CODE_BAD_REQUEST = 400 46 | 47 | ID = 'id' 48 | TAG = 'tag' 49 | GROUP_ID = 'groupid' 50 | VERSION = 'version' 51 | SEQUENCE_NUMBER = 'sequencenumber' 52 | SEQUENCE_TOTAL = 'sequencetotal' 53 | PRIORITY = 'priority' 54 | TIMESTAMP = 'timestamp' 55 | PUBLISHER = 'publisher' 56 | AUTH_ID = 'authid' 57 | AUTH_GROUP = 'authgroup' 58 | CHAIN_POSITION = 'chainposition' 59 | HASH = 'hash' 60 | PREVIOUS_HASH = 'previoushash' 61 | NONCE = 'nonce' 62 | DIFFICULTY_TARGET = 'difficultytarget' 63 | INFO_TYPE = 'infotype' 64 | INFO_FORMAT = 'infoformat' 65 | CONTEXT_DATA = 'contextdata' 66 | CONTENT_DATA = 'contentdata' 67 | 68 | TIME_FRAME_START = 'timeframestart' 69 | TIME_FRAME_END = 'timeframeend' 70 | PUBLISHERS = 'publishers' 71 | CONFIG = 'config' 72 | EDGE_RESOURCES = 'edgeResources' 73 | MESSAGES = 'messages' 74 | STATUS = 'status' 75 | COUNT = 'count' 76 | -------------------------------------------------------------------------------- /iofog/microservices/exception.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | class IoFogException(Exception): 12 | def __init__(self, *args, **kwargs): 13 | super(IoFogException, self).__init__(*args, **kwargs) 14 | 15 | 16 | class IoFogHttpException(IoFogException): 17 | def __init__(self, code, message): 18 | self.code = code 19 | self.message = message 20 | 21 | def __str__(self): 22 | return 'Error code: {}, reason: {}'.format(self.code, self.message) 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /iofog/microservices/httpclient.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | import json 12 | 13 | try: 14 | from urllib.error import HTTPError #for python 3 15 | except ImportError: 16 | from urllib2 import HTTPError #for python 2 17 | 18 | from iofog.microservices.definitions import * 19 | from iofog.microservices.util import make_post_request 20 | from iofog.microservices.iomessage import IoMessage 21 | from iofog.microservices.exception import IoFogHttpException 22 | 23 | 24 | class IoFogHttpClient: 25 | def __init__(self, container_id, ssl, host, port): 26 | protocol_rest = HTTP 27 | if ssl: 28 | protocol_rest = HTTPS 29 | 30 | self.url_base_rest = "{}://{}:{}".format(protocol_rest, host, port) 31 | self.url_get_config = self.url_base_rest + URL_GET_CONFIG 32 | self.url_get_edge_resources = self.url_base_rest + URL_GET_EDGE_RESOURCES 33 | self.url_get_next_messages = self.url_base_rest + URL_GET_NEXT_MESSAGES 34 | self.url_get_publishers_messages = self.url_base_rest + URL_GET_PUBLISHERS_MESSAGES 35 | self.url_post_message = self.url_base_rest + URL_POST_MESSAGE 36 | self.request_body_id = json.dumps({ 37 | ID: container_id 38 | }) 39 | 40 | def get_config(self): 41 | try: 42 | config_resp = make_post_request(self.url_get_config, APPLICATION_JSON, self.request_body_id) 43 | except HTTPError as e: 44 | raise IoFogHttpException(e.code, e.read()) 45 | return json.loads(config_resp[CONFIG]) 46 | 47 | def get_edge_resources(self): 48 | try: 49 | edge_resources_response = make_get_request(self.url_get_edge_resources, APPLICATION_JSON, self.request_body_id) 50 | except HTTPError as e: 51 | raise IoFogHttpException(e.code, e.read()) 52 | return json.loads(edge_resources_response[EDGE_RESOURCES]) 53 | 54 | def get_next_messages(self): 55 | try: 56 | next_messages_resp = make_post_request(self.url_get_next_messages, APPLICATION_JSON, self.request_body_id) 57 | except HTTPError as e: 58 | raise IoFogHttpException(e.code, e.read()) 59 | messages = [] 60 | for json_msg in next_messages_resp[MESSAGES]: 61 | messages.append(IoMessage.from_json(json_msg)) 62 | return messages 63 | 64 | def get_next_messages_from_publishers_within_timeframe(self, query): 65 | try: 66 | next_messages_resp = make_post_request(self.url_get_publishers_messages, APPLICATION_JSON, 67 | json.dumps(query)) 68 | except HTTPError as e: 69 | raise IoFogHttpException(e.code, e.read()) 70 | response = { 71 | TIME_FRAME_START: next_messages_resp[TIME_FRAME_START], 72 | TIME_FRAME_END: next_messages_resp[TIME_FRAME_END] 73 | } 74 | messages = [] 75 | for json_msg in next_messages_resp[MESSAGES]: 76 | messages.append(IoMessage.from_json(json_msg)) 77 | response[MESSAGES] = messages 78 | return response 79 | 80 | def post_message(self, io_msg): 81 | try: 82 | post_resp = make_post_request(self.url_post_message, APPLICATION_JSON, io_msg.to_json()) 83 | del post_resp[STATUS] 84 | return post_resp 85 | except HTTPError as e: 86 | raise IoFogHttpException(e.code, e.read()) 87 | -------------------------------------------------------------------------------- /iofog/microservices/iomessage.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | import json 12 | 13 | from struct import pack, unpack 14 | import sys 15 | 16 | from iofog.microservices.util import * 17 | import base64 18 | 19 | from iofog.microservices.definitions import * 20 | 21 | 22 | # todo check fields == None? 23 | class IoMessage: 24 | def __init__(self): 25 | self.id = '' 26 | self.tag = '' 27 | self.groupid = '' 28 | self.version = IO_MESSAGE_VERSION 29 | self.sequencenumber = 0 30 | self.sequencetotal = 0 31 | self.priority = 0 32 | self.timestamp = 0 33 | self.publisher = '' 34 | self.authid = '' 35 | self.authgroup = '' 36 | self.chainposition = 0 37 | self.hash = '' 38 | self.previoushash = '' 39 | self.nonce = '' 40 | self.difficultytarget = 0 41 | self.infotype = '' 42 | self.infoformat = '' 43 | self.contextdata = bytearray() 44 | self.contentdata = bytearray() 45 | 46 | def to_bytearray(self): 47 | if self.version != IO_MESSAGE_VERSION: 48 | raise Exception('Incompatible IoMessage version') 49 | 50 | header = bytearray() 51 | body = bytearray() 52 | # version 53 | header.extend(pack('>H', self.version)) 54 | # id 55 | header.extend(pack('>B', len(self.id))) 56 | body.extend(self.id.encode()) 57 | # tag 58 | header.extend(pack('>H', len(self.tag))) 59 | body.extend(self.tag.encode()) 60 | # group id 61 | header.extend(pack('>B', len(self.groupid))) 62 | body.extend(self.groupid.encode()) 63 | # sequence number 64 | byte_num, byte_num_len = num_to_bytearray(self.sequencenumber) 65 | header.extend(pack('>B', byte_num_len)) 66 | body.extend(byte_num.encode()) 67 | # sequence total 68 | byte_num, byte_num_len = num_to_bytearray(self.sequencetotal) 69 | header.extend(pack('>B', byte_num_len)) 70 | body.extend(byte_num.encode()) 71 | # priority 72 | byte_num, byte_num_len = num_to_bytearray(self.priority) 73 | header.extend(pack('>B', byte_num_len)) 74 | body.extend(byte_num.encode()) 75 | # timestamp 76 | byte_num, byte_num_len = num_to_bytearray(self.timestamp) 77 | header.extend(pack('>B', byte_num_len)) 78 | body.extend(byte_num.encode()) 79 | # publisher 80 | header.extend(pack('>B', len(self.publisher))) 81 | body.extend(self.publisher.encode()) 82 | # auth id 83 | header.extend(pack('>H', len(self.authid))) 84 | body.extend(self.authid.encode()) 85 | # auth group 86 | header.extend(pack('>H', len(self.authgroup))) 87 | body.extend(self.authgroup.encode()) 88 | # chain position 89 | byte_num, byte_num_len = num_to_bytearray(self.chainposition) 90 | header.extend(pack('>B', byte_num_len)) 91 | body.extend(byte_num.encode()) 92 | # hash 93 | header.extend(pack('>H', len(self.hash))) 94 | body.extend(self.hash.encode()) 95 | # previous hash 96 | header.extend(pack('>H', len(self.previoushash))) 97 | body.extend(self.previoushash.encode()) 98 | # nonce 99 | header.extend(pack('>H', len(self.nonce))) 100 | body.extend(self.nonce.encode()) 101 | # difficulty target 102 | byte_num, byte_num_len = num_to_bytearray(self.difficultytarget) 103 | header.extend(pack('>B', byte_num_len)) 104 | body.extend(byte_num.encode()) 105 | # info type 106 | header.extend(pack('>B', len(self.infotype))) 107 | body.extend(self.infotype.encode()) 108 | # info format 109 | header.extend(pack('>B', len(self.infoformat))) 110 | body.extend(self.infoformat.encode()) 111 | # context data 112 | header.extend(pack('>I', len(self.contextdata))) 113 | body.extend(self.contextdata.encode()) 114 | # content data 115 | header.extend(pack('>I', len(self.contentdata))) 116 | body.extend(self.contentdata.encode()) 117 | 118 | return header + body 119 | 120 | @staticmethod 121 | def from_bytearray(bytes): 122 | msg = IoMessage() 123 | msg.version = unpack('>H', bytes[:2])[0] 124 | if msg.version != IO_MESSAGE_VERSION: 125 | raise Exception('Incompatible IoMessage version') 126 | 127 | data_offset = 33 128 | # id 129 | field_len = bytes[2] 130 | msg.id = str(bytes[data_offset: data_offset + field_len]) 131 | data_offset += field_len 132 | # tag 133 | field_len = unpack('>H', bytes[3:5])[0] 134 | msg.tag = str(bytes[data_offset: data_offset + field_len]) 135 | data_offset += field_len 136 | # group id 137 | field_len = bytes[5] 138 | msg.groupid = str(bytes[data_offset: data_offset + field_len]) 139 | data_offset += field_len 140 | # sequence number 141 | field_len = bytes[6] 142 | msg.sequencenumber = bytearray_to_num(bytes[data_offset: data_offset + field_len]) 143 | data_offset += field_len 144 | # sequence total 145 | field_len = bytes[7] 146 | msg.sequencetotal = bytearray_to_num(bytes[data_offset: data_offset + field_len]) 147 | data_offset += field_len 148 | # priority 149 | field_len = bytes[8] 150 | msg.priority = bytearray_to_num(bytes[data_offset: data_offset + field_len]) 151 | data_offset += field_len 152 | # timestamp 153 | field_len = bytes[9] 154 | msg.timestamp = bytearray_to_num(bytes[data_offset: data_offset + field_len]) 155 | data_offset += field_len 156 | # publisher 157 | field_len = bytes[10] 158 | msg.publisher = str(bytes[data_offset: data_offset + field_len]) 159 | data_offset += field_len 160 | # auth id 161 | field_len = unpack('>H', bytes[11:13])[0] 162 | msg.authid = str(bytes[data_offset: data_offset + field_len]) 163 | data_offset += field_len 164 | # auth group 165 | field_len = unpack('>H', bytes[13:15])[0] 166 | msg.authgroup = str(bytes[data_offset: data_offset + field_len]) 167 | data_offset += field_len 168 | # chain position 169 | field_len = bytes[15] 170 | msg.chainposition = bytearray_to_num(bytes[data_offset: data_offset + field_len]) 171 | data_offset += field_len 172 | # hash 173 | field_len = unpack('>H', bytes[16:18])[0] 174 | msg.hash = str(bytes[data_offset: data_offset + field_len]) 175 | data_offset += field_len 176 | # previous hash 177 | field_len = unpack('>H', bytes[18:20])[0] 178 | msg.previoushash = str(bytes[data_offset: data_offset + field_len]) 179 | data_offset += field_len 180 | # nonce 181 | field_len = unpack('>H', bytes[20:22])[0] 182 | msg.nonce = str(bytes[data_offset: data_offset + field_len]) 183 | data_offset += field_len 184 | # difficulty target 185 | field_len = bytes[22] 186 | msg.difficultytarget = bytearray_to_num(bytes[data_offset: data_offset + field_len]) 187 | data_offset += field_len 188 | # info type 189 | field_len = bytes[23] 190 | msg.infotype = str(bytes[data_offset: data_offset + field_len]) 191 | data_offset += field_len 192 | # info format 193 | field_len = bytes[24] 194 | msg.infoformat = str(bytes[data_offset: data_offset + field_len]) 195 | data_offset += field_len 196 | # context data 197 | field_len = unpack('>I', bytes[25:29])[0] 198 | msg.contextdata = bytes[data_offset: data_offset + field_len] 199 | data_offset += field_len 200 | # content data 201 | field_len = unpack('>I', bytes[29:33])[0] 202 | msg.contentdata = bytes[data_offset: data_offset + field_len] 203 | return msg 204 | 205 | @staticmethod 206 | def from_json(json_msg): 207 | if isinstance(json_msg, str): 208 | json_msg = json.loads(json_msg) 209 | msg = IoMessage() 210 | msg.id = json_msg.get(ID, '') 211 | msg.tag = json_msg.get(TAG, '') 212 | msg.groupid = json_msg.get(GROUP_ID, '') 213 | msg.version = json_msg.get(VERSION, 0) 214 | msg.timestamp = json_msg.get(TIMESTAMP, 0) 215 | msg.sequencenumber = json_msg.get(SEQUENCE_NUMBER, 0) 216 | msg.sequencetotal = json_msg.get(SEQUENCE_TOTAL, 0) 217 | msg.priority = json_msg.get(PRIORITY, 0) 218 | msg.publisher = json_msg.get(PUBLISHER, '') 219 | msg.authid = json_msg.get(AUTH_ID, '') 220 | msg.authgroup = json_msg.get(AUTH_GROUP, '') 221 | msg.chainposition = json_msg.get(CHAIN_POSITION, 0) 222 | msg.hash = json_msg.get(HASH, '') 223 | msg.previoushash = json_msg.get(PREVIOUS_HASH, '') 224 | msg.nonce = json_msg.get(NONCE, '') 225 | msg.difficultytarget = json_msg.get(DIFFICULTY_TARGET, 0) 226 | msg.infotype = json_msg.get(INFO_TYPE, '') 227 | msg.infoformat = json_msg.get(INFO_FORMAT, '') 228 | contextdata = json_msg.get(CONTEXT_DATA, '') 229 | contextdata = base64.b64decode(contextdata) 230 | msg.contextdata = str.encode(contextdata) 231 | contentdata = json_msg.get(CONTENT_DATA, '') 232 | contentdata = base64.b64decode(contentdata) 233 | msg.contentdata = str.encode(contentdata) 234 | return msg 235 | 236 | def to_json(self): 237 | json_msg = { 238 | VERSION: self.version, 239 | ID: self.id, 240 | TAG: self.tag, 241 | GROUP_ID: self.groupid, 242 | TIMESTAMP: self.timestamp, 243 | SEQUENCE_NUMBER: self.sequencenumber, 244 | SEQUENCE_TOTAL: self.sequencetotal, 245 | PRIORITY: self.priority, 246 | PUBLISHER: self.publisher, 247 | AUTH_ID: self.authid, 248 | AUTH_GROUP: self.authgroup, 249 | CHAIN_POSITION: self.chainposition, 250 | HASH: self.hash, 251 | PREVIOUS_HASH: self.previoushash, 252 | NONCE: self.nonce, 253 | DIFFICULTY_TARGET: self.difficultytarget, 254 | INFO_TYPE: self.infotype, 255 | INFO_FORMAT: self.infoformat, 256 | CONTEXT_DATA: base64.b64encode(self.contextdata), 257 | CONTENT_DATA: base64.b64encode(self.contentdata) 258 | } 259 | return json.dumps(json_msg) 260 | -------------------------------------------------------------------------------- /iofog/microservices/listener.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | class IoFogControlWsListener: 12 | def __init__(self): 13 | pass 14 | 15 | def on_control_signal(self): 16 | pass 17 | 18 | def on_edge_resources_signal(self): 19 | pass 20 | 21 | 22 | class IoFogMessageWsListener: 23 | def __init__(self): 24 | pass 25 | 26 | def on_message(self, io_msg): 27 | pass 28 | 29 | def on_receipt(self, message_id, timestamp): 30 | pass 31 | -------------------------------------------------------------------------------- /iofog/microservices/log.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | from logging.handlers import RotatingFileHandler 12 | import json_logging, logging 13 | import datetime 14 | import json 15 | import os 16 | import socket 17 | 18 | DEVICE_KEY="deviceId" 19 | MSG_KEY="msg" 20 | hostname = socket.gethostname() 21 | 22 | class CustomJSONLog(logging.Formatter): 23 | """ 24 | Customized logger 25 | """ 26 | 27 | def format(self, record): 28 | content = json.loads(record.getMessage()) 29 | json_log_object = {"timestamp": datetime.datetime.utcnow().isoformat(), 30 | "hostname": hostname, 31 | "level": record.levelname.lower(), 32 | "message": content[MSG_KEY]} 33 | if DEVICE_KEY in content: 34 | json_log_object[DEVICE_KEY] = content[DEVICE_KEY] 35 | return json.dumps(json_log_object) 36 | 37 | json_logging.init_non_web(custom_formatter=CustomJSONLog, enable_json=True) 38 | 39 | def encode_content(msg, id): 40 | if id is None: 41 | return json.dumps({MSG_KEY: msg}) 42 | return json.dumps({MSG_KEY: msg, DEVICE_KEY: id}) 43 | 44 | class Logger: 45 | 46 | def __init__(self, name, device_id=None, log_dir=None): 47 | self.logger = logging.getLogger(name) 48 | self.device_id = device_id 49 | # Create log file 50 | if log_dir is None: 51 | log_dir = "/var/log/iofog-microservices" 52 | else: 53 | log_dir = log_dir.rstrip('/') 54 | self.file = "{}/{}.log".format(log_dir, name) 55 | if not os.path.exists(self.file): 56 | with open(self.file, 'w+') as f: 57 | pass 58 | # Register log file 59 | self.logger.addHandler(RotatingFileHandler(filename=self.file, maxBytes=10*1024*1024, backupCount=5)) 60 | 61 | def info(self, msg): 62 | self.logger.setLevel(logging.INFO) 63 | self.logger.info(encode_content(msg, self.device_id)) 64 | 65 | def debug(self, msg): 66 | self.logger.setLevel(logging.DEBUG) 67 | self.logger.debug(encode_content(msg, self.device_id)) 68 | 69 | def warning(self, msg): 70 | self.logger.setLevel(logging.WARN) 71 | self.logger.warning(encode_content(msg, self.device_id)) 72 | 73 | def error(self, msg): 74 | self.logger.setLevel(logging.ERROR) 75 | self.logger.error(encode_content(msg, self.device_id)) 76 | 77 | -------------------------------------------------------------------------------- /iofog/microservices/util.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | import json 12 | import math 13 | 14 | try: 15 | import urllib.request as urllib_request #for python 3 16 | except ImportError: 17 | import urllib2 as urllib_request # for python 2 18 | 19 | from struct import pack 20 | from iofog.microservices.definitions import CODE_MSG 21 | 22 | 23 | def num_to_bytearray(num): 24 | if num == 0: 25 | return bytearray([0]), 1 26 | 27 | num_of_bits = num.bit_length() 28 | num_of_bytes = int(math.ceil(num_of_bits / 8.0)) 29 | b = bytearray(num_of_bytes) 30 | shift = 8 * (num_of_bytes - 1) 31 | for i in range(num_of_bytes): 32 | b[i] = (num >> shift) & 0xFF 33 | shift -= 8 34 | return b, num_of_bytes 35 | 36 | 37 | def bytearray_to_num(arr): 38 | num = 0 39 | shift = 0 40 | for i in reversed(arr): 41 | num += i << shift 42 | shift += 8 43 | return num 44 | 45 | 46 | def make_post_request(url, body_type, body): 47 | req = urllib_request.Request(url, body, {'Content-Type': body_type}) 48 | response = urllib_request.urlopen(req) 49 | return json.loads(response.read()) 50 | 51 | def make_get_request(url, body_type, body): 52 | req = urllib_request.Request(url, body, {'Content-Type': body_type}, method="GET") 53 | response = urllib_request.urlopen(req) 54 | return json.loads(response.read()) 55 | 56 | 57 | def prepare_iomessage_for_sending_via_socket(io_msg): 58 | msg_bytes = io_msg.to_bytearray() 59 | package = bytearray([CODE_MSG]) 60 | package.extend(pack('>I', len(msg_bytes))) 61 | return package + msg_bytes 62 | -------------------------------------------------------------------------------- /iofog/microservices/wsclient.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | import logging 12 | import threading 13 | 14 | import time 15 | 16 | import iofog.microservices.util as util 17 | from iofog.microservices.iomessage import IoMessage 18 | from iofog.microservices.definitions import * 19 | from ws4py.client.threadedclient import WebSocketClient 20 | from ws4py.framing import OPCODE_PONG 21 | 22 | 23 | class IoFogWsClient(WebSocketClient): 24 | def __init__(self, container_id, ssl, host, port, url, listener): 25 | protocol_ws = WS 26 | if ssl: 27 | protocol_ws = WSS 28 | 29 | self.logger = logging.getLogger(IOFOG_LOGGER) 30 | self.url_base_ws = '{}://{}:{}'.format(protocol_ws, host, port) 31 | self.url_ws = self.url_base_ws + url + container_id 32 | self.wsAttempt = 0 33 | self.listener = listener 34 | self.worker = None 35 | self.is_open = False 36 | super(IoFogWsClient, self).__init__(self.url_ws) 37 | 38 | def _reconnect(self, code=None, reason=None): 39 | self.is_open = False 40 | self.logger.info('WebSocket connection is closed{}{}. Reconnecting...'.format( 41 | '' if not code else ' with code {}'.format(code), 42 | '' if not reason else '({})'.format(reason))) 43 | sleep_time = 1 << self.wsAttempt * WS_CONNECT_TIMEOUT 44 | if self.wsAttempt < WS_ATTEMPT_LIMIT: 45 | self.wsAttempt += 1 46 | try: 47 | time.sleep(sleep_time) 48 | except KeyboardInterrupt: 49 | pass 50 | 51 | def process(self, bytes): 52 | s = self.stream 53 | 54 | if not bytes and self.reading_buffer_size > 0: 55 | return False 56 | 57 | from ws4py.websocket import DEFAULT_READING_SIZE 58 | self.reading_buffer_size = s.parser.send(bytes) or DEFAULT_READING_SIZE 59 | 60 | if s.closing is not None: 61 | self.logger.debug("Closing message received (%d) '%s'" % (s.closing.code, s.closing.reason)) 62 | if not self.server_terminated: 63 | self.close(s.closing.code, s.closing.reason) 64 | else: 65 | self.client_terminated = True 66 | return False 67 | 68 | if s.errors: 69 | for error in s.errors: 70 | self.logger.debug("Error message received (%d) '%s'" % (error.code, error.reason)) 71 | self.close(error.code, error.reason) 72 | s.errors = [] 73 | return False 74 | 75 | if s.has_message: 76 | self.received_message(s.message) 77 | if s.message is not None: 78 | s.message.data = None 79 | s.message = None 80 | return True 81 | 82 | if s.pings: 83 | for _ in s.pings: 84 | self.logger.debug('Got ping from iofog') 85 | self._write(s.pong(bytearray([OPCODE_PONG]))) 86 | s.pings = [] 87 | 88 | if s.pongs: 89 | for pong in s.pongs: 90 | self.ponged(pong) 91 | s.pongs = [] 92 | 93 | return True 94 | 95 | def opened(self): 96 | self.wsAttempt = 0 97 | self.is_open = True 98 | 99 | def connect(self): 100 | self.logger.debug('Starting connect') 101 | self.worker = threading.Thread(target=self._serve, name='WS Server') 102 | self.worker.start() 103 | 104 | def _serve(self): 105 | self.logger.debug('Starting serving') 106 | while True: 107 | try: 108 | super(IoFogWsClient, self).connect() 109 | except Exception as e: 110 | self._reconnect(reason=e.message) 111 | continue 112 | self.run_forever() 113 | self.logger.debug('Loop exited') 114 | super(IoFogWsClient, self).__init__(self.url_ws) 115 | self._reconnect(reason='Connection terminated') 116 | 117 | 118 | class IoFogControlWsClient(IoFogWsClient): 119 | def __init__(self, container_id, ssl, host, port, url, listener): 120 | super(IoFogControlWsClient, self).__init__(container_id, ssl, host, port, url, listener) 121 | 122 | def received_message(self, message): 123 | opt_code = bytearray(message.data)[0] 124 | if opt_code == CODE_CONTROL_SIGNAL: 125 | self.logger.debug('Received control') 126 | self.listener.on_control_signal() 127 | self.send(bytearray([CODE_ACK]), binary=True) 128 | elif opt_code == CODE_EDGE_RESOURCE_SIGNAL: 129 | self.logger.debug('Received Edge Resource Signal') 130 | self.listener.on_edge_resources_signal() 131 | self.send(bytearray([CODE_ACK]), binary=True) 132 | 133 | 134 | class IoFogMessageWsClient(IoFogWsClient): 135 | def __init__(self, container_id, ssl, host, port, url, listener): 136 | super(IoFogMessageWsClient, self).__init__(container_id, ssl, host, port, url, listener) 137 | 138 | def received_message(self, message): 139 | data = bytearray(message.data) 140 | opt_code = data[0] 141 | if opt_code == CODE_MSG: 142 | self.logger.debug('Received message') 143 | msg_data = data[5:] 144 | if len(msg_data) == 0: 145 | return 146 | msg = IoMessage.from_bytearray(msg_data) 147 | self.send(bytearray([CODE_ACK]), binary=True) 148 | self.listener.on_message(msg) 149 | elif opt_code == CODE_RECEIPT: 150 | self.logger.debug('Received receipt') 151 | receipt_data = data[1:] 152 | if len(receipt_data) == 0: 153 | return 154 | id_len = receipt_data[0] 155 | ts_len = receipt_data[1] 156 | pos = 2 157 | message_id = str(receipt_data[pos: pos + id_len]) 158 | pos += id_len 159 | timestamp = util.bytearray_to_num(receipt_data[pos: pos + ts_len]) 160 | self.send(bytearray([CODE_ACK]), binary=True) 161 | self.listener.on_receipt(message_id, timestamp) 162 | 163 | def send_message(self, io_msg): 164 | self.logger.debug('Sending message') 165 | self.send(util.prepare_iomessage_for_sending_via_socket(io_msg), binary=True) 166 | self.logger.debug('Sent message') 167 | -------------------------------------------------------------------------------- /iofog/rest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-iofog/iofog-python-sdk/647c767e54d6fb535a18886b61354f7f0dbbf29a/iofog/rest/__init__.py -------------------------------------------------------------------------------- /iofog/rest/controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-iofog/iofog-python-sdk/647c767e54d6fb535a18886b61354f7f0dbbf29a/iofog/rest/controller/__init__.py -------------------------------------------------------------------------------- /iofog/rest/controller/client.py: -------------------------------------------------------------------------------- 1 | from iofog.rest.controller.request import * 2 | import yaml 3 | 4 | class Client: 5 | """ 6 | Iofog Controller REST API client. 7 | """ 8 | 9 | 10 | def __init__(self, host, port, email, password, name="", surname=""): 11 | self.base_path = "http://" + host + ":" + str(port) + "/api/v3" 12 | try: 13 | self._login(email, password) 14 | except Exception: 15 | if name == "" or surname == "": 16 | raise Exception("Must provide name and surname if User does not already exist") 17 | self.create_user(email, password, name, surname) 18 | self._login(email, password) 19 | self.error_yaml_spec = "YAML file does not follow required specification. See https://iofog.org/docs/2/reference-iofogctl/reference-application.html" 20 | 21 | def create_user(self, email, password, name, surname): 22 | url = "{}/user/signup".format(self.base_path) 23 | body = { 24 | "email": email, 25 | "password": password, 26 | "lastName": name, 27 | "firstName": surname 28 | } 29 | return request("POST", url, "", body) 30 | 31 | def _login(self, email, password): 32 | url = "{}/user/login".format(self.base_path) 33 | body = { 34 | "email": email, 35 | "password": password 36 | } 37 | self.token = request("POST", url, "", body)["accessToken"] 38 | 39 | def get_status(self): 40 | url = "{}/status".format(self.base_path) 41 | return request("GET", url) 42 | 43 | def create_agent(self, name, host, arch=""): 44 | fog_type_id = 0 45 | if arch != "": 46 | if arch not in [ "x86", "arm" ]: 47 | raise Exception("Agent architecture {} not supported".format(arch)) 48 | fog_type_id = 1 if arch == "x86" else 2 49 | url = "{}/iofog".format(self.base_path) 50 | body = { 51 | "name": name, 52 | "fogType": fog_type_id, 53 | "host": host 54 | } 55 | return request("POST", url, self.token, body)["uuid"] 56 | 57 | def _get_provision_key(self, agent_id): 58 | url = "{}/iofog/{}/provisioning-key".format(self.base_path, agent_id) 59 | return request("GET", url, self.token)["key"] 60 | 61 | def _delete_agent(self, agent_id): 62 | url = "{}/iofog/{}".format(self.base_path, agent_id) 63 | request("DELETE", url, self.token) 64 | 65 | def get_agent_uuid(self, name): 66 | url = "{}/iofog-list".format(self.base_path) 67 | resp = request("GET", url, self.token) 68 | for fog in resp["fogs"]: 69 | if fog["name"] == name: 70 | return fog["uuid"] 71 | 72 | def delete_agent(self, name): 73 | uuid = self.get_agent_uuid(name) 74 | if uuid is not None: 75 | self._delete_agent(uuid) 76 | 77 | def get_provision_key(self, agent_name): 78 | uuid = self.get_agent_uuid(agent_name) 79 | if uuid is None: 80 | raise Exception("Could not get Agent UUID") 81 | return self._get_provision_key(uuid) 82 | 83 | def upgrade_agent(self, agent_name): 84 | uuid = self.get_agent_uuid(agent_name) 85 | url = "{}/iofog/{}/version/upgrade".format(self.base_path, uuid) 86 | return request("POST", url, self.token) 87 | 88 | def patch_agent(self, agent_name, config): 89 | uuid = self.get_agent_uuid(agent_name) 90 | url = "{}/iofog/{}".format(self.base_path, uuid) 91 | return request("PATCH", url, self.token, config) 92 | 93 | def create_app(self, name, msvcs, routes): 94 | if routes is None: 95 | routes = [] 96 | url = "{}/application/".format(self.base_path) 97 | body = { 98 | "name": name, 99 | "routes": routes, 100 | "microservices": msvcs, 101 | "isActivated": True, 102 | "isSystem": False 103 | } 104 | return request("POST", url, self.token, body) 105 | 106 | def create_app_from_yaml(self, file): 107 | with open(file) as file: 108 | doc = yaml.full_load(file) 109 | if "metadata" not in doc or "spec" not in doc or "microservices" not in doc["spec"]: 110 | raise Exception(self.error_yaml_spec) 111 | name = doc["metadata"]["name"] 112 | msvcs = doc["spec"]["microservices"] 113 | routes = None 114 | if "routes" in doc["spec"]: 115 | routes = doc["spec"]["routes"] 116 | json_msvcs = self._jsonify_yaml_msvcs(msvcs) 117 | json_routes = self._jsonify_yaml_routes(routes) 118 | return self.create_app(name, json_msvcs, json_routes) 119 | 120 | def delete_app(self, name): 121 | url = "{}/application/{}".format(self.base_path, name) 122 | return request("DELETE", url, self.token) 123 | 124 | def get_app(self, name): 125 | url = "{}/application/{}".format(self.base_path, name) 126 | return request("GET", url, self.token) 127 | 128 | def _jsonify_yaml_routes(self, routes): 129 | json_routes = [] 130 | if routes is None: 131 | return json_routes 132 | for route in routes: 133 | json_route = route 134 | json_route["name"] = "{}-{}".format(route["from"], route["to"]) 135 | return json_routes 136 | 137 | def _jsonify_yaml_msvcs(self, msvcs): 138 | json_msvcs = [] 139 | for msvc in msvcs: 140 | json_msvc = dict() 141 | # images 142 | images = [] 143 | for arch in [ "x86", "arm" ]: 144 | if arch in msvc["images"]: 145 | fog_type_id = 1 if arch == "x86" else 2 146 | imageDict = { 147 | "fogTypeId": fog_type_id, 148 | "containerImage": msvc["images"][arch] 149 | } 150 | images.append(imageDict) 151 | json_msvc["images"] = images 152 | # container 153 | for pasta in [ "env", "rootHostAccess", "ports", "volumes", "commands" ]: 154 | if pasta in msvc["container"]: 155 | json_msvc[pasta] = msvc["container"][pasta] 156 | if "volumes" in json_msvc: 157 | json_msvc["volumeMappings"] = json_msvc["volumes"] 158 | json_msvc.pop("volumes") 159 | # msvc 160 | for pasta in [ "name" ]: 161 | if pasta in msvc: 162 | json_msvc[pasta] = msvc[pasta] 163 | json_msvc["registryId"] = 1 164 | if "config" in msvc: 165 | json_msvc["config"] = json.dumps(msvc["config"]) 166 | if "env" in json_msvc: 167 | for env in json_msvc["env"]: 168 | env["value"] = str(env["value"]) 169 | if "ports" in json_msvc: 170 | for port in json_msvc["ports"]: 171 | if "public" in port: 172 | port["publicPort"] = port["public"] 173 | port.pop("public") 174 | json_msvc["iofogUuid"] = self.get_agent_uuid(msvc["agent"]["name"]) 175 | json_msvcs.append(json_msvc) 176 | 177 | return json_msvcs 178 | -------------------------------------------------------------------------------- /iofog/rest/controller/request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | 5 | def request(method, address, auth_token="", body={}): 6 | switch = { 7 | "POST": requests.post, 8 | "GET": requests.get, 9 | "DELETE": requests.delete, 10 | "PATCH": requests.patch, 11 | } 12 | headers = {} 13 | data = {} 14 | if body: 15 | headers["Content-Type"] = 'application/json' 16 | data = json.dumps(body, indent=4) 17 | 18 | if auth_token: 19 | headers["Authorization"] = auth_token 20 | else: 21 | headers["cache-control"] = "no-cache" 22 | 23 | response = switch[method](address, data=data, headers=headers, timeout=30) 24 | try: 25 | response.raise_for_status() 26 | except requests.HTTPError as e: 27 | print(e.response.content) 28 | raise e 29 | responseDict = {} 30 | if response.content: 31 | responseDict = json.loads(response.content) 32 | return responseDict -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==5.3.1 2 | requests==2.24.0 3 | ws4py==0.5.1 4 | json-logging==1.2.11 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #******************************************************************************** 2 | # Copyright (c) 2018 Edgeworx, Inc. 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License v. 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0 7 | # 8 | # SPDX-License-Identifier: EPL-2.0 9 | #******************************************************************************** 10 | 11 | import setuptools 12 | 13 | with open("README.md", "r") as fh: 14 | long_description = fh.read() 15 | 16 | setuptools.setup( 17 | name='iofog', 18 | version='3.0.0-alpha1', 19 | project_urls={ 20 | 'Documentation': 'https://github.com/eclipse-iofog/iofog-python-sdk/blob/master/README.md', 21 | 'Source': 'https://github.com/eclipse-iofog/iofog-python-sdk.git', 22 | 'Tracker': 'https://github.com/eclipse-iofog/iofog-python-sdk/issues', 23 | 'Eclipse ioFog': 'http://iofog.org' 24 | }, 25 | packages=setuptools.find_packages(), 26 | url='https://github.com/eclipse-iofog/iofog-python-sdk', 27 | license='EPL-2.0', 28 | author='Eclipse ioFog', 29 | author_email='edgemaster@iofog.org', 30 | description='Python SDK for Eclipse ioFog development.', 31 | long_description=long_description, 32 | long_description_content_type="text/markdown", 33 | requires=['ws4py', 'json', 'requests'], 34 | keywords='ioFog IoT Eclipse fog computing edgeworx', 35 | ) 36 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-iofog/iofog-python-sdk/647c767e54d6fb535a18886b61354f7f0dbbf29a/test/__init__.py -------------------------------------------------------------------------------- /test/bootstrap.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # iofogctl 6 | curl -s https://packagecloud.io/install/repositories/iofog/iofogctl/script.deb.sh | sudo bash 7 | sudo apt install -qy iofogctl 8 | iofogctl version 9 | 10 | # pytest etc 11 | pip install -r requirements.txt 12 | pip install -r test/requirements.txt 13 | -------------------------------------------------------------------------------- /test/conf/app.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: iofog.org/v2 3 | kind: Application 4 | metadata: 5 | name: func-app 6 | spec: 7 | microservices: 8 | - name: func-app-server 9 | agent: 10 | name: agent-1 11 | config: 12 | bluetoothEnabled: true # this will install the iofog/restblue microservice 13 | abstractedHardwareEnabled: false 14 | images: 15 | arm: edgeworx/healthcare-heart-rate:arm-v1 16 | x86: edgeworx/healthcare-heart-rate:x86-v1 17 | registry: remote # public docker 18 | container: 19 | rootHostAccess: false 20 | ports: [] 21 | config: 22 | test_mode: true 23 | data_label: 'Anonymous_Person' 24 | # Simple JSON viewer for the heart rate output 25 | - name: func-app-ui 26 | agent: 27 | name: agent-1 28 | images: 29 | arm: edgeworx/healthcare-heart-rate-ui:arm 30 | x86: edgeworx/healthcare-heart-rate-ui:x86 31 | registry: remote 32 | container: 33 | rootHostAccess: false 34 | ports: 35 | # The ui will be listening on port 80 (internal). 36 | - external: 5000 37 | internal: 80 38 | public: 5000 39 | volumes: 40 | - hostDestination: /tmp/iofog 41 | containerDestination: /data 42 | accessMode: rw 43 | env: 44 | - key: BASE_URL 45 | value: http://localhost:8080/data -------------------------------------------------------------------------------- /test/conf/ecn.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: iofog.org/v2 3 | kind: LocalControlPlane 4 | metadata: 5 | name: local-1 6 | spec: 7 | iofogUser: 8 | name: Foo 9 | surname: Bar 10 | email: user@domain.com 11 | password: g9hr823rhuoi 12 | controller: 13 | container: 14 | image: gcr.io/focal-freedom-236620/controller:develop 15 | --- 16 | apiVersion: iofog.org/v2 17 | kind: LocalAgent 18 | metadata: 19 | name: local-1 20 | spec: 21 | container: 22 | image: gcr.io/focal-freedom-236620/agent:develop 23 | -------------------------------------------------------------------------------- /test/conf/vars.bash: -------------------------------------------------------------------------------- 1 | NS="pytest" 2 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | pytest_plugins = ("dependency",) 2 | -------------------------------------------------------------------------------- /test/log.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import random 3 | import string 4 | from iofog.microservices.log import Logger 5 | 6 | @pytest.fixture(autouse=True) 7 | def _fixture(): 8 | return {} 9 | 10 | @pytest.mark.dependency() 11 | def test_first_logger(_fixture): 12 | id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) 13 | name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) 14 | logger = Logger(name, device_id=id, log_dir="/tmp") 15 | logger.info("hello") 16 | logger.debug("world") 17 | logger.warning("good") 18 | logger.error("bye") 19 | with open("/tmp/{}.log".format(name)) as log_file: 20 | for line in log_file: 21 | assert id in line 22 | assert "deviceId" in line 23 | assert "message" in line 24 | assert "level" in line 25 | assert "hostname" in line 26 | assert "timestamp" in line 27 | 28 | 29 | @pytest.mark.dependency() 30 | def test_second_logger(_fixture): 31 | name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) 32 | logger = Logger(name, log_dir="/tmp/") 33 | logger.info("hello") 34 | logger.debug("world") 35 | logger.warning("good") 36 | logger.error("bye") 37 | with open("/tmp/{}.log".format(name)) as log_file: 38 | for line in log_file: 39 | assert "deviceId" not in line 40 | assert "message" in line 41 | assert "level" in line 42 | assert "hostname" in line 43 | assert "timestamp" in line -------------------------------------------------------------------------------- /test/microservices.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from iofog.microservices.client import Client 3 | from iofog.microservices.iomessage import IoMessage 4 | 5 | class TestMicroservicesState: 6 | def __init__(self): 7 | self.msg = IoMessage() 8 | self.msg.infotype = "infotype" 9 | self.msg.infoformat = "infoformat" 10 | self.msg.tag = "tag" 11 | self.msg.groupid = "groupid" 12 | self.msg.authid = "authid" 13 | self.msg.authgroup = "authgroup" 14 | self.msg.hash = "hash" 15 | self.msg.previoushash = "previoushash" 16 | self.msg.nonce = "nonce" 17 | self.msg.contentdata = "python3" 18 | self.msg.contextdata = "" 19 | 20 | state = TestMicroservicesState() 21 | 22 | @pytest.fixture(autouse=True) 23 | def _fixture(): 24 | return state 25 | 26 | @pytest.mark.dependency() 27 | def test_send(_fixture): 28 | client = Client("sender") 29 | receipt = client.post_message(_fixture.msg) 30 | assert isinstance(receipt, dict) 31 | 32 | @pytest.mark.dependency(depends=["test_send"]) 33 | def test_recieve(_fixture): 34 | client = Client("reciever") 35 | msgs = client.get_next_messages() 36 | for msg in msgs: 37 | assert isinstance(msg, bytes) 38 | assert msg == _fixture.msg.contentdata 39 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==5.3.1 2 | requests==2.24.0 3 | pytest==5.4.3 4 | pytest-dependency==0.5.1 5 | ws4py==0.5.1 6 | -------------------------------------------------------------------------------- /test/rest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from iofog.rest.controller.client import Client 3 | 4 | class TestRestState: 5 | def __init__(self): 6 | self.email = "serge@edgeworx.io" 7 | self.password = "wfhoi982bv1sfdjoi" 8 | self.name = "Serge" 9 | self.surname = "Radinovich" 10 | self.controller_address = "localhost" 11 | self.controller_port = 51121 12 | 13 | state = TestRestState() 14 | 15 | @pytest.fixture(autouse=True) 16 | def _fixture(): 17 | return state 18 | 19 | @pytest.mark.dependency() 20 | def test_create_user(_fixture): 21 | _fixture.client = Client(_fixture.controller_address, 22 | _fixture.controller_port, 23 | _fixture.email, 24 | _fixture.password, 25 | _fixture.name, 26 | _fixture.surname) 27 | 28 | @pytest.mark.dependency(depends=["test_create_user"]) 29 | def test_get_status(_fixture): 30 | status = _fixture.client.get_status() 31 | assert isinstance(status, dict) 32 | assert status["status"] == "online" 33 | assert isinstance(status["timestamp"], int) 34 | assert isinstance(status["uptimeSec"], float) 35 | assert isinstance(status["versions"], dict) 36 | assert isinstance(status["versions"]["controller"], str) 37 | assert isinstance(status["versions"]["ecnViewer"], str) 38 | 39 | @pytest.mark.dependency(depends=["test_create_user"]) 40 | def test_create_agent(_fixture): 41 | name = "agent-1" 42 | uuid = _fixture.client.create_agent(name, "localhost", "x86") 43 | assert isinstance(uuid, str) 44 | assert(_fixture.client.get_agent_uuid(name) == uuid) 45 | 46 | @pytest.mark.dependency(depends=["test_create_agent"]) 47 | def test_create_app(_fixture): 48 | resp = _fixture.client.create_app_from_yaml("test/conf/app.yaml") 49 | assert isinstance(resp, dict) 50 | print(resp) 51 | 52 | @pytest.mark.dependency(depends=["test_create_app"]) 53 | def test_delete_app(_fixture): 54 | _fixture.client.delete_app("func-app") 55 | 56 | @pytest.mark.dependency(depends=["test_delete_app"]) 57 | def test_delete_agent(_fixture): 58 | name = "agent-1" 59 | _fixture.client.delete_agent(name) 60 | -------------------------------------------------------------------------------- /test/run.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Get test names from args, run all if empty 6 | TESTS="$1" 7 | if [ -z "$TESTS" ]; then 8 | TESTS=("rest" "log") 9 | fi 10 | 11 | # Run specified tests 12 | for TEST in ${TESTS[@]}; do 13 | pytest -v "test/${TEST}.py" 14 | done 15 | -------------------------------------------------------------------------------- /test/setup.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Get variables 6 | . test/conf/vars.bash 7 | 8 | # Create namespace 9 | iofogctl create namespace "$NS" -v 10 | iofogctl configure current-namespace "$NS" -v 11 | 12 | # Deploy local ECN 13 | iofogctl deploy -f test/conf/ecn.yaml -v 14 | -------------------------------------------------------------------------------- /test/teardown.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get variables 4 | . test/conf/vars.bash 5 | 6 | # Create namespace 7 | iofogctl delete namespace "$NS" --force -v 8 | --------------------------------------------------------------------------------