## Testcontainers compatibility with the Docker API workaround
In case your tests failed to run testcontainers due to unsupported Docker API version
:coffee: testcontainers (Docker API 1.32) + :whale: docker 29 (min API 1.44) workaround
Add to /etc/docker/daemon.json and restart docker
```json
{
"min-api-version": "1.32"
}
```
Same works on Mac, except `daemon.json` are located in another folder and required to be edited from Docker Desktop UI.
Tip: If your testcontainer are struggling to find any Docker. You can try to remove the testcontainers property file. It will be recreated on the next testcontainers run.
ThingsBoard service is running on Java 17. To install OpenJDK 17, follow these instructions:
#### Step 1. Install Java 25 (OpenJDK)
ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions:
```bash
sudo dnf install java-17-openjdk
sudo dnf install -y java-25-openjdk
{:copy-code}
```
Configure your operating system to use OpenJDK 17 by default. You can configure the default version by running the following command:
Configure your operating system to use OpenJDK 25 by default. You can configure the default version by running the following command:
```bash
sudo update-alternatives --config java
@ -33,7 +35,7 @@ java -version
The expected result is:
```text
openjdk version "17.x.xx"
openjdk version "25.x.xx"
OpenJDK Runtime Environment (...)
OpenJDK 64-Bit Server VM (build ...)
```
@ -41,7 +43,7 @@ OpenJDK 64-Bit Server VM (build ...)
#### Step 2. Configure ThingsBoard Edge Database
ThingsBoard Edge supports **SQL** and **hybrid** database configurations.
In this guide, we’ll use an **SQL** database.
In this guide, we'll use an **SQL** database.
For more details about the hybrid setup, please refer to the official installation instructions on the <ahref="https://thingsboard.io/docs/user-guide/install/edge/rhel/#step-2-configure-thingsboard-database"target="_blank">ThingsBoard documentation site</a>.
To install the PostgreSQL database, run these commands:
@ -137,7 +137,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
defaultCtx.tellFailure(msg.getMsg(),e);
}
}else{
tbMsg.getCallback().onFailure(newRuleNodeException("Message is processed by more then "+maxRuleNodeExecutionsPerMessage+" rule nodes!",ruleChainName,ruleNode));
tbMsg.getCallback().onFailure(newRuleNodeException("Message is processed by more than "+maxRuleNodeExecutionsPerMessage+" rule nodes!",ruleChainName,ruleNode));
}
}
@ -160,7 +160,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
msg.getCtx().tellFailure(msg.getMsg(),e);
}
}else{
tbMsg.getCallback().onFailure(newRuleNodeException("Message is processed by more then "+maxRuleNodeExecutionsPerMessage+" rule nodes!",ruleChainName,ruleNode));
tbMsg.getCallback().onFailure(newRuleNodeException("Message is processed by more than "+maxRuleNodeExecutionsPerMessage+" rule nodes!",ruleChainName,ruleNode));
notes="Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. "+
@ -136,7 +136,7 @@ public class AuthController extends BaseController {
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Create Password' page and same 'activateToken' specified in the URL parameters. "+
"If token is not valid, returns '409 Conflict'. "+
@ -176,7 +176,7 @@ public class AuthController extends BaseController {
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Reset Password' page and same 'resetToken' specified in the URL parameters. "+
"If token is not valid, returns '409 Conflict'. "+
notes="Fetch tenant calculated fields based on the filter.")
@Parameters({
@Parameter(name="name",description="Repeatable name query parameter",in=ParameterIn.QUERY,required=false,array=@ArraySchema(schema=@Schema(type="string")))
@Parameter(description="List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)",array=@ArraySchema(schema=@Schema(type="string")),required=true)
publicList<DashboardInfo>getDashboardsByIds(@Parameter(description="A list of dashboard ids, separated by comma ','",array=@ArraySchema(schema=@Schema(type="string")),required=true)
publicList<DashboardInfo>getDashboardsByIds(@Parameter(description="A list of dashboard ids, separated by comma ','",array=@ArraySchema(schema=@Schema(type="string")),required=true)
@ApiOperation(value="Download server certificate using file path defined in device.connectivity properties (downloadServerCertificate)",notes="Download server certificate.")
@ApiOperation(value="Download server certificate using file path defined in device.connectivity properties (downloadServerCertificate)",
@ -502,8 +507,7 @@ public class DeviceController extends BaseController {
@ApiOperation(value="Get Devices By Ids (getDevicesByIds)",
notes="Requested devices must be owned by tenant or assigned to customer which user is performing the request. "+TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@Parameter(description="A string value representing external entity id. This is `externalId` property of an entity, or otherwise if not set - simply id of this entity.")
@ -263,7 +264,7 @@ public class EntitiesVersionControlController extends BaseController {
"If specified branch does not exist - empty page data will be returned. "+
"The response structure is the same as for `listEntityVersions` API method."+
publicList<EntityRelation>findEntityRelationsByQuery(@Parameter(description="A JSON value representing the entity relations query object.",required=true)
publicList<EntityRelationInfo>findEntityRelationInfosByQuery(@Parameter(description="A JSON value representing the entity relations query object.",required=true)
@ -337,13 +345,13 @@ public class EntityViewController extends BaseController {
}
}
@ApiOperation(value="Find related entity views (findByQuery)",
@ApiOperation(value="Find related entity views (findEntityViewsByQuery)",
notes="Returns all entity views that are related to the specific entity. "+
"The entity id, relation type, entity view types, depth of the search, and other query parameters defined using complex 'EntityViewSearchQuery' object. "+
"See 'Model' tab of the Parameters for more info."+TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
publicList<EntityView>getEntityViewsByIds(@Parameter(description="A list of entity view ids, separated by comma ','",array=@ArraySchema(schema=@Schema(type="string")),required=true)
publicList<EntityView>getEntityViewsByIdsV1(@Parameter(description="A list of entity view ids, separated by comma ','",array=@ArraySchema(schema=@Schema(type="string")),required=true)
publicList<EntityView>getEntityViewsByIds(@Parameter(description="A list of entity view ids, separated by comma ','",array=@ArraySchema(schema=@Schema(type="string")),required=true)
@ -43,12 +43,12 @@ public class MailConfigTemplateController extends BaseController {
privatestaticfinalStringMAIL_CONFIG_TEMPLATE_DEFINITION="Mail configuration template is set of default smtp settings for mail server that specific provider supports";
publicList<NotificationTarget>getNotificationTargetsByIds(@Parameter(description="Comma-separated list of uuids representing targets ids",array=@ArraySchema(schema=@Schema(type="string")),required=true)
publicList<NotificationTarget>getNotificationTargetsByIdsV1(@Parameter(description="Comma-separated list of uuids representing targets ids",array=@ArraySchema(schema=@Schema(type="string")),required=true)
@RequestParam("ids")UUID[]ids,
@AuthenticationPrincipalSecurityUseruser){
// PE: generic permission
@ -166,6 +165,17 @@ public class NotificationTargetController extends BaseController {
publicList<NotificationTarget>getNotificationTargetsByIds(@Parameter(description="Comma-separated list of uuids representing targets ids",array=@ArraySchema(schema=@Schema(type="string")),required=true)
@ApiOperation(value="Get the list of all OAuth2 client registration templates (getClientRegistrationTemplates)"+SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
@ApiOperation(value="Get the list of all OAuth2 client registration templates (getOAuth2ClientRegistrationTemplates)"+SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
@ApiOperation(value="Get Mobile application settings (getMobileAppSettings)",
@ApiOperation(value="Get Mobile application settings (getQrCodeSettings)",
notes="The response payload contains configuration for android/iOS applications and platform qr code widget settings."+AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@ -113,7 +113,7 @@ public class RpcV2Controller extends AbstractRpcController {
privatestaticfinalStringTWO_WAY_RPC_REQUEST_DESCRIPTION="Sends the two-way remote-procedure call (RPC) request to device. "+RPC_REQUEST_DESCRIPTION+TWO_WAY_RPC_RESULT+TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
@ -449,6 +497,9 @@ public class TelemetryController extends BaseController {
" Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) if the value's timestamp matches the time-range and 'deleteLatest' param is true."+
" The replacement value will be fetched from the 'time series' table, and its timestamp will be the most recent one before the defined time-range. "+
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@Parameters({
@Parameter(name="key",description="Repeatable key query parameter (alternative to comma-separated 'keys')",in=ParameterIn.QUERY,required=false,array=@ArraySchema(schema=@Schema(type="string")))
})
@ApiResponses(value={
@ApiResponse(responseCode="200",description="Time series for the selected keys in the request was removed. "+
"Platform creates an audit log event about entity time series removal with action type 'TIMESERIES_DELETED'."),
@ -473,6 +524,7 @@ public class TelemetryController extends BaseController {
@Parameter(description="If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.")
@Parameter(name="key",description="Repeatable key query parameter (alternative to comma-separated 'keys')",in=ParameterIn.QUERY,required=false,array=@ArraySchema(schema=@Schema(type="string")))
})
@ApiResponses(value={
@ApiResponse(responseCode="200",description="Entity attributes was removed for the selected keys in the request. "+
@ApiResponse(responseCode="200",description="Entity attributes were removed for the selected keys in the request (keys that did not exist are silently ignored). "+
"Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."),
@ApiResponse(responseCode="400",description="Platform returns a bad request in case if keys or scope are not specified."),
@ApiResponse(responseCode="401",description="User is not authorized to delete entity attributes for selected entity. Most likely, User belongs to different Customer or Tenant."),
@ -568,6 +628,7 @@ public class TelemetryController extends BaseController {
@ -133,7 +133,7 @@ public class TenantController extends BaseController {
@ApiOperation(value="Get Tenants (getTenants)",notes="Returns a page of tenants registered in the platform. "+PAGE_DATA_PARAMETERS+SYSTEM_AUTHORITY_PARAGRAPH)
@ -242,7 +243,7 @@ public class TenantProfileController extends BaseController {
@ApiOperation(value="Get Tenant Profiles Info (getTenantProfileInfos)",notes="Returns a page of tenant profile info objects registered in the platform. "
publicList<TenantProfile>getTenantProfilesByIds(@Parameter(description="Comma-separated list of tenant profile ids",array=@ArraySchema(schema=@Schema(type="string")))
@ApiOperation(value="Get Tenant Profile list (getTenantProfileList)")
@GetMapping(value="/tenantProfiles/list")
@PreAuthorize("hasAuthority('SYS_ADMIN')")
publicList<TenantProfile>getTenantProfileList(@Parameter(description="Comma-separated list of tenant profile ids",array=@ArraySchema(schema=@Schema(type="string")))
@ -209,8 +210,7 @@ public class WidgetTypeController extends AutoCommitController {
}
}
@ApiOperation(value="Get all Widget types for specified Bundle (getBundleWidgetTypesByBundleAlias) (Deprecated)",
notes="Returns an array of Widget Type objects that belong to specified Widget Bundle."+WIDGET_TYPE_DESCRIPTION+" "+SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@ApiOperation(value="Get all Widget types for specified Bundle (getBundleWidgetTypes)",
notes="Returns an array of Widget Type objects that belong to specified Widget Bundle."+WIDGET_TYPE_DESCRIPTION+" "+SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@ApiOperation(value="Get all Widget types details for specified Bundle (getBundleWidgetTypesDetailsByBundleAlias) (Deprecated)",
notes="Returns an array of Widget Type Details objects that belong to specified Widget Bundle."+WIDGET_TYPE_DETAILS_DESCRIPTION+" "+SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@ApiOperation(value="Get all Widget types for specified Bundle (getBundleWidgetTypes)",
notes="Returns an array of Widget Type objects that belong to specified Widget Bundle."+WIDGET_TYPE_DESCRIPTION+" "+SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@ -222,11 +222,10 @@ public class WidgetsBundleController extends BaseController {
}
}
@ApiOperation(value="Get all Widget Bundles (getWidgetsBundles)",
notes="Returns an array of Widget Bundle objects that are available for current user."+WIDGET_BUNDLE_DESCRIPTION+" "+AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@ -235,13 +234,18 @@ public class WidgetsBundleController extends BaseController {
}
}
@ApiOperation(value="Get Widgets Bundles By Ids (getWidgetsBundlesByIds)",
notes="Requested widgets bundles must be system level or owned by tenant of the user which is performing the request. "+
NEW_LINE)
@ApiOperation(value="Get all Widget Bundles (getAllWidgetsBundles)",
notes="Returns an array of Widget Bundle objects that are available for current user."+WIDGET_BUNDLE_DESCRIPTION+" "+AVAILABLE_FOR_ANY_AUTHORIZED_USER)
log.trace("Removed {} notification requests for tenant {}",tenantRemoved,tenantId);
}
}catch(Exceptione){
log.warn("Failed to clean up notification requests for tenant {}",tenantId,e);
}
}
log.info("Notification requests cleanup completed. Processed {} tenants, removed {} total records older than {}",tenantsProcessed,totalRemoved,Instant.ofEpochMilli(expirationTime));