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 7556ab589b..b780789b26 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -594,4 +594,22 @@ public class EdgeController extends BaseController { throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); } } + + @ApiOperation(value = "Is edge upgrade enabled (isEdgeUpgradeAvailable)", + notes = "Returns 'true' if upgrade available for connected edge, 'false' - otherwise.") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/{edgeId}/upgrade/available", method = RequestMethod.GET) + @ResponseBody + public boolean isEdgeUpgradeAvailable( + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @PathVariable("edgeId") String strEdgeId) throws Exception { + if (isEdgesEnabled() && edgeUpgradeServiceOpt.isPresent()) { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + edgeId = checkNotNull(edgeId); + Edge edge = checkEdgeId(edgeId, Operation.READ); + return edgeUpgradeServiceOpt.get().isUpgradeAvailable(edge.getTenantId(), edge.getId()); + } else { + throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java index 940a813fc6..d4fa1f31a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java @@ -21,8 +21,13 @@ 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.EdgeUpgradeInfo; 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.dao.attributes.AttributesService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.install.InstallScripts; @@ -32,6 +37,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.Optional; @Service @Slf4j @@ -47,6 +53,7 @@ public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstruc private static final String UPGRADE_DIR = "upgrade"; private final InstallScripts installScripts; + private final AttributesService attributesService; @Value("${app.version:unknown}") @Setter @@ -74,13 +81,41 @@ public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstruc } } + @Override + public boolean isUpgradeAvailable(TenantId tenantId, EdgeId edgeId) throws Exception { + Optional attributeKvEntryOpt = attributesService.find(tenantId, edgeId, DataConstants.SERVER_SCOPE, DataConstants.EDGE_VERSION_ATTR_KEY).get(); + if (attributeKvEntryOpt.isPresent()) { + String edgeVersionFormatted = convertEdgeVersionToDocsFormat(attributeKvEntryOpt.get().getValueAsString()); + return isVersionGreaterOrEqualsThan(edgeVersionFormatted, "3.6.0") && !isVersionGreaterOrEqualsThan(edgeVersionFormatted, appVersion); + } + return false; + } + + private boolean isVersionGreaterOrEqualsThan(String version1, String version2) { + String[] v1 = version1.split("\\."); + String[] v2 = version2.split("\\."); + + int length = Math.max(v1.length, v2.length); + for (int i = 0; i < length; i++) { + int num1 = i < v1.length ? Integer.parseInt(v1[i]) : 0; + int num2 = i < v2.length ? Integer.parseInt(v2[i]) : 0; + + if (num1 < num2) { + return false; + } else if (num1 > num2) { + return true; + } + } + return true; + } + private EdgeInstructions getDockerUpgradeInstructions(String tbVersion, String currentEdgeVersion) { EdgeUpgradeInfo edgeUpgradeInfo = upgradeVersionHashMap.get(currentEdgeVersion); if (edgeUpgradeInfo == null || edgeUpgradeInfo.getNextEdgeVersion() == null || tbVersion.equals(currentEdgeVersion)) { return new EdgeInstructions("Edge upgrade instruction for " + currentEdgeVersion + "EDGE is not available."); } StringBuilder result = new StringBuilder(readFile(resolveFile("docker", "upgrade_preparing.md"))); - while (edgeUpgradeInfo.getNextEdgeVersion() != null || !tbVersion.equals(currentEdgeVersion)) { + while (edgeUpgradeInfo.getNextEdgeVersion() != null && !tbVersion.equals(currentEdgeVersion)) { String edgeVersion = edgeUpgradeInfo.getNextEdgeVersion(); String dockerUpgradeInstructions = readFile(resolveFile("docker", "instructions.md")); if (edgeUpgradeInfo.isRequiresUpdateDb()) { @@ -109,7 +144,7 @@ public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstruc String upgrade_preparing = readFile(resolveFile("upgrade_preparing.md")); upgrade_preparing = upgrade_preparing.replace("${OS}", os.equals("centos") ? "RHEL/CentOS 7/8" : "Ubuntu"); StringBuilder result = new StringBuilder(upgrade_preparing); - while (edgeUpgradeInfo.getNextEdgeVersion() != null || !tbVersion.equals(currentEdgeVersion)) { + while (edgeUpgradeInfo.getNextEdgeVersion() != null && !tbVersion.equals(currentEdgeVersion)) { String edgeVersion = edgeUpgradeInfo.getNextEdgeVersion(); String linuxUpgradeInstructions = readFile(resolveFile(os, "instructions.md")); if (edgeUpgradeInfo.isRequiresUpdateDb()) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java index be40bc6c89..30f8082bd3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java @@ -17,6 +17,8 @@ package org.thingsboard.server.service.edge.instructions; import org.thingsboard.server.common.data.EdgeUpgradeInfo; 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 java.util.Map; @@ -27,4 +29,6 @@ public interface EdgeUpgradeInstructionsService { void updateInstructionMap(Map upgradeVersions); void setAppVersion(String version); + + boolean isUpgradeAvailable(TenantId tenantId, EdgeId edgeId) 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 c9fb796831..32ab28b09a 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 @@ -822,7 +822,7 @@ public final class EdgeGrpcSession implements Closeable { } private void processSaveEdgeVersionAsAttribute(String edgeVersion) { - AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry("edgeVersion", edgeVersion), System.currentTimeMillis()); + AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry(DataConstants.EDGE_VERSION_ATTR_KEY, edgeVersion), System.currentTimeMillis()); ctx.getAttributesService().save(this.tenantId, this.edge.getId(), DataConstants.SERVER_SCOPE, attributeKvEntry); } diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java index 48a4187efc..ebc253ca9e 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java @@ -41,7 +41,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -81,8 +80,6 @@ public class DefaultUpdateService implements UpdateService { private final RestTemplate restClient = new RestTemplate(); private UpdateMessage updateMessage; - private EdgeUpgradeMessage edgeUpgradeMessage; - private String edgeInstallVersion; private String platform; private String version; @@ -94,7 +91,6 @@ public class DefaultUpdateService implements UpdateService { updateMessage = new UpdateMessage(false, version, "", "", "https://thingsboard.io/docs/reference/releases", "https://thingsboard.io/docs/reference/releases"); - edgeUpgradeMessage = new EdgeUpgradeMessage(new HashMap<>()); if (updatesEnabled) { try { platform = System.getProperty("platform", "unknown"); @@ -173,5 +169,4 @@ public class DefaultUpdateService implements UpdateService { public UpdateMessage checkUpdates() { return updateMessage; } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java index d4f4a4a241..a0b0455b9e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java @@ -44,11 +44,11 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EdgeUpgradeInfo; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.EdgeUpgradeInfo; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -1200,4 +1200,40 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertTrue(upgradeInstructions.contains("Upgrading to 3.6.1EDGE")); Assert.assertTrue(upgradeInstructions.contains("Upgrading to 3.6.2EDGE")); } + + @Test + public void testIsEdgeUpgradeAvailable() throws Exception { + Edge edge = constructEdge("Edge Upgrade Available", "default"); + Edge savedEdge = doPost("/api/edge", edge, Edge.class); + + // Test 3.5.0 Edge - upgrade not available + String body = "{\"edgeVersion\": \"V_3_5_0\"}"; + doPostAsync("/api/plugins/telemetry/EDGE/" + savedEdge.getId().getId() + "/attributes/SERVER_SCOPE", body, String.class, status().isOk()); + edgeUpgradeInstructionsService.setAppVersion("3.6.0"); + Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + edgeUpgradeInstructionsService.setAppVersion("3.6.2"); + Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + edgeUpgradeInstructionsService.setAppVersion("3.6.2.7"); + Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + + // Test 3.6.0 Edge - upgrade available + body = "{\"edgeVersion\": \"V_3_6_0\"}"; + doPostAsync("/api/plugins/telemetry/EDGE/" + savedEdge.getId().getId() + "/attributes/SERVER_SCOPE", body, String.class, status().isOk()); + edgeUpgradeInstructionsService.setAppVersion("3.6.0"); + Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + edgeUpgradeInstructionsService.setAppVersion("3.6.1.5"); + Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + edgeUpgradeInstructionsService.setAppVersion("3.6.2"); + Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + + // Test 3.6.1 Edge - upgrade available + body = "{\"edgeVersion\": \"V_3_6_1\"}"; + doPostAsync("/api/plugins/telemetry/EDGE/" + savedEdge.getId().getId() + "/attributes/SERVER_SCOPE", body, String.class, status().isOk()); + edgeUpgradeInstructionsService.setAppVersion("3.6.1"); + Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + edgeUpgradeInstructionsService.setAppVersion("3.6.2"); + Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + edgeUpgradeInstructionsService.setAppVersion("3.6.2.6"); + Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index ca48ef364a..912049cb8b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -124,6 +124,7 @@ public class DataConstants { public static final String PASSWORD = "password"; public static final String EDGE_MSG_SOURCE = "edge"; public static final String MSG_SOURCE_KEY = "source"; + public static final String EDGE_VERSION_ATTR_KEY = "edgeVersion"; public static final String LAST_CONNECTED_GATEWAY = "lastConnectedGateway"; diff --git a/ui-ngx/src/app/core/http/edge.service.ts b/ui-ngx/src/app/core/http/edge.service.ts index 30fbb64c54..d3050dfab6 100644 --- a/ui-ngx/src/app/core/http/edge.service.ts +++ b/ui-ngx/src/app/core/http/edge.service.ts @@ -121,4 +121,8 @@ export class EdgeService { public getEdgeUpgradeInstructions(edgeVersion: string, method: string = 'ubuntu', config?: RequestConfig): Observable { return this.http.get(`/api/edge/instructions/upgrade/${edgeVersion}/${method}`, defaultHttpOptionsFromConfig(config)); } + + public isEdgeUpgradeAvailable(edgeId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/edge/${edgeId}/upgrade/available`, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts index b7cb2b1752..cdf7f1b6b6 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts @@ -20,15 +20,13 @@ import { AppState } from '@core/core.state'; import { EntityComponent } from '@home/components/entity/entity.component'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { EntityType } from '@shared/models/entity-type.models'; -import { EdgeInfo, edgeVersionAttributeKey } from '@shared/models/edge.models'; +import { EdgeInfo } from '@shared/models/edge.models'; import { TranslateService } from '@ngx-translate/core'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { generateSecret, guid } from '@core/utils'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { environment as env } from '@env/environment'; -import { AttributeService } from '@core/http/attribute.service'; -import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import {EdgeService} from "@core/http/edge.service"; @Component({ selector: 'tb-edge', @@ -44,7 +42,7 @@ export class EdgeComponent extends EntityComponent { constructor(protected store: Store, protected translate: TranslateService, - private attributeService: AttributeService, + private edgeService: EdgeService, @Inject('entity') protected entityValue: EdgeInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: UntypedFormBuilder, @@ -100,7 +98,10 @@ export class EdgeComponent extends EntityComponent { } }); this.generateRoutingKeyAndSecret(entity, this.entityForm); - this.checkEdgeVersion(); + this.edgeService.isEdgeUpgradeAvailable(this.entity.id.id) + .subscribe(isUpgradeAvailable => { + this.upgradeAvailable = isUpgradeAvailable; + }); } updateFormState() { @@ -139,25 +140,4 @@ export class EdgeComponent extends EntityComponent { form.get('secret').patchValue(generateSecret(20), {emitEvent: false}); } } - - checkEdgeVersion() { - this.attributeService.getEntityAttributes(this.entity.id, AttributeScope.SERVER_SCOPE, [edgeVersionAttributeKey]) - .subscribe(attributes => { - if (attributes?.length) { - const edgeVersion = attributes[0].value; - const tbVersion = 'V_' + env.tbVersion.replaceAll('.', '_'); - this.upgradeAvailable = this.versionUpgradeSupported(edgeVersion) && (edgeVersion !== tbVersion); - } else { - this.upgradeAvailable = false; - } - } - ); - } - - private versionUpgradeSupported(edgeVersion: string): boolean { - const edgeVersionArray = edgeVersion.split('_'); - const major = parseInt(edgeVersionArray[1]); - const minor = parseInt(edgeVersionArray[2]); - return major >= 3 && minor >= 6; - } }