diff --git a/application/src/main/data/json/edge/install_instructions/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md similarity index 98% rename from application/src/main/data/json/edge/install_instructions/centos/instructions.md rename to application/src/main/data/json/edge/instructions/install/centos/instructions.md index 126c79b641..9ad882c387 100644 --- a/application/src/main/data/json/edge/install_instructions/centos/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md @@ -1,4 +1,4 @@ -Here is the list of commands, that can be used to quickly install ThingsBoard Edge on RHEL/CentOS 7/8 and connect to the cloud. +Here is the list of commands, that can be used to quickly install ThingsBoard Edge on RHEL/CentOS 7/8 and connect to the server. #### Prerequisites Before continue to installation execute the following commands in order to install necessary tools: diff --git a/application/src/main/data/json/edge/install_instructions/docker/instructions.md b/application/src/main/data/json/edge/instructions/install/docker/instructions.md similarity index 98% rename from application/src/main/data/json/edge/install_instructions/docker/instructions.md rename to application/src/main/data/json/edge/instructions/install/docker/instructions.md index e308a38076..cbfe5f8c4c 100644 --- a/application/src/main/data/json/edge/install_instructions/docker/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/docker/instructions.md @@ -1,4 +1,4 @@ -Here is the list of commands, that can be used to quickly install ThingsBoard Edge using docker compose and connect to the cloud. +Here is the list of commands, that can be used to quickly install ThingsBoard Edge using docker compose and connect to the server. #### Prerequisites diff --git a/application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md b/application/src/main/data/json/edge/instructions/install/docker/localhost_warning.md similarity index 100% rename from application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md rename to application/src/main/data/json/edge/instructions/install/docker/localhost_warning.md diff --git a/application/src/main/data/json/edge/install_instructions/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md similarity index 98% rename from application/src/main/data/json/edge/install_instructions/ubuntu/instructions.md rename to application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md index 425c08662b..2a6d9c0bc3 100644 --- a/application/src/main/data/json/edge/install_instructions/ubuntu/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md @@ -1,4 +1,4 @@ -Here is the list of commands, that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the cloud. +Here is the list of commands, that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server. #### Install Java 11 (OpenJDK) ThingsBoard service is running on Java 11. Follow these instructions to install OpenJDK 11: diff --git a/application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md b/application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md new file mode 100644 index 0000000000..d0b2a1895d --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md @@ -0,0 +1,30 @@ +#### Upgrading to ${TB_EDGE_VERSION_TITLE} +**NOTE**:These steps are applicable for ThingsBoard Edge ${CURRENT_TB_EDGE_VERSION} version. + +**ThingsBoard Edge package download** +```bash +wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.rpm +{:copy-code} +``` + +#### ThingsBoard Edge service upgrade + +Stop ThingsBoard Edge service if it is running: + +```bash +sudo service tb-edge stop +{:copy-code} +``` + +```bash +sudo rpm -Uvh tb-edge-${TB_EDGE_TAG}.rpm +{:copy-code} +``` + +${UPGRADE_DB} + +Start the service +```bash +sudo service tb-edge start +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md b/application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md new file mode 100644 index 0000000000..ad7f4b2c46 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md @@ -0,0 +1,15 @@ +#### Upgrading to ${TB_EDGE_VERSION} + +**NOTE**:These steps are applicable for ThingsBoard Edge ${CURRENT_TB_EDGE_VERSION} version. +Execute the following command to pull **${TB_EDGE_VERSION}** image: + +```bash +docker pull thingsboard/tb-edge:${TB_EDGE_VERSION} +{:copy-code} +``` + +${STOP_SERVICE} + +${UPGRADE_DB} + +Make sure your image is the set to tb-edge-${TB_EDGE_VERSION}. diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md b/application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md new file mode 100644 index 0000000000..cfdd9a66f2 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md @@ -0,0 +1,7 @@ +Execute the following commands to up this docker compose directly: + +```bash +docker compose up -d +docker compose logs -f mytbedge +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/stop_service.md b/application/src/main/data/json/edge/instructions/upgrade/docker/stop_service.md new file mode 100644 index 0000000000..8f4aedb117 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/stop_service.md @@ -0,0 +1,11 @@ +Set the terminal in the directory which contains the `docker-compose.yml` file and execute the following command to stop +and remove currently running TB Edge container (if it’s still running): + +```bash +make docker-compose.yml + +```bash +docker compose stop +docker compose rm mytbedge +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md new file mode 100644 index 0000000000..ae6181bf08 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md @@ -0,0 +1,53 @@ +Create docker compose file for ThingsBoard Edge upgrade process: + +```bash +nano docker-compose-upgrade.yml +{:copy-code} +``` + +Add the following lines to the yml file: + +```bash +version: '3.0' +services: + mytbedge: + restart: on-failure + image: "thingsboard/tb-edge:${TB_EDGE_VERSION}" + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tb-edge + volumes: + - ~/.mytb-edge-data:/data + - ~/.mytb-edge-logs:/var/log/tb-edge + entrypoint: upgrade-tb-edge.sh + postgres: + restart: always + image: "postgres:12" + ports: + - "5432" + environment: + POSTGRES_DB: tb-edge + POSTGRES_PASSWORD: postgres + volumes: + - ~/.mytb-edge-data/db:/var/lib/postgresql/data +{:copy-code} +``` + +Execute the following command to start upgrade process: + +```bash +docker compose -f docker-compose-upgrade.yml up +{:copy-code} +``` + +Once upgrade process successfully completed, exit from the docker-compose shell by this combination: + +```text +Ctrl + C +``` + +Execute the following command to stop TB Edge upgrade container: + +```bash +docker compose -f docker-compose-upgrade.yml stop +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md new file mode 100644 index 0000000000..c82daaee26 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md @@ -0,0 +1,26 @@ +Here is the list of commands, that can be used to quickly upgrade ThingsBoard Edge on Docker (Linux or MacOS). + +#### Prepare for upgrading ThingsBoard Edge +Set the terminal in the directory which contains the `docker-compose.yml` file and execute the following command +to stop and remove currently running TB Edge container: + +```bash +docker compose stop +docker compose rm mytbedge +{:copy-code} +``` + +If you still rely on Docker Compose as docker-compose (with a hyphen) here is the list of the above commands: +docker-compose stop +docker-compose rm mytbedge + +#### Backup Database +Make a copy of the database folder before upgrading: + +```bash +sudo cp -r ~/.mytb-edge-data/db ~/.mytb-edge-db-BACKUP +{:copy-code} +``` + +```bash +make docker-compose.yml diff --git a/application/src/main/data/json/edge/instructions/upgrade/start_service.md b/application/src/main/data/json/edge/instructions/upgrade/start_service.md new file mode 100644 index 0000000000..0ce6a0b222 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/start_service.md @@ -0,0 +1,6 @@ +Start the service + +```bash +sudo service tb-edge start +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/stop_service.md b/application/src/main/data/json/edge/instructions/upgrade/stop_service.md new file mode 100644 index 0000000000..615d0f8a40 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/stop_service.md @@ -0,0 +1,6 @@ +Stop ThingsBoard Edge service if it is running: + +```bash +sudo service tb-edge stop +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md new file mode 100644 index 0000000000..5824875f47 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md @@ -0,0 +1,19 @@ +#### Upgrading to ${TB_EDGE_VERSION} +**NOTE**:These steps are applicable for ThingsBoard Edge ${CURRENT_TB_EDGE_VERSION} version. + +**ThingsBoard Edge package download** +```bash +wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.deb +{:copy-code} +``` + +#### ThingsBoard Edge service upgrade + +${STOP_SERVICE} + +```bash +sudo dpkg -i tb-edge-${TB_EDGE_TAG}.deb +{:copy-code} +``` + +${UPGRADE_DB} diff --git a/application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md new file mode 100644 index 0000000000..d3a28fb20b --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md @@ -0,0 +1,8 @@ +**NOTE**: Package installer may ask you to merge your tb-edge configuration. It is preferred to use **merge option** to make sure that all your previous parameters will not be overwritten. + +Execute regular upgrade script: + +```bash +sudo /usr/share/tb-edge/bin/install/upgrade.sh --fromVersion=${CURRENT_TB_EDGE_VERSION} +{:copy-code} +``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md b/application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md new file mode 100644 index 0000000000..105a94b778 --- /dev/null +++ b/application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md @@ -0,0 +1,37 @@ +Here is the list of commands, that can be used to quickly upgrade ThingsBoard Edge on RHEL/CentOS 7/8. + +#### Prepare for upgrading ThingsBoard Edge + +Stop ThingsBoard Edge service: + +```bash +sudo systemctl stop tb-edge +{:copy-code} +``` + +#### Backup Database +Make a backup of the database before upgrading. +**Make sure you have enough space to place a backup of the database.** + +Check database size: + +```bash +sudo -u postgres psql -c "SELECT pg_size_pretty( pg_database_size('tb_edge') );" +{:copy-code} +``` + +Check free space: + +```bash +df -h / +{:copy-code} +``` + +If there is enough free space - make a backup: + +```bash +sudo -Hiu postgres pg_dump tb_edge > tb_edge.sql.bak +{:copy-code} +``` + +Check backup file created successfully. diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index fa16fbff81..c621dc777f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInfo; -import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; +import org.thingsboard.server.common.data.edge.EdgeInstructions; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -60,6 +60,7 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.EdgeBulkImportService; import org.thingsboard.server.service.edge.instructions.EdgeInstallService; +import org.thingsboard.server.service.edge.instructions.EdgeUpgradeService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.edge.TbEdgeService; import org.thingsboard.server.service.security.model.SecurityUser; @@ -102,6 +103,7 @@ public class EdgeController extends BaseController { private final TbEdgeService tbEdgeService; private final Optional edgeRpcServiceOpt; private final Optional edgeInstallServiceOpt; + private final Optional edgeUpgradeServiceOpt; public static final String EDGE_ID = "edgeId"; public static final String EDGE_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the edge is owned by the same tenant. " + @@ -553,16 +555,16 @@ public class EdgeController extends BaseController { return edgeBulkImportService.processBulkImport(request, user); } - @ApiOperation(value = "Get Edge Docker Install Instructions (getEdgeDockerInstallInstructions)", + @ApiOperation(value = "Get Edge Install Instructions (getEdgeInstallInstructions)", notes = "Get a docker install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/edge/instructions/{edgeId}/{method}", method = RequestMethod.GET) + @RequestMapping(value = "/edge/instructions/install/{edgeId}/{method}", method = RequestMethod.GET) @ResponseBody - public EdgeInstallInstructions getEdgeDockerInstallInstructions( + public EdgeInstructions getEdgeInstallInstructions( @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId, - @ApiParam(value = "Installation method ('docker', 'ubuntu' or 'centos')") + @ApiParam(value = "Installation method ('docker', 'ubuntu' or 'centos')", allowableValues = "docker,ubuntu,centos") @PathVariable("method") String installationMethod, HttpServletRequest request) throws ThingsboardException { if (isEdgesEnabled() && edgeInstallServiceOpt.isPresent()) { @@ -574,4 +576,27 @@ public class EdgeController extends BaseController { throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); } } + + @ApiOperation(value = "Get Edge Upgrade Instructions (getEdgeUpgradeInstructions)", + notes = "Get a docker install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH, + produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/instructions/upgrade/{edgeId}/{edgeVersion}/{method}", method = RequestMethod.GET) + @ResponseBody + public EdgeInstructions getEdgeUpgradeInstructions( + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @PathVariable("edgeId") String strEdgeId, + @ApiParam(value = "Edge version", required = true) + @PathVariable("edgeVersion") String edgeVersion, + @ApiParam(value = "Installation method ('docker', 'ubuntu' or 'centos')", allowableValues = "docker,ubuntu,centos") + @PathVariable("method") String method) throws Exception { + if (isEdgesEnabled() && edgeUpgradeServiceOpt.isPresent()) { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + edgeId = checkNotNull(edgeId); + Edge edge = checkEdgeId(edgeId, Operation.READ); + return checkNotNull(edgeUpgradeServiceOpt.get().getUpgradeInstructions(getTenantId(), edge, edgeVersion, method)); + } else { + throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java index dd17f83317..24340f7a97 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java @@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; +import org.thingsboard.server.common.data.edge.EdgeInstructions; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.install.InstallScripts; @@ -40,8 +40,8 @@ import java.nio.file.Paths; public class DefaultEdgeInstallService implements EdgeInstallService { private static final String EDGE_DIR = "edge"; - - private static final String EDGE_INSTALL_INSTRUCTIONS_DIR = "install_instructions"; + private static final String INSTRUCTIONS_DIR = "instructions"; + private static final String INSTALL_DIR = "install"; private final InstallScripts installScripts; @@ -55,7 +55,7 @@ public class DefaultEdgeInstallService implements EdgeInstallService { private String appVersion; @Override - public EdgeInstallInstructions getInstallInstructions(TenantId tenantId, Edge edge, String installationMethod, HttpServletRequest request) { + public EdgeInstructions getInstallInstructions(TenantId tenantId, Edge edge, String installationMethod, HttpServletRequest request) { switch (installationMethod.toLowerCase()) { case "docker": return getDockerInstallInstructions(edge, request); @@ -68,7 +68,7 @@ public class DefaultEdgeInstallService implements EdgeInstallService { } } - private EdgeInstallInstructions getDockerInstallInstructions(Edge edge, HttpServletRequest request) { + private EdgeInstructions getDockerInstallInstructions(Edge edge, HttpServletRequest request) { String dockerInstallInstructions = readFile(resolveFile("docker", "instructions.md")); String baseUrl = request.getServerName(); if (baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { @@ -83,26 +83,26 @@ public class DefaultEdgeInstallService implements EdgeInstallService { edgeVersion = edgeVersion.replace("-SNAPSHOT", ""); dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion); dockerInstallInstructions = replacePlaceholders(dockerInstallInstructions, edge); - return new EdgeInstallInstructions(dockerInstallInstructions); + return new EdgeInstructions(dockerInstallInstructions); } - private EdgeInstallInstructions getUbuntuInstallInstructions(Edge edge, HttpServletRequest request) { + private EdgeInstructions getUbuntuInstallInstructions(Edge edge, HttpServletRequest request) { String ubuntuInstallInstructions = readFile(resolveFile("ubuntu", "instructions.md")); ubuntuInstallInstructions = replacePlaceholders(ubuntuInstallInstructions, edge); ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${BASE_URL}", request.getServerName()); String edgeVersion = appVersion.replace("-SNAPSHOT", ""); ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion); - return new EdgeInstallInstructions(ubuntuInstallInstructions); + return new EdgeInstructions(ubuntuInstallInstructions); } - private EdgeInstallInstructions getCentosInstallInstructions(Edge edge, HttpServletRequest request) { + private EdgeInstructions getCentosInstallInstructions(Edge edge, HttpServletRequest request) { String centosInstallInstructions = readFile(resolveFile("centos", "instructions.md")); centosInstallInstructions = replacePlaceholders(centosInstallInstructions, edge); centosInstallInstructions = centosInstallInstructions.replace("${BASE_URL}", request.getServerName()); String edgeVersion = appVersion.replace("-SNAPSHOT", ""); centosInstallInstructions = centosInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion); - return new EdgeInstallInstructions(centosInstallInstructions); + return new EdgeInstructions(centosInstallInstructions); } private String replacePlaceholders(String instructions, Edge edge) { @@ -127,6 +127,6 @@ public class DefaultEdgeInstallService implements EdgeInstallService { } private Path getEdgeInstallInstructionsDir() { - return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, EDGE_INSTALL_INSTRUCTIONS_DIR); + return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, INSTALL_DIR); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeService.java new file mode 100644 index 0000000000..a1d737757d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeService.java @@ -0,0 +1,190 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.instructions; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeInstructions; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.install.InstallScripts; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; + +@Service +@Slf4j +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") +@TbCoreComponent +public class DefaultEdgeUpgradeService implements EdgeUpgradeService { + + private static final HashMap upgradeVersionHashMap; + + static { + upgradeVersionHashMap = new HashMap<>(); + upgradeVersionHashMap.put("3.6.0", new UpgradeInfo(true, "3.6.1")); + upgradeVersionHashMap.put("3.6.1", new UpgradeInfo(true, "3.6.2")); + upgradeVersionHashMap.put("3.6.2", new UpgradeInfo(true, null)); + } + + private static final String EDGE_DIR = "edge"; + private static final String INSTRUCTIONS_DIR = "instructions"; + private static final String UPGRADE_DIR = "upgrade"; + + private final InstallScripts installScripts; + private final AttributesService attributesService; + + @Value("${app.version:unknown}") + private String appVersion; + + @Override + public EdgeInstructions getUpgradeInstructions(TenantId tenantId, Edge edge, String edgeVersion, String upgradeMethod) { + String tbVersion = appVersion.replace("-SNAPSHOT", ""); + String currentEdgeVersion = convertEdgeVersionToDocsFormat(edgeVersion); + switch (upgradeMethod.toLowerCase()) { + case "docker": + return getDockerUpgradeInstructions(tenantId, edge.getId(), tbVersion, currentEdgeVersion); + case "ubuntu": + case "centos": + return getLinuxUpgradeInstructions(tenantId, edge.getId(), tbVersion, currentEdgeVersion, upgradeMethod.toLowerCase()); + default: + throw new IllegalArgumentException("Unsupported upgrade method for Edge: " + upgradeMethod); + } + } + + private EdgeInstructions getDockerUpgradeInstructions(TenantId tenantId, EdgeId edgeId, String tbVersion, String currentEdgeVersion) { + UpgradeInfo upgradeInfo = upgradeVersionHashMap.get(currentEdgeVersion); + if (upgradeInfo.getNextVersion() == null || tbVersion.equals(currentEdgeVersion)) { + return null; + } + boolean stoppedService = false; + StringBuilder result = new StringBuilder(readFile(resolveFile("docker", "upgrade_preparing.md"))); + while (upgradeInfo.getNextVersion() != null || !tbVersion.equals(currentEdgeVersion)) { + String edgeVersion = upgradeInfo.getNextVersion(); + String ubuntuUpgradeInstructions = readFile(resolveFile("docker", "instructions.md")); + if (upgradeInfo.isUpgradeDb()) { + String upgradeDb = readFile(resolveFile("docker", "upgrade_db.md")); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${UPGRADE_DB}", upgradeDb); + } + if (!stoppedService) { + stoppedService = true; + String stopService = readFile(resolveFile("docker", "stop_service.md")); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${STOP_SERVICE}", stopService); + } else { + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${STOP_SERVICE}", ""); + } + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${TB_EDGE_VERSION}", edgeVersion + "EDGE"); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${CURRENT_TB_EDGE_VERSION}", currentEdgeVersion + "EDGE"); + currentEdgeVersion = edgeVersion; + upgradeInfo = upgradeVersionHashMap.get(upgradeInfo.getNextVersion()); + result.append(ubuntuUpgradeInstructions); + } + String startService = readFile(resolveFile("docker", "start_service.md")); + result.append(startService); + AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry("edgeVersion", currentEdgeVersion), System.currentTimeMillis()); + attributesService.save(tenantId, edgeId, DataConstants.SERVER_SCOPE, attributeKvEntry); + return new EdgeInstructions(result.toString()); + } + + private EdgeInstructions getLinuxUpgradeInstructions(TenantId tenantId, EdgeId edgeId, String tbVersion, String currentEdgeVersion, String os) { + UpgradeInfo upgradeInfo = upgradeVersionHashMap.get(currentEdgeVersion); + if (upgradeInfo.getNextVersion() == null || tbVersion.equals(currentEdgeVersion)) { + return null; + } + boolean stoppedService = false; + StringBuilder result = new StringBuilder(readFile(resolveFile("upgrade_preparing.md"))); + while (upgradeInfo.getNextVersion() != null || !tbVersion.equals(currentEdgeVersion)) { + String edgeVersion = upgradeInfo.getNextVersion(); + String ubuntuUpgradeInstructions = readFile(resolveFile(os, "instructions.md")); + if (upgradeInfo.isUpgradeDb()) { + String upgradeDb = readFile(resolveFile("upgrade_db.md")); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${UPGRADE_DB}", upgradeDb); + } + if (!stoppedService) { + stoppedService = true; + String stopService = readFile(resolveFile("stop_service.md")); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${STOP_SERVICE}", stopService); + } else { + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${STOP_SERVICE}", ""); + } + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${TB_EDGE_TAG}", getTagVersion(edgeVersion)); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${CURRENT_TB_EDGE_TAG}", getTagVersion(currentEdgeVersion)); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${TB_EDGE_VERSION}", edgeVersion); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${CURRENT_TB_EDGE_VERSION}", currentEdgeVersion); + ubuntuUpgradeInstructions = ubuntuUpgradeInstructions.replace("${TB_EDGE_VERSION_TITLE}", edgeVersion + "EDGE"); + currentEdgeVersion = edgeVersion; + upgradeInfo = upgradeVersionHashMap.get(upgradeInfo.getNextVersion()); + result.append(ubuntuUpgradeInstructions); + } + String startService = readFile(resolveFile("start_service.md")); + result.append(startService); + AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry("edgeVersion", convertDocsFormatToEdgeVersion(currentEdgeVersion)), System.currentTimeMillis()); + attributesService.save(tenantId, edgeId, DataConstants.SERVER_SCOPE, attributeKvEntry); + return new EdgeInstructions(result.toString()); + } + + private String getTagVersion(String version) { + return version.endsWith(".0") ? version.substring(0, version.length() - 2) : version; + } + + private String convertEdgeVersionToDocsFormat(String edgeVersion) { + return edgeVersion.replace("_", ".").substring(2); + } + + private String convertDocsFormatToEdgeVersion(String edgeVersion) { + return "V_" + edgeVersion.replace(".", "_"); + } + + private String readFile(Path file) { + try { + return Files.readString(file); + } catch (IOException e) { + log.warn("Failed to read file: {}", file, e); + throw new RuntimeException(e); + } + } + + private Path resolveFile(String subDir, String... subDirs) { + return getEdgeInstallInstructionsDir().resolve(Paths.get(subDir, subDirs)); + } + + private Path getEdgeInstallInstructionsDir() { + return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, UPGRADE_DIR); + } + + @AllArgsConstructor + @Data + public static class UpgradeInfo { + private boolean upgradeDb; + private String nextVersion; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java index 20cac25e33..cba8703920 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java @@ -16,12 +16,12 @@ package org.thingsboard.server.service.edge.instructions; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; +import org.thingsboard.server.common.data.edge.EdgeInstructions; import org.thingsboard.server.common.data.id.TenantId; import javax.servlet.http.HttpServletRequest; public interface EdgeInstallService { - EdgeInstallInstructions getInstallInstructions(TenantId tenantId, Edge edge, String installationMethod, HttpServletRequest request); + EdgeInstructions getInstallInstructions(TenantId tenantId, Edge edge, String installationMethod, HttpServletRequest request); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeService.java new file mode 100644 index 0000000000..0f4394ace9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.instructions; + +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeInstructions; +import org.thingsboard.server.common.data.id.TenantId; + +public interface EdgeUpgradeService { + + EdgeInstructions getUpgradeInstructions(TenantId tenantId, Edge edge, String edgeVersion, String upgradeMethod) throws Exception; +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 7318b1eb24..32d648c045 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; @@ -774,7 +775,7 @@ public final class EdgeGrpcSession implements Closeable { try { if (edge.getSecret().equals(request.getEdgeSecret())) { sessionOpenListener.accept(edge.getId(), this); - this.edgeVersion = request.getEdgeVersion(); + this.edgeVersion = processGetAndSaveEdgeVersion(request.getEdgeVersion()); return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.ACCEPTED) .setErrorMsg("") @@ -800,6 +801,12 @@ public final class EdgeGrpcSession implements Closeable { .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } + private EdgeVersion processGetAndSaveEdgeVersion(EdgeVersion edgeVersion) { + AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry("edgeVersion", edgeVersion.name()), System.currentTimeMillis()); + ctx.getAttributesService().save(this.tenantId, this.edge.getId(), DataConstants.SERVER_SCOPE, attributeKvEntry); + return edgeVersion; + } + @Override public void close() { log.debug("[{}][{}] Closing session", this.tenantId, sessionId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstructions.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java rename to common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstructions.java index 8343058250..2c3a66c002 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstructions.java @@ -25,8 +25,8 @@ import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor -public class EdgeInstallInstructions { +public class EdgeInstructions { - @ApiModelProperty(position = 1, value = "Markdown with install instructions") - private String installInstructions; + @ApiModelProperty(position = 1, value = "Markdown with install/upgrade instructions") + private String instructions; } diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index fb878a33b6..d90a58fde8 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -84,7 +84,7 @@ import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeInfo; -import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; +import org.thingsboard.server.common.data.edge.EdgeInstructions; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.AlarmCommentId; @@ -3241,9 +3241,9 @@ public class RestClient implements Closeable { }).getBody(); } - public Optional getEdgeDockerInstallInstructions(EdgeId edgeId) { - ResponseEntity edgeInstallInstructionsResult = - restTemplate.getForEntity(baseURL + "/api/edge/instructions/{edgeId}", EdgeInstallInstructions.class, edgeId.getId()); + public Optional getEdgeDockerInstallInstructions(EdgeId edgeId, String method) { + ResponseEntity edgeInstallInstructionsResult = + restTemplate.getForEntity(baseURL + "/api/edge/instructions/install/{edgeId}/{method}", EdgeInstructions.class, edgeId.getId(), method); return Optional.ofNullable(edgeInstallInstructionsResult.getBody()); }