diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0a22dd6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## [v2.0.0] - unreleased + +* Remove deploy module +* Add rest.controller module +* Rename client module to microservices + +## [v1.3.0] + +* Ability to deploy ioFog microservices, and connections through SDK are now available. + * This can be imported through iofog_python_sdk.deploy options. + * Previous standard iofog_python_sdk can be imported through iofog_python_sdk.microservices +* Added standards for rest calls made to ioFog under deploy.create_rest_call +* Fixed issue with python3 object handling diff --git a/Docker/Dockerfile.recieve b/Docker/Dockerfile.recieve index 59febc5..8b545d8 100644 --- a/Docker/Dockerfile.recieve +++ b/Docker/Dockerfile.recieve @@ -5,6 +5,6 @@ COPY test/recieve.py recieve.py RUN pip install ws4py -RUN python -m pip install iofog-python-sdk +ADD iofog_python_sdk iofog_python_sdk -ENTRYPOINT ["python", "recieve.py"] \ No newline at end of file +CMD export SELFNAME=iofog; python recieve.py \ No newline at end of file diff --git a/Docker/Dockerfile.send b/Docker/Dockerfile.send index 41381ea..6b55ace 100644 --- a/Docker/Dockerfile.send +++ b/Docker/Dockerfile.send @@ -5,6 +5,7 @@ COPY test/send.py send.py RUN pip install ws4py -RUN python -m pip install iofog-python-sdk +ADD iofog_python_sdk iofog_python_sdk + +CMD export SELFNAME=iofog; python send.py -ENTRYPOINT ["python", "send.py"] \ No newline at end of file diff --git a/README.md b/README.md index f703406..2c34142 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ # iofog-python-sdk +This SDK provides the following modules: +* microservices: Clients for Microservices to talk to ioFog (e.g. ioMessage and Web Socket clients) +* rest.controller: Client for Controller REST API + +## Installation + +Install python package: +```bash +sudo python3 -m pip install iofog-python-sdk +``` + +## Client + 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. - send new message to ioFog (post_message) @@ -10,19 +23,14 @@ This module lets you easily build an ioElement. It gives you all the functionali - connect to ioFog Control Channel via WebSocket (establish_control_ws_connection) - connect to ioFog Message Channel via WebSocket (establish_message_ws_connection) and publish new message via this channel (post_message_via_socket) -## Code snippets: - -Install python package: -```bash -sudo python2 -m pip install iofog-python-sdk -``` +### Code snippets: Import iofog client and additional classes to your project: ```python -from iofog_python_sdk.client import IoFogClient -from iofog_python_sdk.exception import IoFogException -from iofog_python_sdk.iomessage import IoMessage -from iofog_python_sdk.listener import * +from iofog_python_sdk.microservices.client import IoFogClient +from iofog_python_sdk.microservices.exception import IoFogException +from iofog_python_sdk.microservices.iomessage import IoMessage +from iofog_python_sdk.microservices.listener import * ``` Create IoFog client with default settings: @@ -41,7 +49,7 @@ except IoFogException as e: # client creation failed, e contains description ``` -#### REST calls +##### REST calls Get list of next unread IoMessages: ```python @@ -94,7 +102,7 @@ except IoFogException, ex: ``` -#### WebSocket calls +##### WebSocket calls To use websocket connections you should implement listeners as follows: ```python @@ -126,7 +134,7 @@ client.post_message_via_socket(io_msg_instance) ``` -#### Message utils +##### Message utils Construct IoMessage from JSON(both json string and python dictionary are acceptable): ```python msg = IoMessage.from_json(json_msg) @@ -146,3 +154,16 @@ Pack IoMessage into bytearray: ```python msg_bytes = io_msg_instance.to_bytearray() ``` +## Deploy + +This module lets you easily communicate with the [Controller REST API](https://iofog.org/docs/1.3.0/controllers/rest-api.html). + + - Deploy flow, microservices, agents, etc. + - Edit microservice configuration + - Edit flow routing + +#### Disclaimer + +These modules are a Work In Progress. It was first written as a set of helper functions used by a python script to deploy a set of microservices configured using yaml files. + +Our [golang SDK](https://github.com/eclipse-iofog/iofog-go-sdk) is highly recommended as Controller REST API client. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0b5042d..e16ff19 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,8 +1,11 @@ trigger: + tags: + include: + - v* branches: include: - - master - develop + - release* paths: exclude: - README.md @@ -17,25 +20,30 @@ jobs: pool: vmImage: 'ubuntu-16.04' steps: - - task: UsePythonVersion-3.x + - task: UsePythonVersion@0 inputs: - versionSpec: '3.5' + versionSpec: '3.7' addToPath: true architecture: 'x64' - script: | bash ./build.sh 'python3' - displayName: 'Building-3.x' + displayName: 'Building 3.7' - script: | - bash ./test.sh - displayName: 'Testing-3.x' - - task: UsePythonVersion-2.7 + bash ./test.sh 'python3' + displayName: 'Testing 3.7' + - task: CopyFiles@2 inputs: - versionSpec: '2.7' - addToPath: true - architecture: 'x64' - - script: | - bash ./build.sh - displayName: 'Building-2.7' - - script: | - bash ./test.sh - displayName: 'Testing-2.7' \ No newline at end of file + SourceFolder: $(System.DefaultWorkingDirectory) + TargetFolder: $(Build.ArtifactStagingDirectory) + Contents: | + setup.py + requirements.txt + README.md + iofog_python_sdk/**/* + OverWrite: true + displayName: 'Copy artifacts' + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'sdk' + displayName: 'Publish artefacts' diff --git a/build.sh b/build.sh index d995059..20d1e2f 100755 --- a/build.sh +++ b/build.sh @@ -10,7 +10,3 @@ fi # Build Python Images docker build --build-arg TAG_NAME=${PYTHON_TAG} -t iofog/test-python-sdk-send:${PYTHON_VERSION} -f ./Docker/Dockerfile.send . docker build --build-arg TAG_NAME=${PYTHON_TAG} -t iofog/test-python-sdk-recieve:${PYTHON_VERSION} -f ./Docker/Dockerfile.recieve . - -# Push Python Images -docker push iofog/test-python-sdk-send:${PYTHON_VERSION} -docker push iofog/test-python-sdk-recieve:${PYTHON_VERSION} \ No newline at end of file diff --git a/iofog_python_sdk/agent_service.py b/iofog_python_sdk/agent_service.py deleted file mode 100644 index 9207e49..0000000 --- a/iofog_python_sdk/agent_service.py +++ /dev/null @@ -1,33 +0,0 @@ -#******************************************************************************** -# Copyright (c) 2018 Edgeworx, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License v. 2.0 which is available at -# http://www.eclipse.org/legal/epl-2.0 -# -# SPDX-License-Identifier: EPL-2.0 -#******************************************************************************** - -from iofog_python_sdk.create_rest_call import rest_call - -class agent_service: - def get_agent_per_microservice(controller_address, auth_token, microservices): - data = {} - agent_uuids = {} - post_address = "{}/iofog-list".format(controller_address) - json_response = rest_call(data, post_address, auth_token, method="GET") - for microserviceKey in microservices: - microservice = microservices[microserviceKey] - agent_uuids[microserviceKey] = next(x for x in json_response["fogs"] if x["name"] == microservice["agent-name"]) - return agent_uuids - - - def get_agent_info(agent): - config = {} - config.update(agent) - return config - - - def update_agent(controller_address, uuid, fog_info, auth_token): - url = "{}/iofog/{}".format(controller_address, uuid) - return rest_call(fog_info, url, auth_token, method="PATCH").response diff --git a/iofog_python_sdk/catalog_service.py b/iofog_python_sdk/catalog_service.py deleted file mode 100755 index 293d180..0000000 --- a/iofog_python_sdk/catalog_service.py +++ /dev/null @@ -1,102 +0,0 @@ -from iofog_python_sdk.create_rest_call import rest_call -from iofog_python_sdk.pretty_print import print_info -from iofog_python_sdk.microservice_service import microservices as msv - - -class catalog_service: - def create_catalog_curl_data(self, catalog_item): - data = {} - data["name"] = catalog_item["name"] - data["images"] = [] - data["images"].append({'containerImage': '{}'.format(catalog_item["images"]["arm"]), 'fogTypeId': 2}) - data["images"].append({'containerImage': '{}'.format(catalog_item["images"]["x86"]), 'fogTypeId': 1}) - data["registryId"] = 1 - - return data - - def update_catalog(self, controller_address, auth_token, catalog_item): - # Delete all microservices that uses this specific catalog item - print_info("====> Deleting all microservices currently using this catalog item") - microself = msv() - msv.delete_all_by_catalog_id(microself, controller_address, auth_token, catalog_item["id"]) - # Update catalog item - data = catalog_service.create_catalog_curl_data(self, catalog_item) - if data == {}: - # If data has failed to be created, exit here, and echo which service failed - return "{} failed to create curl data".format(catalog_item) - post_address = "{}/catalog/microservices/{}".format(controller_address, catalog_item["id"]) - json_response = rest_call(data, post_address, auth_token, method="PATCH").response - - def add_to_catalog(self, controller_address, auth_token, catalog_item): - data = catalog_service.create_catalog_curl_data(self, catalog_item) - if data == {}: - # If data has failed to be created, exit here, and echo which service failed - return "{} failed to create curl data".format(catalog_item) - post_address = "{}/catalog/microservices".format(controller_address) - return rest_call(data, post_address, auth_token).response["id"] - - - def delete_by_id(self, controller_address, catalog_id, auth_token): - post_address = "{}/catalog/microservices/{}".format(controller_address, catalog_id) - return rest_call({}, post_address, auth_token, method="DELETE").response - - - def delete_items(self, controller_address, catalog_items, auth_token): - for catalog_id in catalog_items: - catalog_service.delete_by_id(self, controller_address, catalog_id, auth_token) - - def get_catalog(self, controller_address, auth_token): - post_address = "{}/catalog/microservices".format(controller_address) - return rest_call({}, post_address, auth_token, method="GET").response["catalogItems"] - - def get_catalog_item_by_name(self, controller_address, name, auth_token): - catalog_items = catalog_service.get_catalog(self, controller_address, auth_token) - return next((x for x in catalog_items if x["name"] == name), None) - - def is_same(self, yaml_item, existing_item): - if len(yaml_item["images"]) != len(existing_item["images"]): - return False - for image_type in yaml_item["images"]: - image = yaml_item["images"][image_type] - same = next((x for x in existing_item["images"] if (image_type == "x86" and x["fogTypeId"] == 1 and x["containerImage"] == image) or (image_type == "arm" and x["fogTypeId"] == 2 and x["containerImage"] == image)), False) - if same == False: - return False - return True - - def setup(self, controller_address, auth_token, microservices): - # For each microservice check if catalog item exists - print_info("====> Reading the catalog") - updated = False - existing_catalog_items = catalog_service.get_catalog(self, controller_address, auth_token) - catalog_ids = {} - for microserviceKey in microservices: - microservice = microservices[microserviceKey] - catalog_item = { - "images": microservice["images"], - "name": microservice["microservice"]["name"] + "_catalog" - } - catalog_id = "" - existing_catalog_item = next((x for x in existing_catalog_items if x["name"] == catalog_item["name"]), None) - # If it does not exists yet, create - if existing_catalog_item == None: - print_info("====> Adding to the catalog") - updated = True - catalog_id = catalog_service.add_to_catalog(self, controller_address, auth_token, catalog_item) - # Otherwise, patch to update images - else: - catalog_item["id"] = existing_catalog_item["id"] - # Check if images have changed - if catalog_service.is_same(self, catalog_item, existing_catalog_item) == False: - print_info("====> Updating a catalog item (Delete / Recreate)") - updated = True - # update_catalog(controller_address, auth_token, catalog_item) - catalog_service.delete_by_id(self, controller_address, existing_catalog_item["id"], auth_token) - catalog_id = catalog_service.add_to_catalog(self, controller_address, auth_token, catalog_item) - else: - catalog_id = catalog_item["id"] - catalog_ids[microserviceKey] = catalog_id - if updated == False: - print_info("====> Catalog is up-to-date") - else: - print_info("====> Catalog updated") - return catalog_ids diff --git a/iofog_python_sdk/create_rest_call.py b/iofog_python_sdk/create_rest_call.py deleted file mode 100644 index e1b672a..0000000 --- a/iofog_python_sdk/create_rest_call.py +++ /dev/null @@ -1,47 +0,0 @@ -import requests -import json - -from requests.exceptions import HTTPError -from iofog_python_sdk.pretty_print import * - -class rest_call: - # - # create a rest call, with two optional variables, auth_token for before you can retrieve it, and - # get if this needs to be a GET call instead of POST - # - def __init__(self, data, address, auth_token="none", method="POST"): - switch = { - "POST": requests.post, - "GET": requests.get, - "DELETE": requests.delete, - "PATCH": requests.patch, - } - headers = {} - headers["Content-Type"] = 'application/json' - - # Dump the data to Json so the curl call can take it in. - data = json.dumps(data, indent=4) - - if auth_token == "none": - headers["cache-control"] = "no-cache" - else: - headers["Authorization"] = auth_token - - print("==== " + method + " CALL ====") - print("Sending data: " + data) - print("To addr: " + address) - - try: - r = switch[method](address, data=data, headers=headers, timeout=30) - r.raise_for_status() - except HTTPError as http_err: - print_error("HTTP error occurred: " + str(http_err)) - except Exception as err: - print_error("Other error occurred: " + str(err)) - else: - jsonResponse = "" - if r.text: - jsonResponse = json.loads(r.text) - print("Response: " + str(jsonResponse)) - print('=====') - self.response = jsonResponse diff --git a/iofog_python_sdk/flow_service.py b/iofog_python_sdk/flow_service.py deleted file mode 100644 index 2834ffd..0000000 --- a/iofog_python_sdk/flow_service.py +++ /dev/null @@ -1,69 +0,0 @@ -from iofog_python_sdk.create_rest_call import rest_call -import time - - -class Flow: - - def __init__(self): - self.id = "" - - def create_flow(self, controller_address, auth_token, flow): - data = {} - - data["name"] = flow - post_address = "{}/flow".format(controller_address) - jsonResponse = rest_call(data, post_address, auth_token).response - self.id = jsonResponse["id"] - - def create_flow_if_not_exist(self, controller_address, auth_token, flow): - try: - id = Flow.get_id(self, controller_address, flow, auth_token) - return id - except StopIteration: - return Flow.create_flow(self, controller_address, auth_token, flow) - - def restart_flow(self, controller_address, flow_id, flow_name, auth_token): - flow = Flow.get_flow_by_id(self, controller_address, flow_id, auth_token) - if flow.get("isActivated", False) == True: - Flow.stop_flow(self, controller_address, flow_id, flow_name, auth_token) - time.sleep(3) - Flow.start_flow(self, controller_address, flow_id, flow_name, auth_token) - - def start_flow(self, controller_address, flow_id, flow_name, auth_token): - data = {} - data["name"] = flow_name - data["isActivated"] = True - post_address = "{}/flow/{}".format(controller_address, flow_id) - jsonRespone = rest_call(data, post_address, auth_token, method="PATCH").response - - def stop_flow(self, controller_address, flow_id, flow_name, auth_token): - data = {} - data["name"] = flow_name - data["isActivated"] = False - post_address = "{}/flow/{}".format(controller_address, flow_id) - jsonRespone = rest_call(data, post_address, auth_token, method="PATCH").response - - def delete_flow(self, controller_address, flow_id, auth_token): - data = {} - post_address = "{}/flow/{}".format(controller_address, flow_id) - jsonRespone = rest_call(data, post_address, auth_token, method="DELETE").response - - def get_flow_by_name(self, controller_address, flow_name, auth_token): - flows = Flow.get_all(self, controller_address, auth_token) - return next(x for x in flows if x["name"] == flow_name) - - def get_flow_by_id(self, controller_address, flow_id, auth_token): - data = {} - post_address = "{}/flow/{}".format(controller_address, flow_id) - json_response = rest_call(data, post_address, auth_token, method="GET").response - return json_response - - def get_id(self, controller_address, flow_name, auth_token): - self.id = Flow.get_flow_by_name(self, controller_address, flow_name, auth_token)["id"] - - def get_all(self, controller_address, auth_token): - data = {} - flow_ids = {} - post_address = "{}/flow".format(controller_address) - json_response = rest_call(data, post_address, auth_token, method="GET").response - return json_response["flows"] diff --git a/iofog_python_sdk/microservice_service.py b/iofog_python_sdk/microservice_service.py deleted file mode 100644 index 4131eec..0000000 --- a/iofog_python_sdk/microservice_service.py +++ /dev/null @@ -1,220 +0,0 @@ -import json - -from iofog_python_sdk.create_rest_call import rest_call -from iofog_python_sdk.pretty_print import print_info, print_error -from iofog_python_sdk.flow_service import Flow - -class microservices: - def __init__(self): - self.data = {} - - def create_microservice_curl_data(self, microservice, flow_id, fog_uuid, catalog_id): - self.data["name"] = microservice["name"] - - if len(microservice.get("volumes", [])) > 0: - self.data["volumeMappings"] = microservice["volumes"] - - if len(microservice.get("env", [])) > 0: - self.data["env"] = microservice["env"] - - if len(microservice.get("ports", [])) > 0: - self.data["ports"] = microservice["ports"] - - if "config" in microservice: - self.data["config"] = json.dumps(microservice.get("config", {})) - - self.data["rootHostAccess"] = microservice.get("root-host", False) - self.data["flowId"] = flow_id - self.data["iofogUuid"] = fog_uuid - self.data["catalogItemId"] = catalog_id - - return self.data - - def update_microservice_curl_data(self, microservice, fog_uuid, catalog_id): - self.data["name"] = microservice["name"] - - if len(microservice.get("volumes", [])) > 0: - self.data["volumeMappings"] = microservice["volumes"] - - if len(microservice.get("env", [])) > 0: - self.data["env"] = microservice["env"] - - # if len(microservice.get("ports", [])) > 0: - # data["ports"] = microservice["ports"] - - if "config" in microservice: - self.data["config"] = json.dumps(microservice.get("config", {})) - - self.data["rootHostAccess"] = microservice.get("root-host", False) - self.data["iofogUuid"] = fog_uuid - # data["catalogItemId"] = catalog_id - - return self.data - - def create_route(controller_address, auth_token, route): - post_address = "{}/microservices/{}/routes/{}".format(controller_address, route["from"], route["to"]) - json_response = rest_call({}, post_address, auth_token).response - - def delete_route(controller_address, auth_token, route): - post_address = "{}/microservices/{}/routes/{}".format(controller_address, route["from"], route["to"]) - json_response = rest_call({}, post_address, auth_token, method="DELETE").response - - def update_routing(controller_address, microservices_per_name, auth_token, routes): - print_info("====> Update routing") - updated = False - for route in routes: - # Check if route exists - msvc = microservices_per_name.get(route["from"], None) - dest_msvc = microservices_per_name.get(route["to"], None) - if msvc == None: - print_error("No source microservice for route: {} - Microservices: {}".format(route, microservices_per_name)) - continue - if dest_msvc == None: - print_error("No destination microservice for route: {} - Microservices: {}".format(route, microservices_per_name)) - continue - route_exists = msvc != None and next((x for x in msvc.get("routes", []) if x == dest_msvc["uuid"]), False) - # Create missing route - if route_exists == False: - print_info("====> Create new route") - updated = True - microservices.create_route(controller_address, auth_token, - { - "from": microservices_per_name[route["from"]]["uuid"], - "to": microservices_per_name[route["to"]]["uuid"] - }) - # Delete unecessary routes - for microservice_name in microservices_per_name: - microservice = microservices_per_name[microservice_name] - microservice_routes = microservice.get("routes", []) - for route in microservice_routes: - route_needed = next((x for x in routes if x["from"] == microservice["name"] and microservices_per_name.get(x["to"], {"uuid": None})["uuid"] == route), False) - if route_needed == False: - print_info("====> Delete outdated route") - updated = True - microservices.delete_route(controller_address, auth_token, - { - "from": microservice["uuid"], - "to": route - }) - if updated == False: - print_info("====> Routing is up-to-date.") - else: - print_info("====> Routing updated.") - - def create_microservice(self, controller_address, microservice, fog_uuid, catalog_id, flow_id, auth_token): - data = microservices.create_microservice_curl_data(microservice, flow_id, fog_uuid, catalog_id) - post_address = "{}/microservices".format(controller_address) - json_response = rest_call(data, post_address, auth_token).response - return json_response - - def get_microservice_port_mapping(self, controller_address, microservice_uuid, auth_token): - data = microservices().data - post_address = "{}/microservices/{}/port-mapping".format(controller_address, microservice_uuid) - json_response = rest_call(data, post_address, auth_token, method="GET").response - return json_response["ports"] - - def delete_microservice_port_mapping(self, controller_address, microservice_uuid, mapping, auth_token): - post_address = "{}/microservices/{}/port-mapping/{}".format(controller_address, microservice_uuid, mapping["internal"]) - json_response = rest_call(self.data, post_address, auth_token, method="DELETE").response - return json_response - - def create_microservice_port_mapping(self, controller_address, microservice_uuid, mapping, auth_token): - data = microservices().data - data = mapping - post_address = "{}/microservices/{}/port-mapping".format(controller_address, microservice_uuid) - json_response = rest_call(data, post_address, auth_token).response - return json_response - - def update_ports(self, controller_address, microservice, auth_token): - print_info("====> Getting current port mapping") - updated = False - ports = microservice.get("ports", []) - existing_port_mappings = microservices.get_microservice_port_mapping(controller_address, microservice["uuid"], auth_token) - # Remove false port mapping - for existing_mapping in existing_port_mappings: - valid = next((x for x in ports if x["internal"] == existing_mapping["internal"] and x["external"] == existing_mapping["external"]), False) - if valid == False: - print_info("====> Remove outdated port mapping") - updated = True - microservices.delete_microservice_port_mapping(controller_address, microservice["uuid"], existing_mapping, auth_token) - # Create missing port mapping - for new_mapping in ports: - exists = next((x for x in existing_port_mappings if x["internal"] == new_mapping["internal"] and x["external"] == new_mapping["external"]), False) - if exists == False: - print_info("====> Create new port mapping") - updated = True - microservices.create_microservice_port_mapping(controller_address, microservice["uuid"], new_mapping, auth_token) - if updated == False: - print_info("====> Port mapping is up-to-date") - else: - print_info("====> Port mapping updated") - - - def update_microservice(self, controller_address, microservice, fog_uuid, catalog_id, auth_token): - self.data = microservices.update_microservice_curl_data(self, microservice, fog_uuid, catalog_id) - post_address = "{}/microservices/{}".format(controller_address, microservice["uuid"]) - json_response = rest_call(self.data, post_address, auth_token, method="PATCH").response - # Update port mapping - microservices.update_ports(controller_address, microservice, auth_token) - return json_response - - def get_microservices_by_flow_id(self, controller_address, flow_id, auth_token): - data = microservices() - post_address = "{}/microservices?flowId={}".format(controller_address, flow_id) - json_response = rest_call(data, post_address, auth_token, method="GET").response - return json_response["microservices"] - - def get_all_microservices(self, controller_address, auth_token): - # Get all flows - flow_self = Flow() - flows = Flow.get_all(flow_self, controller_address, auth_token) - all_msvcs = [] - for flow in flows: - all_msvcs.extend(microservices.get_microservices_by_flow_id(self, controller_address, flow["id"], auth_token)) - return all_msvcs - - def get_microservice_by_name(self, controller_address, microservice_name, flow_id, auth_token): - msvcs = microservices.get_microservices_by_flow_id(self, controller_address, flow_id, auth_token) - return next(x for x in msvcs if x["name"] == microservice_name) - - def setup(self, controller_address, flow_id, fog_per_microservice, catalog_ids, auth_token, microservices, routes): - route = "" - microservices_per_name = {} - # Get exisiting microservices - for microserviceKey in microservices: - microservice = microservices[microserviceKey] - name = microservice["microservice"]["name"] - try: - msvc = microservices.get_microservice_by_name(controller_address, name, flow_id, auth_token) - microservices_per_name[name] = msvc - except StopIteration: - microservices_per_name[name] = None - continue - # Create missing microservices - for microserviceKey in microservices: - microservice = microservices[microserviceKey]["microservice"] - name = microservice["name"] - fog_uuid = fog_per_microservice[microserviceKey]["uuid"] - catalog_id = catalog_ids[microserviceKey] - msvc = None - if microservices_per_name[name] == None: - msvc = microservices.create_microservice(controller_address, microservice, fog_uuid, catalog_id, flow_id, auth_token) - else: - microservice["uuid"] = microservices_per_name[name]["uuid"] - microservices.update_microservice(controller_address, microservice, fog_uuid, catalog_id, auth_token) - msvc = {**microservice, **microservices_per_name[name]} - microservices_per_name[microservice["name"]] = msvc - # Update routing - microservices.update_routing(controller_address, microservices_per_name, auth_token, routes) - - def delete_microservice(self, controller_address, microservice, auth_token): - post_address = "{}/microservices/{}".format(controller_address, microservice["uuid"]) - json_response = rest_call({}, post_address, auth_token, method="DELETE").response - return json_response - - def delete_all_by_catalog_id(self, controller_address, auth_token, catalog_id): - msvcs = microservices.get_all_microservices(self, controller_address, auth_token) - print("All microservices: {}".format(msvcs)) - for msvc in msvcs: - if msvc["catalogItemId"] == catalog_id: - microservices.delete_microservice(self, controller_address, msvc, auth_token) \ No newline at end of file diff --git a/iofog_python_sdk/microservices/__init__.py b/iofog_python_sdk/microservices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/iofog_python_sdk/client.py b/iofog_python_sdk/microservices/client.py similarity index 93% rename from iofog_python_sdk/client.py rename to iofog_python_sdk/microservices/client.py index 019b650..a68572c 100644 --- a/iofog_python_sdk/client.py +++ b/iofog_python_sdk/microservices/client.py @@ -14,11 +14,11 @@ import subprocess -from iofog_python_sdk.httpclient import IoFogHttpClient -from iofog_python_sdk.definitions import * -from iofog_python_sdk.wsclient import IoFogControlWsClient, IoFogMessageWsClient -from iofog_python_sdk.listener import * -from iofog_python_sdk.exception import * +from iofog_python_sdk.microservices.httpclient import IoFogHttpClient +from iofog_python_sdk.microservices.definitions import * +from iofog_python_sdk.microservices.wsclient import IoFogControlWsClient, IoFogMessageWsClient +from iofog_python_sdk.microservices.listener import * +from iofog_python_sdk.microservices.exception import * parser = argparse.ArgumentParser() parser.add_argument("-l", "--log", dest="logLevel", choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], diff --git a/iofog_python_sdk/definitions.py b/iofog_python_sdk/microservices/definitions.py similarity index 100% rename from iofog_python_sdk/definitions.py rename to iofog_python_sdk/microservices/definitions.py diff --git a/iofog_python_sdk/exception.py b/iofog_python_sdk/microservices/exception.py similarity index 100% rename from iofog_python_sdk/exception.py rename to iofog_python_sdk/microservices/exception.py diff --git a/iofog_python_sdk/httpclient.py b/iofog_python_sdk/microservices/httpclient.py similarity index 91% rename from iofog_python_sdk/httpclient.py rename to iofog_python_sdk/microservices/httpclient.py index 68549e4..93ae5b2 100644 --- a/iofog_python_sdk/httpclient.py +++ b/iofog_python_sdk/microservices/httpclient.py @@ -15,10 +15,10 @@ except ImportError: from urllib2 import HTTPError #for python 2 -from iofog_python_sdk.definitions import * -from iofog_python_sdk.util import make_post_request -from iofog_python_sdk.iomessage import IoMessage -from iofog_python_sdk.exception import IoFogHttpException +from iofog_python_sdk.microservices.definitions import * +from iofog_python_sdk.microservices.util import make_post_request +from iofog_python_sdk.microservices.iomessage import IoMessage +from iofog_python_sdk.microservices.exception import IoFogHttpException class IoFogHttpClient: diff --git a/iofog_python_sdk/iomessage.py b/iofog_python_sdk/microservices/iomessage.py similarity index 97% rename from iofog_python_sdk/iomessage.py rename to iofog_python_sdk/microservices/iomessage.py index 31a6769..dc866ca 100644 --- a/iofog_python_sdk/iomessage.py +++ b/iofog_python_sdk/microservices/iomessage.py @@ -13,10 +13,10 @@ from struct import pack, unpack import sys -from iofog_python_sdk.util import * +from iofog_python_sdk.microservices.util import * import base64 -from iofog_python_sdk.definitions import * +from iofog_python_sdk.microservices.definitions import * # todo check fields == None? @@ -40,8 +40,8 @@ def __init__(self): self.difficultytarget = 0 self.infotype = '' self.infoformat = '' - self.contextdata = bytearray() - self.contentdata = bytearray() + self.contextdata = '' + self.contentdata = '' def to_bytearray(self): if self.version != IO_MESSAGE_VERSION: @@ -253,7 +253,7 @@ def to_json(self): DIFFICULTY_TARGET: self.difficultytarget, INFO_TYPE: self.infotype, INFO_FORMAT: self.infoformat, - CONTEXT_DATA: base64.b64encode(self.contextdata), - CONTENT_DATA: base64.b64encode(self.contentdata) + CONTEXT_DATA: self.contextdata, + CONTENT_DATA: self.contentdata } return json.dumps(json_msg) diff --git a/iofog_python_sdk/listener.py b/iofog_python_sdk/microservices/listener.py similarity index 100% rename from iofog_python_sdk/listener.py rename to iofog_python_sdk/microservices/listener.py diff --git a/iofog_python_sdk/util.py b/iofog_python_sdk/microservices/util.py similarity index 87% rename from iofog_python_sdk/util.py rename to iofog_python_sdk/microservices/util.py index 5cb31ea..c77d03f 100644 --- a/iofog_python_sdk/util.py +++ b/iofog_python_sdk/microservices/util.py @@ -17,7 +17,7 @@ import urllib2 as urllib_request # for python 2 from struct import pack -from iofog_python_sdk.definitions import CODE_MSG +from iofog_python_sdk.microservices.definitions import CODE_MSG def num_to_bytearray(num): @@ -44,6 +44,9 @@ def bytearray_to_num(arr): def make_post_request(url, body_type, body): + # urllib.request.Request(url, data ...) + # 'data' should be encoded to bytes before being used as the data parameter. + body = body.encode("utf-8") req = urllib_request.Request(url, body, {'Content-Type': body_type}) response = urllib_request.urlopen(req) return json.loads(response.read()) diff --git a/iofog_python_sdk/wsclient.py b/iofog_python_sdk/microservices/wsclient.py similarity index 97% rename from iofog_python_sdk/wsclient.py rename to iofog_python_sdk/microservices/wsclient.py index 869b4a3..9ca08ad 100644 --- a/iofog_python_sdk/wsclient.py +++ b/iofog_python_sdk/microservices/wsclient.py @@ -13,9 +13,9 @@ import time -import iofog_python_sdk.util as util -from iofog_python_sdk.iomessage import IoMessage -from iofog_python_sdk.definitions import * +import iofog_python_sdk.microservices.util as util +from iofog_python_sdk.microservices.iomessage import IoMessage +from iofog_python_sdk.microservices.definitions import * from ws4py.client.threadedclient import WebSocketClient from ws4py.framing import OPCODE_PONG diff --git a/iofog_python_sdk/pretty_print.py b/iofog_python_sdk/pretty_print.py deleted file mode 100644 index 40b183b..0000000 --- a/iofog_python_sdk/pretty_print.py +++ /dev/null @@ -1,54 +0,0 @@ -from colorama import init - -# -# Pretty Print is a rather simple helper utility that exposes some helper methods to make printing logging -# information more colorful and easier to understand. -# -# Initialize colorama -init(autoreset=True) - -# These are the colors that we'll use in our output -NO_FORMAT = '\033[0m' -C_SKYBLUE1 = '\033[38;5;117m' -C_DEEPSKYBLUE4 = '\033[48;5;25m' -RED = '\033[38;5;1m' -GREEN = '\033[38;5;28m' - - -# -# Display a nice title line for any output. You can optionally populate it with a string -# -# Usage: pretty_title("Bootstrapping ioFog") -# -def pretty_title(title): - print_info("## " + title + " ####################################################") - - -# -# Display a nice header for any command line script. You can optionally populate it with a string -# -# Usage: pretty_header("Bootstrapping ioFog") -# -def pretty_header(title): - print_info("## " + title + " ####################################################") - print_info("## Copyright (C) 2019, Edgeworx, Inc.\n") - - -# Basic subtle output -def print_info(message): - print(C_SKYBLUE1 + message + NO_FORMAT) - - -# Highlighted output with a background -def print_notify(message): - print(C_DEEPSKYBLUE4 + message + NO_FORMAT) - - -# Hurrah! -def print_success(message): - print(GREEN + message + NO_FORMAT) - - -# Houston, we have a problem! -def print_error(message): - print(RED + str(message) + NO_FORMAT) diff --git a/iofog_python_sdk/rest/__init__.py b/iofog_python_sdk/rest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/iofog_python_sdk/rest/controller/__init__.py b/iofog_python_sdk/rest/controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/iofog_python_sdk/rest/controller/client.py b/iofog_python_sdk/rest/controller/client.py new file mode 100644 index 0000000..bfc051f --- /dev/null +++ b/iofog_python_sdk/rest/controller/client.py @@ -0,0 +1,67 @@ +from iofog_python_sdk.rest.controller.request import * + +class Client: + """ + Iofog Controller REST API client. + """ + + def __init__(self, host, port, email, password): + self.base_path = "http://" + host + ":" + str(port) + "/api/v3" + self._login(email, password) + + def _login(self, email, password): + url = "{}/user/login".format(self.base_path) + body = { + "email": email, + "password": password + } + self.token = request("POST", url, "", body)["accessToken"] + + def get_status(self): + url = "{}/status".format(self.base_path) + return request("GET", url) + + def create_agent(self, name, host): + url = "{}/iofog".format(self.base_path) + body = { + "name": name, + "fogType": 0, + "host": host + } + return request("POST", url, self.token, body)["uuid"] + + def _get_provision_key(self, agent_id): + url = "{}/iofog/{}/provisioning-key".format(self.base_path, agent_id) + return request("GET", url, self.token)["key"] + + def _delete_agent(self, agent_id): + url = "{}/iofog/{}".format(self.base_path, agent_id) + request("DELETE", url, self.token) + + def _get_agent_id(self, name): + url = "{}/iofog-list".format(self.base_path) + resp = request("GET", url, self.token) + for fog in resp["fogs"]: + if fog["name"] == name: + return fog["uuid"] + + def delete_agent(self, name): + uuid = self._get_agent_id(name) + if uuid is not None: + self._delete_agent(uuid) + + def get_provision_key(self, agent_name): + uuid = self._get_agent_id(agent_name) + if uuid is None: + raise Exception("Could not get Agent UUID") + return self._get_provision_key(uuid) + + def upgrade_agent(self, agent_name): + uuid = self._get_agent_id(agent_name) + url = "{}/iofog/{}/version/upgrade".format(self.base_path, uuid) + return request("POST", url, self.token) + + def patch_agent(self, agent_name, config): + uuid = self._get_agent_id(agent_name) + url = "{}/iofog/{}".format(self.base_path, uuid) + return request("PATCH", url, self.token, config) diff --git a/iofog_python_sdk/rest/controller/request.py b/iofog_python_sdk/rest/controller/request.py new file mode 100644 index 0000000..bdc6d8f --- /dev/null +++ b/iofog_python_sdk/rest/controller/request.py @@ -0,0 +1,32 @@ +import requests +import json + + +def request(method, address, auth_token="", body={}): + switch = { + "POST": requests.post, + "GET": requests.get, + "DELETE": requests.delete, + "PATCH": requests.patch, + } + headers = {} + data = {} + if body: + headers["Content-Type"] = 'application/json' + data = json.dumps(body, indent=4) + + if auth_token: + headers["Authorization"] = auth_token + else: + headers["cache-control"] = "no-cache" + + response = switch[method](address, data=data, headers=headers, timeout=30) + try: + response.raise_for_status() + except requests.HTTPError as e: + print(e.response.content) + raise e + responseDict = {} + if response.content: + responseDict = json.loads(response.content) + return responseDict \ No newline at end of file diff --git a/setup.py b/setup.py index f96e2cd..833afc0 100644 --- a/setup.py +++ b/setup.py @@ -8,23 +8,28 @@ # SPDX-License-Identifier: EPL-2.0 #******************************************************************************** -from distutils.core import setup +import setuptools -setup( +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( name='iofog-python-sdk', - version='1.0.0', + version='2.0.1', project_urls={ - 'Documentation': 'https://github.com/ioFog/iofog-python-sdk/blob/master/README.md', - 'Source': 'https://github.com/ioFog/iofog-python-sdk.git', - 'Tracker': 'https://github.com/ioFog/iofog-python-sdk/issues', + 'Documentation': 'https://github.com/eclipse-iofog/iofog-python-sdk/blob/master/README.md', + 'Source': 'https://github.com/eclipse-iofog/iofog-python-sdk.git', + 'Tracker': 'https://github.com/eclipse-iofog/iofog-python-sdk/issues', 'Eclipse ioFog': 'http://iofog.org' }, - packages=['iofog_python_sdk'], - url='https://github.com/ioFog/iofog-python-sdk', + packages=setuptools.find_packages(), + url='https://github.com/eclipse-iofog/iofog-python-sdk', license='EPL-2.0', author='Eclipse ioFog', author_email='edgemaster@iofog.org', description='Native python SDK for Eclipse ioFog development.', - requires=['ws4py'], - keywords='iofog IoT Eclipse fog computing edgeworx', + long_description=long_description, + long_description_content_type="text/markdown", + requires=['ws4py', 'json', 'requests'], + keywords='ioFog IoT Eclipse fog computing edgeworx', ) diff --git a/test.sh b/test.sh old mode 100644 new mode 100755 index 95e8296..08813ae --- a/test.sh +++ b/test.sh @@ -1,31 +1,24 @@ #!/usr/bin/env bash -PYTHON_VERSIONS=( "python3" "python2" ) -FAILURES=( 0 0 ) +PYTHON_VERSION=$1 -idx=0 -for version in ${PYTHON_VERSIONS[@]}; do +docker run --name send --network host -d iofog/test-python-sdk-send:$PYTHON_VERSION 1>/dev/null +docker run --name recieve --network host -d iofog/test-python-sdk-recieve:$PYTHON_VERSION 1>/dev/null - docker pull iofog/test-python-sdk-send::${version} - docker pull iofog/test-python-sdk-recieve:${version} +echo 'wait for 5 secs to check result....' +sleep 5s - docker run -d iofog/test-python-sdk-send:${version} - docker run -d iofog/test-python-sdk-recieve:${version} +SEND_CONTAINER_ID=$(docker ps -aqf "name=send") +RECIEVE_CONTAINER_ID=$(docker ps -aqf "name=recieve") - RECIEVE_CONTAINER_ID=$( docker ps | grep "python recieve.py" | awk '{print $1}' ) - SEND_CONTAINER_ID=$( docker ps | grep "python send.py" | awk '{print $1}' ) +if [[ -z $RECIEVE_CONTAINER_ID || -z $SEND_CONTAINER_ID ]]; then + echo 'Failed: either send container or recieve container is dead, please check' + exit 1 +fi - if [[ "${RECIEVE_CONTAINER_ID}" -eq "" || "${SEND_CONTAINER_ID}" -eq "" ]]; then - echo "${version} has failed to send/recieve data" - ${FAILURES[idx]}= 1 - fi - idx+=1 -done +docker rm -f send 1>/dev/null +docker rm -f recieve 1>/dev/null -for failure in ${FAILURES[@]}; do - if [[ ${failure} == 1 ]]; then - exit 1 - fi -done +echo 'Success! ' exit 0 diff --git a/test/recieve.py b/test/recieve.py index 6e3b512..bcd4a86 100644 --- a/test/recieve.py +++ b/test/recieve.py @@ -1,23 +1,42 @@ -from iofog_python_sdk.client import IoFogClient -from iofog_python_sdk.exception import IoFogException -from iofog_python_sdk.iomessage import IoMessage -from iofog_python_sdk.listener import * +from iofog_python_sdk.microservices.client import IoFogClient +from iofog_python_sdk.microservices.exception import IoFogException +from iofog_python_sdk.microservices.iomessage import IoMessage +from iofog_python_sdk.microservices.listener import * +import logging + +def init_logger(): + logger = logging.getLogger(__name__) + logger.setLevel('DEBUG') + ch = logging.StreamHandler() + ch.setLevel('DEBUG') + formatter = logging.Formatter( + '%(levelname)5s [%(asctime)-15s] %(module)10s - - %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) def main(): + init_logger() + logger = logging.getLogger(__name__) try: client = IoFogClient() except IoFogException as e: - print("Failed to start client") + logger.error("Failed to start client") + exit(e) while True: try: messages = client.get_next_messages() for message in messages: if message.contentdata == "python2" or message.contentdata == "python3": - print("It worked") + logger.info("It worked") else: exit(1) except IoFogException as e: - print("Could not get next message") + logger.error("Could not get next message") exit(e) + + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test/send.py b/test/send.py index 4ce9fb2..b1a169f 100644 --- a/test/send.py +++ b/test/send.py @@ -1,9 +1,23 @@ -from iofog_python_sdk.client import IoFogClient -from iofog_python_sdk.exception import IoFogException -from iofog_python_sdk.iomessage import IoMessage -from iofog_python_sdk.listener import * +from iofog_python_sdk.microservices.client import IoFogClient +from iofog_python_sdk.microservices.exception import IoFogException +from iofog_python_sdk.microservices.iomessage import IoMessage +from iofog_python_sdk.microservices.listener import * +from iofog_python_sdk.microservices.definitions import * import sys +import time +import logging + +def init_logger(): + logger = logging.getLogger(__name__) + logger.setLevel('DEBUG') + ch = logging.StreamHandler() + ch.setLevel('DEBUG') + formatter = logging.Formatter( + '%(levelname)5s [%(asctime)-15s] %(module)10s - - %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + def build_message(): msg = IoMessage() @@ -21,10 +35,11 @@ def build_message(): return msg def python3(): + logger = logging.getLogger(__name__) try: client = IoFogClient() except IoFogException as e: - print("Client Failed on Python3") + logger.error("Client Failed on Python3") exit(e) msg = build_message() @@ -33,17 +48,18 @@ def python3(): try: receipt = client.post_message(msg) except IoFogException as e: - print("Message Failed on Python3") + logger.error("Message Failed on Python3") exit(e) - print("Python3 working with SDK") + logger.info("Python3 working with SDK") def python2(): + logger = logging.getLogger(__name__) try: client = IoFogClient() except IoFogException as e: - print("Client Failed on Python2") + logger.error("Client Failed on Python2") exit(e) msg = build_message() @@ -52,17 +68,27 @@ def python2(): try: receipt = client.post_message(msg) except IoFogException as e: - print("Message Failed on Python2") + logger.error("Message Failed on Python2") exit(e) - print("Python2 working with SDK") + logger.info("Python2 working with SDK") + + def main(): - if sys.version_info[0] > 3: + init_logger() + if sys.version_info[0] >= 3: print("test python3") while True: + time.sleep(2) python3() else: print("test python2") while True: - python2() \ No newline at end of file + time.sleep(2) + python2() + + + +if __name__ == "__main__": + main() \ No newline at end of file