diff --git a/cli/rmaker_cmd/provision.py b/cli/rmaker_cmd/provision.py index f86ea02..8b91d6a 100644 --- a/cli/rmaker_cmd/provision.py +++ b/cli/rmaker_cmd/provision.py @@ -14,22 +14,22 @@ import uuid import time -from packaging import version import sys -try: - from rmaker_lib.logger import log - from rmaker_lib import session, configmanager, node - from rmaker_lib.exceptions import NetworkError, SSLError -except ImportError as err: - print("Failed to import ESP Rainmaker library. " + str(err)) - raise err - -MINIMUM_PROTOBUF_VERSION = '3.10.0' TRANSPORT_MODE_SOFTAP = 'softap' MAX_HTTP_CONNECTION_RETRIES = 5 -PROVISION_FAILURE_MSG = 'Provisioning Failed. Reset your board to factory' -'defaults and retry.' +PROVISION_FAILURE_MSG = ('Provisioning Failed. Reset your board to factory ' + 'defaults and retry.') + +try: + from rmaker_tools.rmaker_prov.esp_rainmaker_prov import provision_device + from rmaker_lib.logger import log + from rmaker_lib import session, configmanager, node + from rmaker_lib.exceptions import NetworkError, SSLError,\ + RequestTimeoutError +except ImportError as err: + print("Failed to import ESP Rainmaker library.\n" + str(err)) + raise err def provision(vars=None): @@ -46,19 +46,6 @@ def provision(vars=None): :return: None on Success and Failure :rtype: None """ - try: - from rmaker_tools.rmaker_prov.esp_rainmaker_prov\ - import provision_device - except ImportError as err: - import google.protobuf - if version.parse(google.protobuf.__version__)\ - < version.parse(MINIMUM_PROTOBUF_VERSION): - log.warn('Package protobuf does not satisfy\ - the minimum required version.\n' - 'Minimum required version is ' + MINIMUM_PROTOBUF_VERSION) - else: - log.error('Provisioning failed due to import error.', err) - raise err log.info('Provisioning the node.') secret_key = str(uuid.uuid4()) pop = vars['pop'] @@ -78,14 +65,14 @@ def provision(vars=None): node_id = provision_device(TRANSPORT_MODE_SOFTAP, pop, userid, secret_key) if node_id is None: - print(PROVISION_FAILURE_MSG) + log.error(PROVISION_FAILURE_MSG) return log.debug('Node ' + node_id + ' provisioned successfully.') print('------------------------------------------') - input('Please ensure host machine is connected to internet and' + input('Please ensure host machine is connected to internet and ' 'Press Enter to continue...') - print('Adding User-Node association...') + retries = MAX_HTTP_CONNECTION_RETRIES node_object = None while retries > 0: @@ -95,90 +82,120 @@ def provision(vars=None): node_object = node.Node(node_id, session.Session()) except SSLError: log.error(SSLError()) - print(PROVISION_FAILURE_MSG) - return - except NetworkError: - time.sleep(5) - log.warn("Session is expired. Initialising new session.") - pass + break + except (NetworkError, RequestTimeoutError) as conn_err: + print(conn_err) + log.warn(conn_err) except Exception as node_init_err: log.error(node_init_err) - print(PROVISION_FAILURE_MSG) - return + break else: break + time.sleep(5) retries -= 1 + if retries: + print("Retries left:", retries) + log.info("Retries left: " + str(retries)) if node_object is None: - print('Please check the internet connectivity.') - print(PROVISION_FAILURE_MSG) + log.error('Initialising new session...Failed\n' + + '\n' + PROVISION_FAILURE_MSG) return + + print('\nAdding User-Node association') + log.info("Adding User-Node association") + retries = MAX_HTTP_CONNECTION_RETRIES request_id = None + log.info('Sending User-Node Association Request...') while retries > 0: + print('Sending User-Node Association Request...') try: - log.debug('Adding user-node association.') request_id = node_object.add_user_node_mapping(secret_key) except SSLError: log.error(SSLError()) - print(PROVISION_FAILURE_MSG) - return - except Exception as user_node_mapping_err: - print('Sending User-Node association request to ' - 'ESP RainMaker Cloud - Failed\nRetrying...') - log.warn(user_node_mapping_err) - pass + break + except (NetworkError, RequestTimeoutError) as conn_err: + print(conn_err) + log.warn(conn_err) + except Exception as mapping_err: + print(mapping_err) + log.warn(mapping_err) + break else: if request_id is not None: - log.debug('User-node mapping added successfully' - 'with request_id' + log.debug('User-Node mapping added successfully ' + 'with request_id ' + request_id) break - time.sleep(5) + retries -= 1 + if retries: + print("Retries left:", retries) + log.info("Retries left: " + str(retries)) + time.sleep(5) if request_id is None: - print('Sending User-Node association request to' - 'ESP RainMaker Cloud - Failed') - print(PROVISION_FAILURE_MSG) + log.error('User-Node Association Request...Failed\n' + + '\n' + PROVISION_FAILURE_MSG) return - print('Sending User-Node association request to' - 'ESP RainMaker Cloud - Successful') + print('User-Node Association Request...Success') + log.info('User-Node Association Request...Success') + + retries = MAX_HTTP_CONNECTION_RETRIES status = None - while True: - log.debug('Checking user-node association status.') + log.info('Checking User-Node Association Status...') + while retries > 0: + print('Checking User-Node Association Status...') try: status = node_object.get_mapping_status(request_id) except SSLError: log.error(SSLError()) - print(PROVISION_FAILURE_MSG) - return + break + except (NetworkError, RequestTimeoutError) as conn_err: + print(conn_err) + log.warn(conn_err) + status = None except Exception as mapping_status_err: + print(mapping_status_err) log.warn(mapping_status_err) - pass + break else: - log.debug('User-node association status ' + status) if status == 'requested': - print('Checking User Node association status -' - 'Requested\nRetrying...') + print('User-Node Association Status - Requested' + '\n') + log.debug('User-Node Association Status - Requested' + '\n') elif status == 'confirmed': - print('Checking User Node association status - Confirmed') - print('Provisioning was Successful.') - return + print('User-Node Association Status - Confirmed' + '\nProvisioning was Successful.') + log.debug('User-Node Association Status - Confirmed' + '\nProvisioning was Successful.') + break elif status == 'timedout': - print('Checking User Node association status - Timeout') - print(PROVISION_FAILURE_MSG) - return + print('User-Node Association Status - Timedout') + log.debug('User-Node Association Status - Timedout') + break elif status == 'discarded': - print('Checking User Node association status - Discarded') - print(PROVISION_FAILURE_MSG) - return + print('User-Node Association Status - Discarded') + log.debug('User-Node Association Status - Discarded') + break + else: + log.debug('User-Node Association Status - ' + status) + break + + if status not in ["requested"]: + retries -= 1 + if retries: + print("Retries left:", retries) + log.info("Retries left: " + str(retries)) time.sleep(5) - if status is None: - print(PROVISION_FAILURE_MSG) - print('Checking User Node association status failed. ' - 'Please check the internet connectivity.') + if status not in ["confirmed"]: + log.error('Checking User-Node Association Status...Failed.\n' + '\nCould not confirm User-Node Association Status. ' + '\nPlease use cli command ' + '`python3 rainmaker.py getnodes` to confirm.') return return diff --git a/cli/rmaker_lib/configmanager.py b/cli/rmaker_lib/configmanager.py index b4e2679..cf5bdf9 100644 --- a/cli/rmaker_lib/configmanager.py +++ b/cli/rmaker_lib/configmanager.py @@ -18,6 +18,7 @@ import os import base64 import time import requests +import socket from pathlib import Path from os import path @@ -27,7 +28,8 @@ from rmaker_lib.exceptions import NetworkError,\ InvalidUserError,\ InvalidApiVersionError,\ ExpiredSessionError,\ - SSLError + SSLError,\ + RequestTimeoutError from rmaker_lib.logger import log CONFIG_DIRECTORY = '.espressif/rainmaker' @@ -227,11 +229,17 @@ class Config: if access_token is None: raise InvalidConfigError if self.__is_valid_token() is False: + print('Previous Session expired. Initialising new session...') + log.info('Previous Session expired. Initialising new session...') username = self.get_token_attribute('email') refresh_token = self.get_refresh_token() access_token, id_token = self.__get_new_token(username, refresh_token) self.update_config(access_token, id_token) + print('Previous Session expired. Initialising new session...' + 'Success') + log.info('Previous Session expired. Initialising new session...' + 'Success') return access_token def get_user_id(self): @@ -270,7 +278,6 @@ class Config: current_timestamp = int(time.time()) if exp_timestamp > current_timestamp: return True - log.info("Session expired.") return False def __is_valid_version(self): @@ -285,16 +292,20 @@ class Config: :return: True on Success, False on Failure :rtype: bool """ + socket.setdefaulttimeout(10) log.info("Checking for supported version.") path = 'apiversions' request_url = serverconfig.HOST.split(serverconfig.VERSION)[0] + path try: log.debug("Version check request url : " + request_url) - response = requests.get(url=request_url, verify=CERT_FILE) + response = requests.get(url=request_url, verify=CERT_FILE, + timeout=(5.0, 5.0)) log.debug("Version check response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError + except requests.exceptions.Timeout: + raise RequestTimeoutError except requests.exceptions.ConnectionError: raise NetworkError except Exception as ver_err: @@ -331,6 +342,7 @@ class Config: :rtype: str | None """ + socket.setdefaulttimeout(10) log.info("Extending user login session.") path = 'login' request_payload = { @@ -343,13 +355,16 @@ class Config: log.debug("Extend session url : " + request_url) response = requests.post(url=request_url, data=json.dumps(request_payload), - verify=CERT_FILE) + verify=CERT_FILE, + timeout=(5.0, 5.0)) response.raise_for_status() log.debug("Extend session response : " + response.text) except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError + except requests.exceptions.Timeout: + raise RequestTimeoutError except Exception: raise ExpiredSessionError diff --git a/cli/rmaker_lib/exceptions.py b/cli/rmaker_lib/exceptions.py index c88d920..2a6f97a 100644 --- a/cli/rmaker_lib/exceptions.py +++ b/cli/rmaker_lib/exceptions.py @@ -16,8 +16,15 @@ class NetworkError(Exception): """ Raised when internet connection is not available """ def __str__(self): - return 'Please check the internet connectivity.\ - No internet connection available.' + return ('Could not connect. ' + 'Please check your Internet connection.') + + +class RequestTimeoutError(Exception): + """ Raised when HTTP Request times out """ + def __str__(self): + return ('HTTP Request timed out. ' + 'Please check your Internet connection.') class InvalidJSONError(Exception): diff --git a/cli/rmaker_lib/node.py b/cli/rmaker_lib/node.py index a3cf9f2..640555f 100644 --- a/cli/rmaker_lib/node.py +++ b/cli/rmaker_lib/node.py @@ -14,8 +14,12 @@ import requests import json +import socket from rmaker_lib import serverconfig, configmanager -from rmaker_lib.exceptions import NetworkError, InvalidClassInput, SSLError +from requests.exceptions import Timeout, ConnectionError,\ + RequestException +from rmaker_lib.exceptions import NetworkError, InvalidClassInput, SSLError,\ + RequestTimeoutError from rmaker_lib.logger import log @@ -213,6 +217,7 @@ class Node: :return: Request Id if Success, None if Failure :rtype: str | None """ + socket.setdefaulttimeout(10) path = 'user/nodes/mapping' config = configmanager.Config() userid = config.get_user_id() @@ -231,15 +236,22 @@ class Node: response = requests.put(url=request_url, data=json.dumps(request_payload), headers=self.__request_header, - verify=configmanager.CERT_FILE) + verify=configmanager.CERT_FILE, + timeout=(5.0, 5.0)) log.debug("User node mapping response : " + response.text) response.raise_for_status() - except requests.exceptions.SSLError: + except requests.exceptions.SSLError as ssl_err: + log.debug(ssl_err) raise SSLError - except requests.exceptions.ConnectionError: + except (ConnectionError, socket.timeout) as conn_err: + log.debug(conn_err) raise NetworkError - except Exception: - raise Exception(response.text) + except Timeout as time_err: + log.debug(time_err) + raise RequestTimeoutError + except RequestException as mapping_status_err: + log.debug(mapping_status_err) + raise mapping_status_err try: response = json.loads(response.text) @@ -304,8 +316,9 @@ class Node: :return: Request Status on Success, None on Failure :type: str | None """ - log.info("Checking status of user node mapping with request_id : " + - request_id) + socket.setdefaulttimeout(10) + log.debug("Checking status of user node mapping with request_id : " + + request_id) path = 'user/nodes/mapping' query_parameters = "&request_id=" + request_id @@ -315,15 +328,22 @@ class Node: request_url) response = requests.get(url=request_url, headers=self.__request_header, - verify=configmanager.CERT_FILE) + verify=configmanager.CERT_FILE, + timeout=(5.0, 5.0)) log.debug("Check user node mapping status response : " + response.text) response.raise_for_status() - except requests.exceptions.SSLError: + except requests.exceptions.SSLError as ssl_err: + log.debug(ssl_err) raise SSLError - except requests.exceptions.ConnectionError: + except (ConnectionError, socket.timeout) as conn_err: + log.debug(conn_err) raise NetworkError - except Exception as mapping_status_err: + except Timeout as time_err: + log.debug(time_err) + raise RequestTimeoutError + except RequestException as mapping_status_err: + log.debug(mapping_status_err) raise mapping_status_err try: diff --git a/cli/rmaker_tools/rmaker_prov/transport/transport_http.py b/cli/rmaker_tools/rmaker_prov/transport/transport_http.py index 066975a..0a6902c 100644 --- a/cli/rmaker_tools/rmaker_prov/transport/transport_http.py +++ b/cli/rmaker_tools/rmaker_prov/transport/transport_http.py @@ -35,7 +35,8 @@ class Transport_HTTP(Transport): ssl_ctx = ssl.create_default_context(cafile=certfile) self.conn = http.client.HTTPSConnection(hostname, context=ssl_ctx, timeout=30) try: - print("Connecting to " + hostname) + print("Connecting to " + hostname + + " (this may take some time)") self.conn.connect() except Exception as err: raise RuntimeError("Connection Failure : " + str(err)) diff --git a/docs/conf.py b/docs/conf.py index 4c9e133..11affa3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,7 @@ import sys sys.path.insert(0, os.path.abspath('.')) sys.path.append(os.path.abspath('../cli/')) -autodoc_mock_imports = ["pathlib", "cryptography", "nvs_partition_gen", "oauth2client", "serial", "user_mapping", "rmaker_tools.esp_rainmaker_prov.security","rmaker_tools.esp_rainmaker_prov.prov", "rmaker_tools.esp_rainmaker_prov.prov_util", "rmaker_tools.rmaker_claim.claim"] +autodoc_mock_imports = ["pathlib", "cryptography", "nvs_partition_gen", "oauth2client", "serial", "user_mapping", "rmaker_tools.rmaker_prov.esp_rainmaker_prov", "rmaker_tools.rmaker_prov.security", "rmaker_tools.rmaker_prov.prov", "rmaker_tools.rmaker_prov.prov_util", "rmaker_tools.rmaker_claim.claim"] # -- Project information -----------------------------------------------------