From abd00f9e04cf0f0d58cd3e8199c4c1e50858b8c5 Mon Sep 17 00:00:00 2001 From: Quentin WEPHRE Date: Thu, 4 Dec 2025 08:11:00 +0100 Subject: [PATCH] multiple evolutions --- Moulinette/data_path_config.py | 4 +- .../azure_iot_hub_get_connection_strings.py | 11 +- Python/azure_iot_hub_list_devices.py | 10 +- Python/azure_iot_hub_thingspro_api_call.py | 64 +++ Python/cube_activate_ssh.py | 20 +- Python/cube_ssh_batch.py | 6 +- Python/danish_batch_api copy.py | 430 ++++++++---------- Python/requirements.txt | 3 +- Python/ssh_fabric_batch.py | 119 ++++- 9 files changed, 403 insertions(+), 264 deletions(-) create mode 100644 Python/azure_iot_hub_thingspro_api_call.py diff --git a/Moulinette/data_path_config.py b/Moulinette/data_path_config.py index 082a9eb..7bcadd5 100644 --- a/Moulinette/data_path_config.py +++ b/Moulinette/data_path_config.py @@ -40,7 +40,7 @@ allowed_name_characters.append('.') logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', filename='data_config_debug.log', filemode='w', level=logging.DEBUG, datefmt='%Y%m%d%H%M%S') dir_name = 'I-Sight_Generated_Files' -input_datamodel = 'DATAMODEL_1.0.6_LIBERTY.xlsx' +input_datamodel = 'DATAMODEL_1.0.6_CW.xlsx' shell_script_name = dir_name + '/I-Sight_Configuration_' global_shell_script_name = dir_name + '/I-Sight_Global_Configuration.sh' @@ -208,7 +208,7 @@ def jq_filter(current_device, dsh, dsh_global, row_device): jq_data["enable"] = False jq_data["properties"] = [{"key": "deviceType", "value": "AC_GATEWAY"}, {"key": "cdid", "value": current_device}] jq_data["outputTopic"] = filter - jq_data["sendOutThreshold"] = {"mode": "bySize", "size": int(128000), "time": int(30), "sizeIdleTimer": {"enable": True, "time": int(30)}} + jq_data["sendOutThreshold"] = {"mode": "bySize", "size": int(128000), "time": int(30), "sizeIdleTimer": {"enable": True, "time": int(5)}} jq_data["minPublishInterval"] = int(0) jq_data["samplingMode"] = "allValues" jq_data["customSamplingRate"] = False diff --git a/Python/azure_iot_hub_get_connection_strings.py b/Python/azure_iot_hub_get_connection_strings.py index bf908ce..6e8c426 100644 --- a/Python/azure_iot_hub_get_connection_strings.py +++ b/Python/azure_iot_hub_get_connection_strings.py @@ -15,20 +15,21 @@ if CONNECTION_STRING == "": print("Provide a connection string for the Iot Hub before running the script!") exit(13) -SITE_NAME = "LIBERTY" +SITE_NAME = "Bell" registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING) -query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND tags.site = '" + SITE_NAME + "' AND capabilities.iotEdge = true") +#query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND tags.site = '" + SITE_NAME + "' AND capabilities.iotEdge = true") +query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND tags.site = '" + SITE_NAME + "' AND capabilities.iotEdge = false") query_result = registry_manager.query_iot_hub(query_spec) devices = [] for item in query_result.items: deviceId = str(item.device_id) - site = str(item.tags['site']) - number = int(item.tags['number']) - cloud_version = str(item.tags['version']) + site = str(item.tags.get('site')) if item.tags.get('site') else None + number = int(item.tags.get('number')) if item.tags.get('number') else None + cloud_version = str(item.tags.get('version')) if item.tags.get('version') else None devices.append([deviceId, site, number, cloud_version]) ordered_devices = sorted(devices, key = lambda x: (x[1], x[2])) diff --git a/Python/azure_iot_hub_list_devices.py b/Python/azure_iot_hub_list_devices.py index aba3172..b622e26 100644 --- a/Python/azure_iot_hub_list_devices.py +++ b/Python/azure_iot_hub_list_devices.py @@ -31,14 +31,18 @@ for twin in query_result.items: "number": twin.tags.get("number") if twin.tags else None, "site": twin.tags.get("site") if twin.tags else None, "connection_state": twin.connection_state, - "last_activity_time": twin.last_activity_time, + "last_activity_time": twin.last_activity_time }) df = pd.DataFrame(rows) - +df['number'] = pd.to_numeric(df['number'], errors='coerce') +df['number'] = df['number'].astype('Int64') df_sorted = df.sort_values(by=["site", "number"]).reset_index(drop=True) -print(df_sorted) +for row in df_sorted.itertuples(): + if "cube" not in row.device_id: + print(f"\"{row.device_id}\", \"{row.site}\", \"{row.number}\",") + # Compute difference in hours (float) df_sorted["time_since_last_activity_hours"] = df_sorted["last_activity_time"].apply( diff --git a/Python/azure_iot_hub_thingspro_api_call.py b/Python/azure_iot_hub_thingspro_api_call.py new file mode 100644 index 0000000..16dccd9 --- /dev/null +++ b/Python/azure_iot_hub_thingspro_api_call.py @@ -0,0 +1,64 @@ +from azure.iot.hub import IoTHubRegistryManager +from azure.iot.hub.protocol.models import QuerySpecification, Module +from azure.iot.hub.models import CloudToDeviceMethod, CloudToDeviceMethodResult +from dotenv import load_dotenv +from isight_device import iSightDevice + +import json +import os +import pandas as pd + +load_dotenv() + +CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD")) +if CONNECTION_STRING == "": + print("Provide a connection string for the Iot Hub before running the script!") + exit(13) + +registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING) +query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND capabilities.iotEdge = true ") + +query_result = registry_manager.query_iot_hub(query_spec) + +devices = [] +for item in query_result.items: + devices.append(iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version']))) + +devices.sort(key = lambda d: (d.site, d.number)) + +rows = [] + +for device in devices: + print(device, end="\t") + current_device_modules = registry_manager.get_modules(device.deviceId) + for module in current_device_modules: + if (module.module_id == "thingspro-agent"): + device.setModule(module) + method_name = "thingspro-api-v1" + payload = { + "method": "GET", + "path": "/device/general" + } + module_id = "thingspro-agent" + try: + direct_method = CloudToDeviceMethod(method_name = method_name, payload = payload) + response = registry_manager.invoke_device_module_method(device_id = device.deviceId, module_id = module_id, direct_method_request = direct_method) + #print(response) + #print(json.dumps(response.payload, indent = 2)) + hostname = response.payload['data']['hostName'] + serial = response.payload['data']['serialNumber'] + version = response.payload['data']['firmwareVersion'] + description = response.payload['data']['description'] + print(hostname + " " + serial + " " + version + " " + description) + rows.append({ + "hostname": hostname, + "site": device.getSite(), + "number": device.getNumber(), + "cloud_version": device.getVersion(), + "device_version": version + }) + except Exception as e: + print(f"Error: {e}") + +df = pd.DataFrame(rows) +df.to_excel("thingspro-version.xlsx", index=False) \ No newline at end of file diff --git a/Python/cube_activate_ssh.py b/Python/cube_activate_ssh.py index a977ffd..2bbfbe5 100644 --- a/Python/cube_activate_ssh.py +++ b/Python/cube_activate_ssh.py @@ -87,7 +87,7 @@ def set_ssh_status(base_url, token): else: raise -def activate_ssh(ip_address): +def activate_ssh(ip_address, silent = False): # Ensure the URL uses HTTPS url = ip_address @@ -104,17 +104,23 @@ def activate_ssh(ip_address): if not verify_ssl: urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - print(f"HTTPS", end=" ", flush=True) + if not silent: + print(f"HTTPS", end=" ", flush=True) try: token = authenticate(url) - print(f"✅", end="", flush=True) + if not silent: + print(f"✅", end="", flush=True) except Exception as e: - print(f"❌", flush=True) + if not silent: + print(f"❌", flush=True) raise - print(f"SSH", end=" ", flush=True) + if not silent: + print(f"SSH", end=" ", flush=True) try: set_ssh_status(url, token) - print(f"✅", end="\n", flush=True) + if not silent: + print(f"✅", end="\n", flush=True) except Exception as e: - print(f"❌", flush=True) + if not silent: + print(f"❌", flush=True) raise \ No newline at end of file diff --git a/Python/cube_ssh_batch.py b/Python/cube_ssh_batch.py index dfe5348..b8c3d14 100644 --- a/Python/cube_ssh_batch.py +++ b/Python/cube_ssh_batch.py @@ -10,11 +10,11 @@ from azure.iot.hub.models import Twin, TwinProperties load_dotenv(override=True) -ip_address_prefix = "10.81.60." +ip_address_prefix = "10.81.56." ssh_command = "hostname" -csv_filename = "Grandpuits_01.csv" -SITE_NAME = "Grandpuits" +csv_filename = "DK2_01.csv" +SITE_NAME = "DK2" ssh_username = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_USER") ssh_password = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_PASSWORD") diff --git a/Python/danish_batch_api copy.py b/Python/danish_batch_api copy.py index dd60f83..2890a1a 100644 --- a/Python/danish_batch_api copy.py +++ b/Python/danish_batch_api copy.py @@ -6,24 +6,28 @@ import json import os from dotenv import load_dotenv import time +from tqdm import tqdm load_dotenv() # Function to authenticate and get token def authenticate(device_ip, payload): auth_url = f"https://{device_ip}:8443/api/v1/auth" - response = requests.post(auth_url, json=payload, verify=False) - if response.status_code == 200: - token = response.json()["data"]["token"] - #print(f"Authentication successful. Token received: {token}") - #print(" authenticated!") - return token - else: - print(f"Authentication failed. Status code: {response.status_code}") - return None + try: + response = requests.post(auth_url, json=payload, verify=False) + if response.status_code == 200: + token = response.json()["data"]["token"] + #print(f"Authentication successful. Token received: {token}") + #print(" authenticated!") + return token + else: + print(f"Authentication failed. Status code: {response.status_code}") + return None + except Exception as e: + raise Exception("Authentication failed!") from e -# Function to send PATCH request -def send_patch_request(device_ip, token, connection_string): +# Function to update connection string through PATCH request +def update_connection_string(device_ip, token, connection_string): headers = { "mx-api-token": token } @@ -35,45 +39,50 @@ def send_patch_request(device_ip, token, connection_string): } } patch_url = f"https://{device_ip}:8443/api/v1/azure-iotedge" - response = requests.patch(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - print(f"PATCH request successful for device {device_ip}") - else: - print(f"Failed to send PATCH request to device {device_ip}. Status code: {response.status_code}") + try: + response = requests.patch(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + print(f"PATCH request successful for device {device_ip}") + else: + print(f"Failed to send PATCH request to device {device_ip}. Status code: {response.status_code}") + except Exception as e: + raise Exception("Update connection string failed!") from e -# Function to send PATCH request -def patch_time(device_ip, token): +# Function to update NTP +def update_ntp(device_ip, token): headers = { "mx-api-token": token } - # payload = { - # "ntp": { - # "source": "timeserver", - # "server": "10.84.171.254", - # "enable": True - # } - # } + payload = { + "ntp": { + "source": "timeserver", + "server": "10.84.171.254", + "enable": True + } + } payload = { "timezone": "America/Chicago" } patch_url = f"https://{device_ip}:8443/api/v1/device/time" - response = requests.patch(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - json_data = json.loads(response.content.decode()) - time = json_data['data']['time'] - timezone = json_data['data']['timezone'] - last = json_data['data']['lastUpdateTime'] - server = json_data['data']['ntp']['server'] - enabled = json_data['data']['ntp']['enable'] - print(time + " " + timezone + " " + last + " " + server + " " + str(enabled)) - else: - json_data = json.loads(response.content.decode()) - #print(json.dumps(json_data, indent=2)) + try: + response = requests.patch(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + json_data = json.loads(response.content.decode()) + time = json_data['data']['time'] + timezone = json_data['data']['timezone'] + last = json_data['data']['lastUpdateTime'] + server = json_data['data']['ntp']['server'] + enabled = json_data['data']['ntp']['enable'] + print(time + " " + timezone + " " + last + " " + server + " " + str(enabled)) + else: + json_data = json.loads(response.content.decode()) + except Exception as e: + raise Exception("Update NTP failed!") from e -# Function to send UPGRADE request -def send_upgrade_request(device_ip, token, upgrade_url): +# Function to create an upgrade job +def create_upgrade_job(device_ip, token, upgrade_url): headers = { "mx-api-token": token } @@ -83,13 +92,15 @@ def send_upgrade_request(device_ip, token, upgrade_url): "url": upgrade_url, } patch_url = f"https://{device_ip}:8443/api/v1/upgrades" - response = requests.post(patch_url, json=payload, headers=headers, verify=False) - json_data = json.loads(response.content.decode()) - #print(json.dumps(json_data, indent=4, sort_keys=True)) - id = json_data['data']['id'] - return id + try: + response = requests.post(patch_url, json=payload, headers=headers, verify=False) + json_data = json.loads(response.content.decode()) + id = json_data['data']['id'] + return id + except Exception as e: + raise Exception("Create upgrade job failed") from e -# Function to send UPGRADE request +# Function to get upgrade job def get_upgrade_job(device_ip, token, id): headers = { "mx-api-token": token @@ -97,55 +108,24 @@ def get_upgrade_job(device_ip, token, id): payload = { } patch_url = f"https://{device_ip}:8443/api/v1/upgrades/{id}" - response = requests.get(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - #print(f"GET request successful for device {device_ip}") - #print(json_str) - #print(json_data['data'][json_data['count'] - 1]['parameter']['url'], json_data['data'][json_data['count'] - 1]['state'], json_data['count']) - json_data = json.loads(response.content.decode()) - getid = json_data['data']['id'] - created = json_data['data']['createdAt'] - cur_status = json_data['data']['state'] - current_tasks = json_data['data']['completedTask'] - total_tasks = json_data['data']['totalTask'] - print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks)) - print("CREATED ON: " + str(created)) - print("CURRENT STATUS: " + str(cur_status)) - print(json.dumps(json_data, indent=2)) - else: - print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") - print(response.content) + try: + response = requests.get(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + json_data = json.loads(response.content.decode()) + getid = json_data['data']['id'] + created = json_data['data']['createdAt'] + started = json_data['data']['startedAt'] + cur_status = json_data['data']['state'] + current_tasks = json_data['data']['completedTask'] + total_tasks = json_data['data']['totalTask'] + print(f"Upgrade job #{str(getid)} ({str(current_tasks)}/{str(total_tasks)}) {str(cur_status)}") + else: + print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") + print(response.content) + except Exception as e: + raise Exception("Failed getting upgrade job!") from e def get_upgrade_jobs(device_ip, token): - headers = { - "mx-api-token": token - } - payload = { - } - patch_url = f"https://{device_ip}:8443/api/v1/upgrades" - response = requests.get(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - #print(f"GET request successful for device {device_ip}") - #print(json_str) - #print(json_data['data'][json_data['count'] - 1]['parameter']['url'], json_data['data'][json_data['count'] - 1]['state'], json_data['count']) - json_data = json.loads(response.content.decode()) - count = json_data['count'] - print(str(count)) - for i in range(count): - getid = json_data['data'][i]['id'] - created = json_data['data'][i]['createdAt'] - cur_status = json_data['data'][i]['state'] - current_tasks = json_data['data'][i]['completedTask'] - total_tasks = json_data['data'][i]['totalTask'] - print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks)) - print("CREATED ON: " + str(created)) - print("CURRENT STATUS: " + str(cur_status)) - print(json.dumps(json_data, indent=4, sort_keys=True)) - else: - print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") - print(response.content) - -def get_last_job(device_ip, token): headers = { "mx-api-token": token } @@ -155,9 +135,34 @@ def get_last_job(device_ip, token): try: response = requests.get(patch_url, json=payload, headers=headers, verify=False) if response.status_code == 200: - #print(f"GET request successful for device {device_ip}") - #print(json_str) - #print(json_data['data'][json_data['count'] - 1]['parameter']['url'], json_data['data'][json_data['count'] - 1]['state'], json_data['count']) + json_data = json.loads(response.content.decode()) + count = json_data['count'] + print(str(count)) + for i in range(count): + getid = json_data['data'][i]['id'] + created = json_data['data'][i]['createdAt'] + cur_status = json_data['data'][i]['state'] + current_tasks = json_data['data'][i]['completedTask'] + total_tasks = json_data['data'][i]['totalTask'] + print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks)) + print("CREATED ON: " + str(created)) + print("CURRENT STATUS: " + str(cur_status)) + else: + print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") + print(response.content) + except Exception as e: + raise Exception("Failed getting all upgrade jobs!") from e + +def get_last_upgrade_job(device_ip, token): + headers = { + "mx-api-token": token + } + payload = { + } + patch_url = f"https://{device_ip}:8443/api/v1/upgrades" + try: + response = requests.get(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: json_data = json.loads(response.content.decode()) last_job = int(json_data['count'] - 1) getid = json_data['data'][last_job]['id'] @@ -171,15 +176,14 @@ def get_last_job(device_ip, token): for task in range(total_tasks): if json_data['data'][last_job]['tasks'][task]['type'] == "download": print(f"Downloaded: {json_data['data'][last_job]['tasks'][task]['progress']}%") - #print(json.dumps(json_data, indent=4, sort_keys=True)) return getid else: print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") print(response.content) except Exception as e: - print(f"Connection failed {e}") + raise Exception("Failed getting last upgrade job!") from e -# Function to send UPGRADE request +# Function to start upgrade job def start_upgrade_job(device_ip, token, id): headers = { "mx-api-token": token @@ -187,60 +191,61 @@ def start_upgrade_job(device_ip, token, id): payload = { } patch_url = f"https://{device_ip}:8443/api/v1/upgrades/{id}/start" - response = requests.put(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - json_data = json.loads(response.content.decode()) - curid = json_data['data']['id'] - startedat = json_data['data']['startedAt'] - print("Job #" + str(curid) + " started on " + str(startedat)) - else: - print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") - print(response.content.decode()) + try: + response = requests.put(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + json_data = json.loads(response.content.decode()) + curid = json_data['data']['id'] + startedat = json_data['data']['startedAt'] + print("Job #" + str(curid) + " started on " + str(startedat)) + else: + print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") + print(response.content.decode()) + except Exception as e: + raise Exception("Failed starting upgrade job!") from e -# Function to send UPGRADE request +# Function to send a PUT request def put_API(device_ip, token): headers = { "mx-api-token": token } payload = { } - patch_url = f"https://{device_ip}:8443/api/v1/upgrades/3/start" - response = requests.put(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - #print(f"GET request successful for device {device_ip}") - #json_data = json.loads(response.content.decode()) - #json_str = json.dumps(json_data) - #print(jq.compile('.data.completedAt').input(json.loads(json_str)).first()) - json_data = json.loads(response.content.decode()) - print(json_data['data']['firmwareVersion']) - else: - print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") - print(response.content.decode()) + patch_url = "" #f"https://{device_ip}:8443/api/v1/upgrades/3/start" + try: + if patch_url == "": + raise Exception("Empty URL!") + response = requests.put(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + json_data = json.loads(response.content.decode()) + print(json_data['data']['firmwareVersion']) + else: + print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") + print(response.content.decode()) + except Exception as e: + raise Exception("Failed sending PUT request!") from e -def get_API(device_ip, token): +def get_version(device_ip, token): headers = { "mx-api-token": token } payload = { } patch_url = f"https://{device_ip}:8443/api/v1/device/general" - response = requests.get(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - #print(f"GET request successful for device {device_ip}") - #json_data = json.loads(response.content.decode()) - #json_str = json.dumps(json_data) - #print(jq.compile('.data.completedAt').input(json.loads(json_str)).first()) - json_data = json.loads(response.content.decode()) - hostname = json_data['data']['hostName'] - serial = json_data['data']['serialNumber'] - version = json_data['data']['firmwareVersion'] - description = json_data['data']['description'] - print(hostname + " " + serial + " " + version + " " + description) - #json_str = json.dumps(json_data, indent=2) - #print(json_str) - else: - print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") - print(response.content.decode()) + try: + response = requests.get(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + json_data = json.loads(response.content.decode()) + hostname = json_data['data']['hostName'] + serial = json_data['data']['serialNumber'] + version = json_data['data']['firmwareVersion'] + description = json_data['data']['description'] + print(hostname + " " + serial + " " + version + " " + description) + else: + print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") + print(response.content.decode()) + except Exception as e: + raise Exception("Failed getting version!") from e def get_time(device_ip, token): headers = { @@ -249,22 +254,19 @@ def get_time(device_ip, token): payload = { } patch_url = f"https://{device_ip}:8443/api/v1/device/time" - response = requests.get(patch_url, json=payload, headers=headers, verify=False) - if response.status_code == 200: - #print(f"GET request successful for device {device_ip}") - #json_data = json.loads(response.content.decode()) - #json_str = json.dumps(json_data) - #print(jq.compile('.data.completedAt').input(json.loads(json_str)).first()) - json_data = json.loads(response.content.decode()) - time = json_data['data']['time'] - timezone = json_data['data']['timezone'] - last = json_data['data']['lastUpdateTime'] - print(time + " " + timezone + " " + last) - #json_str = json.dumps(json_data, indent=2) - #print(json_str) - else: - print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") - print(response.content.decode()) + try: + response = requests.get(patch_url, json=payload, headers=headers, verify=False) + if response.status_code == 200: + json_data = json.loads(response.content.decode()) + time = json_data['data']['time'] + timezone = json_data['data']['timezone'] + last = json_data['data']['lastUpdateTime'] + print(time + " " + timezone + " " + last) + else: + print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}") + print(response.content.decode()) + except Exception as e: + raise Exception("Failed getting time!") from e def delete_API(device_ip, token): headers = { @@ -272,95 +274,63 @@ def delete_API(device_ip, token): } payload = { } - patch_url = f"https://{device_ip}:8443/api/v1/upgrades/5" - response = requests.delete(patch_url, json=payload, headers=headers, verify=False) - print(response.status_code) - json_data = json.loads(response.content.decode()) - json_str = json.dumps(json_data, indent=2) - print(json_str) + patch_url = "" #f"https://{device_ip}:8443/api/v1/upgrades/5" + try: + if patch_url == "": + raise Exception("Empty URL!") + response = requests.delete(patch_url, json=payload, headers=headers, verify=False) + print(response.status_code) + json_data = json.loads(response.content.decode()) + json_str = json.dumps(json_data, indent=2) + print(json_str) + except Exception as e: + raise Exception("Failed sending DELETE request!") from e -# Read the Excel file -# excel_file_path = "" -# if excel_file_path == "": -# print("Provide Excel file path before running the script!") -# exit(11) -# df = pd.read_excel(excel_file_path) -# df = df[df["device_name"].notnull()] - -# Iterate over each row in the DataFrame +def visual_wait(total_seconds): + # "total=total_seconds" sets the bar max value + # "bar_format" removes the default stats to keep it clean + with tqdm(total=total_seconds, bar_format="{desc} [{bar}]") as pbar: + # Loop backwards from 200 down to 1 + for remaining in range(total_seconds, 0, -1): + # Manually update the text description to show the countdown + pbar.set_description_str(f"Waiting {remaining}s") + + # Advance the visual bar by 1 step + pbar.update(1) + time.sleep(1) + + # Final update to show 0s at the very end + pbar.set_description_str(f"Finished waiting!") requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) -# for index, row in df.iterrows(): -# device_name = row['device_name'] -# device_ip_address_https = row['device_ip_address_http'] -# connection_string = row['connection_string'] -# upgrade_url = "" #https://files.thingsprocloud.com/package/moxa-aig-301-series-includes-security-patch-firmware-v1.0.deb.yaml -# if upgrade_url == "": -# print("Provide upgrade URL before running the script!") -# exit(12) - - -# # Authenticate and get token -# payload_auth = { -# "acceptEULA": True, -# "name": "", -# "password": "" -# } -# if payload_auth["name"] == "" or payload_auth["password"] == "": -# print("Provide the credentials before running the script!") -# exit(10) -# print(device_name, end="") -# token = authenticate(device_ip_address_https, payload_auth) -# if token: -# get_API(device_ip_address_https, token) -# print("\n") - default_user = str(os.getenv("DEFAULT_MOXA_USER")) default_password = str(os.getenv("DEFAULT_MOXA_PASSWORD")) -moxa_range = [i for i in range(131, 132) if i not in (136, 137)] +moxa_range = [i for i in range(132, 159) ] +moxa_range.remove(137) for i in moxa_range: device_ip_address = str("10.84.157." + str(i)) - print(device_ip_address, end=" ") + print(device_ip_address) payload_auth = { "acceptEULA": True, "name": default_user, "password": default_password } - token = authenticate(device_ip_address, payload_auth) - print(hash(token)) - upgrade_url = "http://10.84.157.137:8080/1.7.0.yaml" + upgrade_url = "http://10.84.157.137:8080/1.8.1.yaml" - if token: - #send_upgrade_request(device_ip_address, token, upgrade_url) - #time.sleep(10) - id = get_last_job(device_ip_address, token) - #time.sleep(10) - #start_upgrade_job(device_ip_address, token, id) - #time.sleep(120) - #input("Continue?") - else: - raise(Exception("Authentication failed!")) - - # upgrade_url = "https://10.84.157.137/Upgrade_AIG-301_2.5.0-4404_IMG_1.5_to_1.6.0.yaml" - - - # device_ip_address = str("10.84.157." + str(i)) - # print(f"{device_ip_address}") - - # #print(device_ip_address, end="") - # token = authenticate(device_ip_address, payload_auth) - # if token: - # #id = send_upgrade_request(device_ip_address,token,upgrade_url) - # #print(id) - # get_last_job(device_ip_address, token) - # #start_upgrade_job(device_ip_address, token, id) - # #put_API(device_ip_address, token) - # #patch_time(device_ip_address,token) - # #get_time(device_ip_address, token) - # #delete_API(device_ip_address,token) - # #get_API(device_ip_address, token) - # else: - # print("Authentication failed!") \ No newline at end of file + try: + token = authenticate(device_ip_address, payload_auth) + if token: + get_version(device_ip_address, token) + get_last_upgrade_job(device_ip_address, token) + # id = create_upgrade_job(device_ip_address, token, upgrade_url) + # visual_wait(3) + # start_upgrade_job(device_ip_address, token, id) + # visual_wait(3) + # get_upgrade_job(device_ip_address, token, id) + # visual_wait(30) + except Exception as e: + print(f"Exception for {device_ip_address}: {e}") + continue \ No newline at end of file diff --git a/Python/requirements.txt b/Python/requirements.txt index 82c65b0..93af15a 100644 --- a/Python/requirements.txt +++ b/Python/requirements.txt @@ -10,4 +10,5 @@ textual fabric ruamel.yaml netmiko -pexpect \ No newline at end of file +pexpect +tqdm \ No newline at end of file diff --git a/Python/ssh_fabric_batch.py b/Python/ssh_fabric_batch.py index 1fc225e..2371d86 100644 --- a/Python/ssh_fabric_batch.py +++ b/Python/ssh_fabric_batch.py @@ -175,6 +175,14 @@ def find_config_value(config_content, option): # If the loop finishes without finding the option, return None return None +def cloud_configuration_csv(result): + lightTelemetry = find_config_value(result, "light-telemetry") + telemetryOn = find_config_value(result, "telemetry-on") + compressionEnabled = find_config_value(result, "compression-enabled") + remoteUpdateOn = find_config_value(result, "remote-update-on") + connectionString = find_config_value(result, "connection-string") + return f"{lightTelemetry};{telemetryOn};{compressionEnabled};{remoteUpdateOn};{connectionString};" + def cloud_configuration_check(hostname, result, iot_hub, proxy_host, proxy_port): print(f"\tLight telemetry:", end=" ", flush=True) status = find_config_value(result, "light-telemetry") @@ -185,7 +193,7 @@ def cloud_configuration_check(hostname, result, iot_hub, proxy_host, proxy_port) print(f"\tTelemetry:", end=" ", flush=True) status = find_config_value(result, "telemetry-on") - if status == "true": + if status == "False": print(f"✅", end="\n", flush=True) else: print(f"❌") @@ -437,12 +445,55 @@ def write_remote_config_base64_sudo(c, remote_path, content, sudo_pass, user_own # Re-raise the exception for the main loop. raise +def check_for_specific_curl_error(c): + + def execute_command(c, command): + """Executes a simple command on the remote device.""" + try: + result = c.run(command, hide=True) + return result.stdout + except Exception as e: + raise + + """ + Checks for the specific cURL exit code 35. + + Args: + c: The connection object. + + Returns: + True if the expected error is caught, False otherwise. + """ + try: + # We call execute_command, but expect it to fail and raise an exception + result = execute_command(c, "curl -m 15 -x https://10.81.35.126:8080 https://iot-ingest-ess-prod.azure-devices.net") + + # If the command somehow succeeds, the expected error did not occur. + print(f"Success (unexpected): {result.strip()}", end="\n", flush=True) + return False + + except Exception as e: + # The command failed as expected. Now, check if it's the RIGHT failure. + error_message = str(e) + + # Check for the unique identifiers of your expected error. + is_exit_code_35 = "Exit code: 35" in error_message + is_ssl_version_error = "wrong version number" in error_message + + if is_exit_code_35 and is_ssl_version_error: + # This is the exact error you were expecting. + # print("Caught expected cURL error (Exit code 35, SSL wrong version number).") + return True + else: + # This is a different, unexpected error. + print(f"\n[cURL] An unexpected exception occurred: {e}") + return False def main(): """Main function to parse arguments and orchestrate tasks.""" - ip_address_prefix = "10.81.56." # DK2 subnet - ip_address_range = list(range(129, 145)) # From 129 to 144 (16 CUBEs) - # ip_address_range.append(72) # Add 85 after 74. + ip_address_prefix = "10.84.165." # DK2 subnet + ip_address_range = list(range(131, 188)) # From 129 to 144 (16 CUBEs) + # ip_address_range.append(85) # Add 85 after 74. hosts = [f"{ip_address_prefix}{suffix}" for suffix in ip_address_range] ssh_port = 11022 @@ -459,34 +510,47 @@ def main(): for host in hosts: - print(f"{host}", end=" - ", flush=True) + #print(f"{host}", end=" - ", flush=True) hostname = "" result = "" try: - activate_ssh(host) + activate_ssh(host, True) except Exception as e: print(f"Exception: {e}") continue with Connection(host=host, user=ssh_user, port=ssh_port, connect_timeout=60, connect_kwargs=connect_args) as c: + # try: + # print(f"Hostname:", end=" ", flush=True) + # result = execute_command(c, "hostname") + # print(f"{result.strip()}", end="\n", flush=True) + # hostname = str.lower(result) + # except Exception as e: + # print(f"[Hostname] Exception: {e}") + # continue + + # print(f"cURL:", end=" ", flush=True) + # result = check_for_specific_curl_error(c) + # if result: + # print(f"✅", end="\n", flush=True) + # else: + # print(f"❌", end="\n", flush=True) + try: - print(f"Hostname:", end=" ", flush=True) result = execute_command(c, "hostname") - print(f"{result.strip()}", end="\n", flush=True) + print(f"{host};{result.strip()}", end=";", flush=True) hostname = str.lower(result) except Exception as e: - print(f"[Hostname] Exception: {e}") + print(f"{host};ERROR") continue try: - print(f"cURL:", end=" ", flush=True) - result = execute_command(c, "curl -m 15 -x https://10.81.35.126:8080 https://iot-ingest-ess-prod.azure-devices.net") - print(f"{result.strip()}", end="\n", flush=True) + result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password) + print(cloud_configuration_csv(result)) except Exception as e: - print(f"[cURL] Exception: {e}") continue # try: @@ -508,6 +572,35 @@ def main(): # continue # cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080") + # result_telemetry_off = set_config_field(result, "telemetry-on", False) + # result = result_telemetry_off + # cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080") + + # try: + # write_remote_config_sudo(c, "/etc/cube/config-azure.properties", result, ssh_password, "cube", "root", "644") + # print(f"✅", end="\n", flush=True) + # except Exception as e: + # print(f"❌", end="\n", flush=True) + # print(f"[Proxy configuration] Exception: {e}") + # continue + + # print(f"Checking Cloud configuration:", end=" ", flush=True) + # try: + # result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password) + # print(f"✅", end="\n", flush=True) + # except Exception as e: + # print(f"❌", end="\n", flush=True) + # print(f"[Proxy verification] Exception: {e}") + # continue + + # try: + # print(f"Restarting cube-web-cloudagent: ", end=" ", flush=True) + # execute_sudo_command(c, "systemctl restart cube-web-cloudagent", ssh_password) + # print(f"✅", end="\n", flush=True) + # except Exception as e: + # print(f"❌", end="\n", flush=True) + # print(f"[Restarting cube-web-cloudagent] Exception: {e}") + # continue # print(f"Setting proxy configuration:", end="\n", flush=True) # result_proxy_host = set_config_field(result, "proxy-host", "10.81.35.126", True)