{ "metadata": { "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python", "version": "3.6.6", "mimetype": "text/x-python", "codemirror_mode": { "name": "ipython", "version": 3 }, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py" } }, "nbformat_minor": 2, "nbformat": 4, "cells": [ { "cell_type": "markdown", "source": [ "![Microsoft](https://raw.githubusercontent.com/microsoft/azuredatastudio/main/extensions/resource-deployment/images/microsoft-small-logo.png)\n", "## Deploy Azure SQL Edge to multiple devices via IoT hub\n", "This notebook will walk you through process of deploying Azure SQL Edge to multiple devices of an IoT hub.\n", "\n", "### Dependencies\n", "- Azure CLI. For more information, see [Azure CLI Installation](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest).\n", "\n", "Please press the \"Run all\" button to run the notebook" ], "metadata": { "azdata_cell_guid": "15b8cfc7-dd7f-4db8-9a3c-2151932fe7b5" } }, { "cell_type": "markdown", "source": [ "### Check dependencies" ], "metadata": { "azdata_cell_guid": "f78f4ff3-d4c9-4c3e-853f-4add05061eb0" } }, { "cell_type": "code", "source": [ "import sys,os,json,html,getpass,time,ntpath,uuid\n", "\n", "def run_command(command:str, displayCommand:str = \"\", returnObject:bool = False):\n", " print(\"Executing: \" + (displayCommand if displayCommand != \"\" else command))\n", " if returnObject:\n", " output = os.popen(command).read()\n", " print(f'Command successfully executed')\n", " return json.loads(''.join(output))\n", " else:\n", " !{command}\n", " if _exit_code != 0:\n", " sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n')\n", " else:\n", " print(f'Command successfully executed')\n", "\n", "run_command(command='az --version')" ], "metadata": { "azdata_cell_guid": "70b9744f-eb59-44e8-9b35-db590ac4651d", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Install Azure IoT extension for Azure CLI" ], "metadata": { "azdata_cell_guid": "a7f15c68-1725-4caa-b4f7-ddc2b4934883" } }, { "cell_type": "code", "source": [ "extensions = run_command('az extension list', returnObject=True)\r\n", "extensions = [ext for ext in extensions if ext['name'] == 'azure-cli-iot-ext']\r\n", "if len(extensions) > 0:\r\n", " run_command('az extension remove --name azure-cli-iot-ext')\r\n", "run_command('az extension add --name azure-iot')" ], "metadata": { "azdata_cell_guid": "55bb2f96-6f7f-4aa0-9daf-d0f7f9d9243c", "tags": [] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Required information" ], "metadata": { "azdata_cell_guid": "b5dc5586-06e8-44d9-8bc3-2861d510efe5" } }, { "cell_type": "code", "source": [ "azure_subscription_id = os.environ[\"AZDATA_NB_VAR_ASDE_SUBSCRIPTIONID\"]\n", "azure_resource_group = os.environ[\"AZDATA_NB_VAR_ASDE_RESOURCEGROUP\"]\n", "sa_password = os.environ[\"AZDATA_NB_VAR_SA_PASSWORD\"]\n", "package_path = os.environ[\"AZDATA_NB_VAR_ASDE_PACKAGE_PATH\"]\n", "sql_port = os.environ[\"AZDATA_NB_VAR_ASDE_SQL_PORT\"]\n", "iot_hub_name = os.environ[\"AZDATA_NB_VAR_ASDE_HUBNAME\"]\n", "target_condition = os.environ[\"AZDATA_NB_VAR_ASDE_TARGET_CONDITION\"]\n", "print(f'Subscription: {azure_subscription_id}')\n", "print(f'Resource group: {azure_resource_group}')\n", "print(f'IoT hub name: {iot_hub_name}')\n", "print(f'Target condition: {target_condition}')\n", "print(f'Azure SQL Edge instance port: {sql_port}')\n", "print(f'Azure SQL Edge instance sa password: ******')\n", "print(f'Package path: {package_path}')" ], "metadata": { "azdata_cell_guid": "dde9388b-f623-4d62-bb74-36a05f5d2ea3", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Default settings" ], "metadata": { "azdata_cell_guid": "2a5755eb-85a7-4237-8d87-04cdab13cf40" } }, { "cell_type": "code", "source": [ "suffix = time.strftime(\"%y%m%d%H%M%S\", time.localtime())\n", "azure_storage_account = f'sa{suffix}'\n", "deployment_id = f'asde{suffix}'\n", "storage_account_container = 'sqldatabasepackage'\n", "sql_lcid = '1033'\n", "sql_collation = 'SQL_Latin1_General_CP1_CI_AS'\n", "deployment_priority = 10" ], "metadata": { "azdata_cell_guid": "19ebeaf4-94c9-4d2b-bd9f-e3c6bf7f2dda", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Login to Azure" ], "metadata": { "azdata_cell_guid": "84f57c09-5772-4f7a-a270-4039b8d5b081" } }, { "cell_type": "code", "source": [ "run_command('az login')" ], "metadata": { "azdata_cell_guid": "f9e8ddee-aefa-4951-b767-b318d941d2cd", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Set active Azure subscription" ], "metadata": { "azdata_cell_guid": "59249fa6-f76c-4e5d-bee7-a9ebef6f873e" } }, { "cell_type": "code", "source": [ "if azure_subscription_id != \"\":\n", " run_command(f'az account set --subscription {azure_subscription_id}')\n", "else:\n", " print('Using the default Azure subscription', {azure_subscription_id})\n", "run_command(f'az account show')" ], "metadata": { "azdata_cell_guid": "6e085676-2cc5-4af8-819c-fa210244e6c3", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Make sure the IoT hub exists" ], "metadata": { "azdata_cell_guid": "b207680d-a0df-49b5-9e9e-5fd8c3d492d3" } }, { "cell_type": "code", "source": [ "hub_list = run_command(f'az iot hub list --resource-group {azure_resource_group}', returnObject=True)\r\n", "hub_list = [hub for hub in hub_list if hub['name'] == iot_hub_name]\r\n", "if len(hub_list) == 0:\r\n", " sys.exit(f'IoT hub \\\"{iot_hub_name}\\\" does not exist.')" ], "metadata": { "azdata_cell_guid": "38798c56-b2a0-4af8-b39e-5e26f3c79aeb", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Create storage account and storage account container, then upload the package" ], "metadata": { "azdata_cell_guid": "90ec2b26-0c4a-4aa4-b397-f16b09b454ea" } }, { "cell_type": "code", "source": [ "storage_account_created = False\n", "if package_path == \"\":\n", " print(f'Package file not provided')\n", " blob_sas = ''\n", "else:\n", " package_name = ntpath.basename(package_path)\n", " storage_accounts = run_command(f'az storage account list --resource-group {azure_resource_group} --subscription {azure_subscription_id}', returnObject=True)\n", " storage_accounts = [storage_account for storage_account in storage_accounts if storage_account['name'] == azure_storage_account]\n", " if len(storage_accounts) == 0:\n", " storage_account_created = True\n", " run_command(f'az storage account create -n {azure_storage_account} -g {azure_resource_group} --sku Standard_LRS --kind Storage')\n", " else:\n", " print(f'storage account \\\"{azure_storage_account}\\\" already exists.')\n", "\n", " storage_account_key = run_command(f'az storage account keys list --account-name {azure_storage_account} --resource-group {azure_resource_group}', returnObject=True)[0]['value']\n", " container_exists = run_command(f'az storage container exists --name {storage_account_container} --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key --output json', returnObject=True)['exists']\n", " if container_exists:\n", " print(f'storage account container \\\"{storage_account_container}\\\" already exists.')\n", " else:\n", " run_command(f'az storage container create --name {storage_account_container} --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key')\n", "\n", " blob_exists = run_command(f'az storage blob exists --container-name {storage_account_container} --name \\\"{package_name}\\\" --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key', returnObject=True)['exists']\n", " if blob_exists:\n", " print(f'blob \\\"{package_name}\\\" already exists.')\n", " else:\n", " run_command(f'az storage blob upload --account-name {azure_storage_account} --container-name {storage_account_container} --name {package_name} --file \\\"{package_path}\\\" --account-key {storage_account_key} --auth-mode key')\n", " now = time.localtime()\n", " expiry = f'{(now.tm_year + 1)}-{now.tm_mon}-{now.tm_mday}'\n", " blob_sas = run_command(f'az storage blob generate-sas --container-name {storage_account_container} --name \\\"{package_name}\\\" --account-name {azure_storage_account} --account-key {storage_account_key} --auth-mode key --full-uri --https-only --permissions r --expiry {expiry}', returnObject=True)" ], "metadata": { "azdata_cell_guid": "7ab2b3ec-0832-40b3-98c0-4aa87320e7ce", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "markdown", "source": [ "### Deploy Azure SQL Edge to the device" ], "metadata": { "azdata_cell_guid": "ec46957f-0795-4c75-804d-f8a7ecb26382" } }, { "cell_type": "code", "source": [ "manifest = '{\\\"modulesContent\\\":{\\\"$edgeAgent\\\":{\\\"properties.desired\\\":{\\\"modules\\\":{\\\"AzureSQLEdge\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azure-sql-edge\\\",\\\"createOptions\\\":\\\"{\\\\\\\"HostConfig\\\\\\\":{\\\\\\\"CapAdd\\\\\\\":[\\\\\\\"SYS_PTRACE\\\\\\\"],\\\\\\\"Binds\\\\\\\":[\\\\\\\"sqlvolume:/sqlvolume\\\\\\\"],\\\\\\\"PortBindings\\\\\\\":{\\\\\\\"1433/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"\\\\\\\"}]},\\\\\\\"Mounts\\\\\\\":[{\\\\\\\"Type\\\\\\\":\\\\\\\"volume\\\\\\\",\\\\\\\"Source\\\\\\\":\\\\\\\"sqlvolume\\\\\\\",\\\\\\\"Target\\\\\\\":\\\\\\\"/var/opt/mssql\\\\\\\"}]},\\\\\\\"User\\\\\\\":\\\\\\\"0:0\\\\\\\",\\\\\\\"Env\\\\\\\":[\\\\\\\"MSSQL_AGENT_ENABLED=TRUE\\\\\\\",\\\\\\\"ClientTransportType=AMQP_TCP_Only\\\\\\\",\\\\\\\"PlanId=asde-developer-on-iot-edge\\\\\\\"]}\\\"},\\\"type\\\":\\\"docker\\\",\\\"version\\\":\\\"1.0\\\",\\\"env\\\":{\\\"ACCEPT_EULA\\\":{\\\"value\\\":\\\"Y\\\"},\\\"SA_PASSWORD\\\":{\\\"value\\\":\\\"\\\"},\\\"MSSQL_LCID\\\":{\\\"value\\\":\\\"\\\"},\\\"MSSQL_COLLATION\\\":{\\\"value\\\":\\\"\\\"}},\\\"status\\\":\\\"running\\\",\\\"restartPolicy\\\":\\\"always\\\"}},\\\"runtime\\\":{\\\"settings\\\":{\\\"minDockerVersion\\\":\\\"v1.25\\\"},\\\"type\\\":\\\"docker\\\"},\\\"schemaVersion\\\":\\\"1.0\\\",\\\"systemModules\\\":{\\\"edgeAgent\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azureiotedge-agent:1.0\\\",\\\"createOptions\\\":\\\"\\\"},\\\"type\\\":\\\"docker\\\"},\\\"edgeHub\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azureiotedge-hub:1.0\\\",\\\"createOptions\\\":\\\"{\\\\\\\"HostConfig\\\\\\\":{\\\\\\\"PortBindings\\\\\\\":{\\\\\\\"443/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"443\\\\\\\"}],\\\\\\\"5671/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"5671\\\\\\\"}],\\\\\\\"8883/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"8883\\\\\\\"}]}}}\\\"},\\\"type\\\":\\\"docker\\\",\\\"status\\\":\\\"running\\\",\\\"restartPolicy\\\":\\\"always\\\"}}}},\\\"$edgeHub\\\":{\\\"properties.desired\\\":{\\\"routes\\\":{},\\\"schemaVersion\\\":\\\"1.0\\\",\\\"storeAndForwardConfiguration\\\":{\\\"timeToLiveSecs\\\":7200}}},\\\"AzureSQLEdge\\\":{\\\"properties.desired\\\":{\\\"ASAJobInfo\\\":\\\"\\\"}}}}'\n", "package_info = '' if blob_sas == ''else ',\\\"MSSQL_PACKAGE\\\":{\\\"value\\\":\\\"'+blob_sas+'\\\"}'\n", "manifest = manifest.replace('', package_info).replace('',sa_password).replace('',sql_lcid).replace('',sql_port).replace('',sql_collation)\n", "file_name = f'{uuid.uuid4().hex}.json'\n", "manifest_file = open(file_name, 'w')\n", "manifest_file.write(manifest)\n", "manifest_file.close()\n", "run_command(f'az iot edge deployment create --target-condition \\\"{target_condition}\\\" --hub-name \\\"{iot_hub_name}\\\" --content \\\"{file_name}\\\" --deployment-id \\\"{deployment_id}\\\" --priority {deployment_priority}')\n", "os.remove(file_name)" ], "metadata": { "azdata_cell_guid": "81a86ff6-5a83-48be-8be7-654d152eea89", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null }, { "cell_type": "code", "source": [ "from IPython.display import *\r\n", "if storage_account_created:\r\n", " delete_storage_account_command = \"run_command(f'az storage account delete -n {azure_storage_account} -g {azure_resource_group} --yes')\"\r\n", " display(HTML('NOTE: A storage account was created to host the package file, you can delete it after the database is created and populated successfully. To delete the storage account, copy the following code to a new code cell and run the cell.'))\r\n", " display(HTML(''+delete_storage_account_command+''))" ], "metadata": { "azdata_cell_guid": "13d698eb-4c1c-42e4-be28-efb9bf8b8c37", "tags": [ "hide_input" ] }, "outputs": [], "execution_count": null } ] }