267 changed files with 10056 additions and 2652 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,56 @@ |
|||
/** |
|||
* 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.controller; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.mail.TbMailConfigTemplateService; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@RequestMapping("/api/mail/config/template") |
|||
@Slf4j |
|||
public class MailConfigTemplateController extends BaseController { |
|||
private static final String MAIL_CONFIG_TEMPLATE_DEFINITION = "Mail configuration template is set of default smtp settings for mail server that specific provider supports"; |
|||
private final TbMailConfigTemplateService mailConfigTemplateService; |
|||
|
|||
@ApiOperation(value = "Get the list of all OAuth2 client registration templates (getClientRegistrationTemplates)" + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, |
|||
notes = MAIL_CONFIG_TEMPLATE_DEFINITION) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
|||
@RequestMapping(method = RequestMethod.GET, produces = "application/json") |
|||
@ResponseBody |
|||
public JsonNode getClientRegistrationTemplates() throws ThingsboardException, IOException { |
|||
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); |
|||
return mailConfigTemplateService.findAllMailConfigTemplates(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* 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.mail; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.core.io.ClassPathResource; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.io.IOException; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class DefaultTbMailConfigTemplateService implements TbMailConfigTemplateService { |
|||
|
|||
private JsonNode mailConfigTemplates; |
|||
|
|||
@PostConstruct |
|||
private void postConstruct() throws IOException { |
|||
mailConfigTemplates = JacksonUtil.toJsonNode(new ClassPathResource("/templates/mail_config_templates.json").getFile()); |
|||
} |
|||
|
|||
@Override |
|||
public JsonNode findAllMailConfigTemplates() { |
|||
return mailConfigTemplates; |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* 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.mail; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import com.google.api.client.auth.oauth2.ClientParametersAuthentication; |
|||
import com.google.api.client.auth.oauth2.RefreshTokenRequest; |
|||
import com.google.api.client.auth.oauth2.TokenResponse; |
|||
import com.google.api.client.http.GenericUrl; |
|||
import com.google.api.client.http.javanet.NetHttpTransport; |
|||
import com.google.api.client.json.gson.GsonFactory; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.AdminSettings; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.settings.AdminSettingsService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.io.IOException; |
|||
import java.time.Duration; |
|||
import java.time.Instant; |
|||
|
|||
import static org.thingsboard.server.common.data.mail.MailOauth2Provider.OFFICE_365; |
|||
|
|||
@TbCoreComponent |
|||
@Service |
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
public class RefreshTokenExpCheckService { |
|||
public static final int AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS = 90; |
|||
private final AdminSettingsService adminSettingsService; |
|||
|
|||
@Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${mail.oauth2.refreshTokenCheckingInterval})}", fixedDelayString = "${mail.oauth2.refreshTokenCheckingInterval}") |
|||
public void check() throws IOException { |
|||
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); |
|||
if (settings != null && settings.getJsonValue().has("enableOauth2") && settings.getJsonValue().get("enableOauth2").asBoolean()) { |
|||
JsonNode jsonValue = settings.getJsonValue(); |
|||
if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshTokenExpires")) { |
|||
long expiresIn = jsonValue.get("refreshTokenExpires").longValue(); |
|||
if ((expiresIn - System.currentTimeMillis()) < 604800000L) { //less than 7 days
|
|||
log.info("Trying to refresh refresh token."); |
|||
|
|||
String clientId = jsonValue.get("clientId").asText(); |
|||
String clientSecret = jsonValue.get("clientSecret").asText(); |
|||
String refreshToken = jsonValue.get("refreshToken").asText(); |
|||
String tokenUri = jsonValue.get("tokenUri").asText(); |
|||
|
|||
TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(), |
|||
new GenericUrl(tokenUri), refreshToken) |
|||
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) |
|||
.execute(); |
|||
((ObjectNode)jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); |
|||
((ObjectNode)jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); |
|||
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* 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.mail; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
public interface TbMailConfigTemplateService { |
|||
JsonNode findAllMailConfigTemplates() throws IOException; |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* 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.mail; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.dao.settings.AdminSettingsService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
@Component |
|||
@Data |
|||
@Lazy |
|||
public class TbMailContextComponent { |
|||
|
|||
@Autowired |
|||
private AdminSettingsService adminSettingsService; |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
/** |
|||
* 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.mail; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import com.google.api.client.auth.oauth2.ClientParametersAuthentication; |
|||
import com.google.api.client.auth.oauth2.RefreshTokenRequest; |
|||
import com.google.api.client.auth.oauth2.TokenResponse; |
|||
import com.google.api.client.http.GenericUrl; |
|||
import com.google.api.client.http.javanet.NetHttpTransport; |
|||
import com.google.api.client.json.gson.GsonFactory; |
|||
import lombok.SneakyThrows; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.lang.Nullable; |
|||
import org.springframework.mail.javamail.JavaMailSenderImpl; |
|||
import org.thingsboard.server.common.data.AdminSettings; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.mail.MailOauth2Provider; |
|||
import org.thingsboard.server.dao.exception.IncorrectParameterException; |
|||
|
|||
import javax.mail.internet.MimeMessage; |
|||
import java.time.Duration; |
|||
import java.time.Instant; |
|||
import java.util.Properties; |
|||
import java.util.concurrent.locks.Lock; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
|
|||
import static org.thingsboard.server.service.mail.RefreshTokenExpCheckService.AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS; |
|||
|
|||
@Slf4j |
|||
public class TbMailSender extends JavaMailSenderImpl { |
|||
|
|||
private static final String MAIL_PROP = "mail."; |
|||
private final TbMailContextComponent ctx; |
|||
private final Lock lock; |
|||
private final Boolean oauth2Enabled; |
|||
private volatile String accessToken; |
|||
private volatile long tokenExpires; |
|||
|
|||
public TbMailSender(TbMailContextComponent ctx, JsonNode jsonConfig) { |
|||
super(); |
|||
this.lock = new ReentrantLock(); |
|||
this.tokenExpires = 0L; |
|||
this.ctx = ctx; |
|||
this.oauth2Enabled = jsonConfig.has("enableOauth2") && jsonConfig.get("enableOauth2").asBoolean(); |
|||
|
|||
setHost(jsonConfig.get("smtpHost").asText()); |
|||
setPort(parsePort(jsonConfig.get("smtpPort").asText())); |
|||
setUsername(jsonConfig.get("username").asText()); |
|||
if (jsonConfig.has("password")) { |
|||
setPassword(jsonConfig.get("password").asText()); |
|||
} |
|||
setJavaMailProperties(createJavaMailProperties(jsonConfig)); |
|||
} |
|||
|
|||
@SneakyThrows |
|||
@Override |
|||
public void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) { |
|||
if (oauth2Enabled && (System.currentTimeMillis() > tokenExpires)){ |
|||
refreshAccessToken(); |
|||
setPassword(accessToken); |
|||
} |
|||
super.doSend(mimeMessages, originalMessages); |
|||
} |
|||
|
|||
private Properties createJavaMailProperties(JsonNode jsonConfig) { |
|||
Properties javaMailProperties = new Properties(); |
|||
String protocol = jsonConfig.get("smtpProtocol").asText(); |
|||
javaMailProperties.put("mail.transport.protocol", protocol); |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".host", jsonConfig.get("smtpHost").asText()); |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".port", jsonConfig.get("smtpPort").asText()); |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".timeout", jsonConfig.get("timeout").asText()); |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText()))); |
|||
boolean enableTls = false; |
|||
if (jsonConfig.has("enableTls")) { |
|||
if (jsonConfig.get("enableTls").isBoolean() && jsonConfig.get("enableTls").booleanValue()) { |
|||
enableTls = true; |
|||
} else if (jsonConfig.get("enableTls").isTextual()) { |
|||
enableTls = "true".equalsIgnoreCase(jsonConfig.get("enableTls").asText()); |
|||
} |
|||
} |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls); |
|||
if (enableTls && jsonConfig.has("tlsVersion") && !jsonConfig.get("tlsVersion").isNull()) { |
|||
String tlsVersion = jsonConfig.get("tlsVersion").asText(); |
|||
if (StringUtils.isNoneEmpty(tlsVersion)) { |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", tlsVersion); |
|||
} |
|||
} |
|||
|
|||
boolean enableProxy = jsonConfig.has("enableProxy") && jsonConfig.get("enableProxy").asBoolean(); |
|||
|
|||
if (enableProxy) { |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.host", jsonConfig.get("proxyHost").asText()); |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.port", jsonConfig.get("proxyPort").asText()); |
|||
String proxyUser = jsonConfig.get("proxyUser").asText(); |
|||
if (StringUtils.isNoneEmpty(proxyUser)) { |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.user", proxyUser); |
|||
} |
|||
String proxyPassword = jsonConfig.get("proxyPassword").asText(); |
|||
if (StringUtils.isNoneEmpty(proxyPassword)) { |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.password", proxyPassword); |
|||
} |
|||
} |
|||
|
|||
if (oauth2Enabled) { |
|||
javaMailProperties.put(MAIL_PROP + protocol + ".auth.mechanisms", "XOAUTH2"); |
|||
} |
|||
return javaMailProperties; |
|||
} |
|||
|
|||
public void refreshAccessToken() throws ThingsboardException { |
|||
lock.lock(); |
|||
try { |
|||
if (System.currentTimeMillis() > tokenExpires) { |
|||
AdminSettings settings = ctx.getAdminSettingsService().findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); |
|||
JsonNode jsonValue = settings.getJsonValue(); |
|||
|
|||
String clientId = jsonValue.get("clientId").asText(); |
|||
String clientSecret = jsonValue.get("clientSecret").asText(); |
|||
String refreshToken = jsonValue.get("refreshToken").asText(); |
|||
String tokenUri = jsonValue.get("tokenUri").asText(); |
|||
String providerId = jsonValue.get("providerId").asText(); |
|||
|
|||
TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(), |
|||
new GenericUrl(tokenUri), refreshToken) |
|||
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) |
|||
.execute(); |
|||
if (MailOauth2Provider.OFFICE_365.name().equals(providerId)) { |
|||
((ObjectNode)jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); |
|||
((ObjectNode)jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); |
|||
ctx.getAdminSettingsService().saveAdminSettings(TenantId.SYS_TENANT_ID, settings); |
|||
} |
|||
accessToken = tokenResponse.getAccessToken(); |
|||
tokenExpires = System.currentTimeMillis() + (tokenResponse.getExpiresInSeconds().intValue() * 1000); |
|||
} |
|||
} catch (Exception e) { |
|||
log.warn("Unable to retrieve access token: {}", e.getMessage()); |
|||
throw new ThingsboardException("Error while retrieving access token: " + e.getMessage(), ThingsboardErrorCode.GENERAL); |
|||
} finally { |
|||
lock.unlock(); |
|||
} |
|||
} |
|||
|
|||
private int parsePort(String strPort) { |
|||
try { |
|||
return Integer.parseInt(strPort); |
|||
} catch (NumberFormatException e) { |
|||
throw new IncorrectParameterException(String.format("Invalid smtp port value: %s", strPort)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
[ |
|||
{ |
|||
"providerId": "SENDGRID", |
|||
"smtpProtocol": "SMTPS", |
|||
"smtpHost": "smtp.sendgrid.net", |
|||
"smtpPort": 465, |
|||
"timeout": 10000, |
|||
"enableTls": true, |
|||
"tlsVersion": "TLSv1.2", |
|||
"authorizationUri": null, |
|||
"accessTokenUri": null, |
|||
"scope": [ |
|||
"" |
|||
], |
|||
"helpLink": null, |
|||
"name": "SendGrid" |
|||
}, |
|||
{ |
|||
"providerId": "GOOGLE", |
|||
"smtpProtocol": "SMTPS", |
|||
"smtpHost": "smtp.gmail.com", |
|||
"smtpPort": 465, |
|||
"timeout": 10000, |
|||
"enableTls": true, |
|||
"tlsVersion": "TLSv1.2", |
|||
"authorizationUri": "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline", |
|||
"accessTokenUri": "https://oauth2.googleapis.com/token", |
|||
"scope": [ |
|||
"https://mail.google.com/" |
|||
], |
|||
"helpLink": "https://support.google.com/googleapi/answer/6158849", |
|||
"name": "Google" |
|||
}, |
|||
{ |
|||
"providerId": "OFFICE_365", |
|||
"smtpProtocol": "SMTP", |
|||
"smtpHost": "smtp.office365.com", |
|||
"smtpPort": 587, |
|||
"timeout": 10000, |
|||
"enableTls": true, |
|||
"tlsVersion": "TLSv1.2", |
|||
"authorizationUri": "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize", |
|||
"accessTokenUri": "https://login.microsoftonline.com/%s/oauth2/v2.0/token", |
|||
"scope": [ |
|||
"https://outlook.office365.com/SMTP.Send", |
|||
"offline_access", |
|||
"openid" |
|||
], |
|||
"helpLink": "https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth", |
|||
"name": "Office 365" |
|||
} |
|||
] |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,45 @@ |
|||
/** |
|||
* 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.transport.mqtt.mqttv3; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.paho.client.mqttv3.MqttMessage; |
|||
|
|||
@Data |
|||
@Slf4j |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class MqttTestSubscribeOnTopicCallback extends MqttTestCallback { |
|||
|
|||
protected final String awaitSubTopic; |
|||
|
|||
public MqttTestSubscribeOnTopicCallback(String awaitSubTopic) { |
|||
super(); |
|||
this.awaitSubTopic = awaitSubTopic; |
|||
} |
|||
|
|||
@Override |
|||
public void messageArrived(String requestTopic, MqttMessage mqttMessage) { |
|||
log.warn("messageArrived on topic: {}, awaitSubTopic: {}", requestTopic, awaitSubTopic); |
|||
if (awaitSubTopic.equals(requestTopic)) { |
|||
qoS = mqttMessage.getQos(); |
|||
payloadBytes = mqttMessage.getPayload(); |
|||
subscribeLatch.countDown(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* 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.cache; |
|||
|
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.data.redis.connection.RedisSentinelConfiguration; |
|||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; |
|||
|
|||
@Configuration |
|||
@ConditionalOnMissingBean(TbCaffeineCacheConfiguration.class) |
|||
@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "sentinel") |
|||
public class TBRedisSentinelConfiguration extends TBRedisCacheConfiguration { |
|||
|
|||
@Value("${redis.sentinel.master:}") |
|||
private String master; |
|||
|
|||
@Value("${redis.sentinel.sentinels:}") |
|||
private String sentinels; |
|||
|
|||
@Value("${redis.sentinel.password:}") |
|||
private String sentinelPassword; |
|||
|
|||
@Value("${redis.sentinel.useDefaultPoolConfig:true}") |
|||
private boolean useDefaultPoolConfig; |
|||
|
|||
@Value("${redis.db:}") |
|||
private Integer database; |
|||
|
|||
@Value("${redis.password:}") |
|||
private String password; |
|||
|
|||
public JedisConnectionFactory loadFactory() { |
|||
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(); |
|||
redisSentinelConfiguration.setMaster(master); |
|||
redisSentinelConfiguration.setSentinels(getNodes(sentinels)); |
|||
redisSentinelConfiguration.setSentinelPassword(sentinelPassword); |
|||
redisSentinelConfiguration.setPassword(password); |
|||
redisSentinelConfiguration.setDatabase(database); |
|||
if (useDefaultPoolConfig) { |
|||
return new JedisConnectionFactory(redisSentinelConfiguration); |
|||
} else { |
|||
return new JedisConnectionFactory(redisSentinelConfiguration, buildPoolConfig()); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* 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.common.data; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
@Data |
|||
@Builder |
|||
public class TbResourceInfoFilter { |
|||
|
|||
private TenantId tenantId; |
|||
private ResourceType resourceType; |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* 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.common.data.mail; |
|||
|
|||
public enum MailOauth2Provider { |
|||
GOOGLE("Google"), OFFICE_365("Office 365"), SENDGRID("SendGrid"), CUSTOM("Custom"); |
|||
|
|||
public final String label; |
|||
|
|||
MailOauth2Provider(String label) { |
|||
this.label = label; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return label; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
CACHE_TYPE=redis |
|||
REDIS_CONNECTION_TYPE=sentinel |
|||
REDIS_MASTER=mymaster |
|||
REDIS_SENTINELS=redis-sentinel:26379 |
|||
REDIS_SENTINEL_PASSWORD=sentinel |
|||
REDIS_USE_DEFAULT_POOL_CONFIG=false |
|||
REDIS_PASSWORD=thingsboard |
|||
@ -0,0 +1,40 @@ |
|||
# |
|||
# 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. |
|||
# |
|||
|
|||
version: '3.0' |
|||
|
|||
services: |
|||
# Redis sentinel |
|||
redis-master: |
|||
volumes: |
|||
- redis-sentinel-data-master:/bitnami/redis/data |
|||
redis-slave: |
|||
volumes: |
|||
- redis-sentinel-data-slave:/bitnami/redis/data |
|||
redis-sentinel: |
|||
volumes: |
|||
- redis-sentinel-data-sentinel:/bitnami/redis/data |
|||
|
|||
volumes: |
|||
redis-sentinel-data-master: |
|||
external: |
|||
name: ${REDIS_SENTINEL_DATA_VOLUME_MASTER} |
|||
redis-sentinel-data-slave: |
|||
external: |
|||
name: ${REDIS_SENTINEL_DATA_VOLUME_SLAVE} |
|||
redis-sentinel-data-sentinel: |
|||
external: |
|||
name: ${REDIS_SENTINEL_DATA_VOLUME_SENTINEL} |
|||
@ -0,0 +1,119 @@ |
|||
# |
|||
# 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. |
|||
# |
|||
|
|||
version: '3.0' |
|||
|
|||
services: |
|||
# Redis sentinel |
|||
redis-master: |
|||
image: 'bitnami/redis:7.0' |
|||
volumes: |
|||
- ./tb-node/redis-sentinel-data-master:/bitnami/redis/data |
|||
environment: |
|||
- 'REDIS_REPLICATION_MODE=master' |
|||
- 'REDIS_PASSWORD=thingsboard' |
|||
|
|||
redis-slave: |
|||
image: 'bitnami/redis:7.0' |
|||
volumes: |
|||
- ./tb-node/redis-sentinel-data-slave:/bitnami/redis/data |
|||
environment: |
|||
- 'REDIS_REPLICATION_MODE=slave' |
|||
- 'REDIS_MASTER_HOST=redis-master' |
|||
- 'REDIS_MASTER_PASSWORD=thingsboard' |
|||
- 'REDIS_PASSWORD=thingsboard' |
|||
depends_on: |
|||
- redis-master |
|||
|
|||
redis-sentinel: |
|||
image: 'bitnami/redis-sentinel:7.0' |
|||
volumes: |
|||
- ./tb-node/redis-sentinel-data-sentinel:/bitnami/redis/data |
|||
environment: |
|||
- 'REDIS_MASTER_HOST=redis-master' |
|||
- 'REDIS_MASTER_SET=mymaster' |
|||
- 'REDIS_SENTINEL_PASSWORD=sentinel' |
|||
- 'REDIS_MASTER_PASSWORD=thingsboard' |
|||
depends_on: |
|||
- redis-master |
|||
- redis-slave |
|||
|
|||
# ThingsBoard setup to use redis-sentinel |
|||
tb-core1: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-core2: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-rule-engine1: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-rule-engine2: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-mqtt-transport1: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-mqtt-transport2: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-http-transport1: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-http-transport2: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-coap-transport: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-lwm2m-transport: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-snmp-transport: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-vc-executor1: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
tb-vc-executor2: |
|||
env_file: |
|||
- cache-redis-sentinel.env |
|||
depends_on: |
|||
- redis-sentinel |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* 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.monitoring.config; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface MonitoringTarget { |
|||
|
|||
UUID getDeviceId(); |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* 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.monitoring.config.transport; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.monitoring.config.MonitoringTarget; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
public class TransportMonitoringTarget implements MonitoringTarget { |
|||
|
|||
private String baseUrl; |
|||
private DeviceConfig device; // set manually during initialization
|
|||
|
|||
@Override |
|||
public UUID getDeviceId() { |
|||
return device.getId(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
/** |
|||
* 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.monitoring.service; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.ApplicationContext; |
|||
import org.thingsboard.monitoring.client.TbClient; |
|||
import org.thingsboard.monitoring.client.WsClient; |
|||
import org.thingsboard.monitoring.client.WsClientFactory; |
|||
import org.thingsboard.monitoring.config.MonitoringConfig; |
|||
import org.thingsboard.monitoring.config.MonitoringTarget; |
|||
import org.thingsboard.monitoring.data.Latencies; |
|||
import org.thingsboard.monitoring.data.MonitoredServiceKey; |
|||
import org.thingsboard.monitoring.service.transport.TransportHealthChecker; |
|||
import org.thingsboard.monitoring.util.TbStopWatch; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public abstract class BaseMonitoringService<C extends MonitoringConfig<T>, T extends MonitoringTarget> { |
|||
|
|||
@Autowired |
|||
private List<C> configs; |
|||
private final List<BaseHealthChecker<C, T>> healthCheckers = new LinkedList<>(); |
|||
private final List<UUID> devices = new LinkedList<>(); |
|||
|
|||
@Autowired |
|||
private TbClient tbClient; |
|||
@Autowired |
|||
private WsClientFactory wsClientFactory; |
|||
@Autowired |
|||
private TbStopWatch stopWatch; |
|||
@Autowired |
|||
private MonitoringReporter reporter; |
|||
@Autowired |
|||
protected ApplicationContext applicationContext; |
|||
|
|||
@PostConstruct |
|||
private void init() { |
|||
tbClient.logIn(); |
|||
configs.forEach(config -> { |
|||
config.getTargets().forEach(target -> { |
|||
BaseHealthChecker<C, T> healthChecker = (BaseHealthChecker<C, T>) createHealthChecker(config, target); |
|||
log.info("Initializing {}", healthChecker.getClass().getSimpleName()); |
|||
healthChecker.initialize(tbClient); |
|||
devices.add(target.getDeviceId()); |
|||
healthCheckers.add(healthChecker); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public final void runChecks() { |
|||
if (healthCheckers.isEmpty()) { |
|||
return; |
|||
} |
|||
try { |
|||
log.info("Starting {}", getName()); |
|||
stopWatch.start(); |
|||
String accessToken = tbClient.logIn(); |
|||
reporter.reportLatency(Latencies.LOG_IN, stopWatch.getTime()); |
|||
|
|||
try (WsClient wsClient = wsClientFactory.createClient(accessToken)) { |
|||
wsClient.subscribeForTelemetry(devices, TransportHealthChecker.TEST_TELEMETRY_KEY).waitForReply(); |
|||
|
|||
for (BaseHealthChecker<C, T> healthChecker : healthCheckers) { |
|||
healthChecker.check(wsClient); |
|||
} |
|||
} |
|||
reporter.reportLatencies(tbClient); |
|||
log.debug("Finished {}", getName()); |
|||
} catch (Throwable error) { |
|||
try { |
|||
reporter.serviceFailure(MonitoredServiceKey.GENERAL, error); |
|||
} catch (Throwable reportError) { |
|||
log.error("Error occurred during service failure reporting", reportError); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected abstract BaseHealthChecker<?, ?> createHealthChecker(C config, T target); |
|||
|
|||
protected abstract String getName(); |
|||
|
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
/** |
|||
* 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.monitoring.service.transport; |
|||
|
|||
import com.fasterxml.jackson.databind.node.TextNode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.RandomStringUtils; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.monitoring.client.TbClient; |
|||
import org.thingsboard.monitoring.config.transport.DeviceConfig; |
|||
import org.thingsboard.monitoring.config.transport.TransportInfo; |
|||
import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig; |
|||
import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; |
|||
import org.thingsboard.monitoring.config.transport.TransportType; |
|||
import org.thingsboard.monitoring.service.BaseHealthChecker; |
|||
import org.thingsboard.monitoring.util.ResourceUtils; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.TbResource; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; |
|||
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; |
|||
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.data.DeviceData; |
|||
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|||
|
|||
@Slf4j |
|||
public abstract class TransportHealthChecker<C extends TransportMonitoringConfig> extends BaseHealthChecker<C, TransportMonitoringTarget> { |
|||
|
|||
private static final String DEFAULT_DEVICE_NAME = "[Monitoring] %s transport (%s)"; |
|||
private static final String DEFAULT_PROFILE_NAME = "[Monitoring] %s"; |
|||
|
|||
public TransportHealthChecker(C config, TransportMonitoringTarget target) { |
|||
super(config, target); |
|||
} |
|||
|
|||
@Override |
|||
protected void initialize(TbClient tbClient) { |
|||
String deviceName = String.format(DEFAULT_DEVICE_NAME, config.getTransportType(), target.getBaseUrl()); |
|||
Device device = tbClient.getTenantDevice(deviceName) |
|||
.orElseGet(() -> { |
|||
log.info("Creating new device '{}'", deviceName); |
|||
return createDevice(config.getTransportType(), deviceName, tbClient); |
|||
}); |
|||
DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId()) |
|||
.orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId())); |
|||
|
|||
DeviceConfig deviceConfig = new DeviceConfig(); |
|||
deviceConfig.setId(device.getId().toString()); |
|||
deviceConfig.setName(deviceName); |
|||
deviceConfig.setCredentials(credentials); |
|||
target.setDevice(deviceConfig); |
|||
} |
|||
|
|||
@Override |
|||
protected String createTestPayload(String testValue) { |
|||
return JacksonUtil.newObjectNode().set(TEST_TELEMETRY_KEY, new TextNode(testValue)).toString(); |
|||
} |
|||
|
|||
@Override |
|||
protected Object getInfo() { |
|||
return new TransportInfo(getTransportType(), target.getBaseUrl()); |
|||
} |
|||
|
|||
@Override |
|||
protected String getKey() { |
|||
return getTransportType().name().toLowerCase() + "Transport"; |
|||
} |
|||
|
|||
protected abstract TransportType getTransportType(); |
|||
|
|||
|
|||
private Device createDevice(TransportType transportType, String name, TbClient tbClient) { |
|||
Device device = new Device(); |
|||
device.setName(name); |
|||
|
|||
DeviceCredentials credentials = new DeviceCredentials(); |
|||
credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20)); |
|||
|
|||
DeviceData deviceData = new DeviceData(); |
|||
deviceData.setConfiguration(new DefaultDeviceConfiguration()); |
|||
if (transportType != TransportType.LWM2M) { |
|||
device.setType("default"); |
|||
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); |
|||
credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
|||
} else { |
|||
tbClient.getResources(new PageLink(1, 0, "lwm2m monitoring")).getData() |
|||
.stream().findFirst() |
|||
.orElseGet(() -> { |
|||
TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); |
|||
log.info("Creating LwM2M resource"); |
|||
return tbClient.saveResource(newResource); |
|||
}); |
|||
String profileName = String.format(DEFAULT_PROFILE_NAME, transportType); |
|||
DeviceProfile profile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData() |
|||
.stream().findFirst() |
|||
.orElseGet(() -> { |
|||
DeviceProfile newProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class); |
|||
newProfile.setName(profileName); |
|||
log.info("Creating LwM2M device profile"); |
|||
return tbClient.saveDeviceProfile(newProfile); |
|||
}); |
|||
device.setType(profileName); |
|||
device.setDeviceProfileId(profile.getId()); |
|||
deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); |
|||
|
|||
credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); |
|||
LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials(); |
|||
NoSecClientCredential client = new NoSecClientCredential(); |
|||
client.setEndpoint(credentials.getCredentialsId()); |
|||
lwm2mCreds.setClient(client); |
|||
LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials(); |
|||
bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential()); |
|||
bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential()); |
|||
lwm2mCreds.setBootstrap(bootstrap); |
|||
credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds)); |
|||
} |
|||
return tbClient.saveDeviceWithCredentials(device, credentials).get(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* 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.monitoring.service.transport; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig; |
|||
import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; |
|||
import org.thingsboard.monitoring.service.BaseHealthChecker; |
|||
import org.thingsboard.monitoring.service.BaseMonitoringService; |
|||
|
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public final class TransportsMonitoringService extends BaseMonitoringService<TransportMonitoringConfig, TransportMonitoringTarget> { |
|||
|
|||
@Override |
|||
protected BaseHealthChecker<?, ?> createHealthChecker(TransportMonitoringConfig config, TransportMonitoringTarget target) { |
|||
return applicationContext.getBean(config.getTransportType().getServiceClass(), config, target); |
|||
} |
|||
|
|||
@Override |
|||
protected String getName() { |
|||
return "transports check"; |
|||
} |
|||
|
|||
} |
|||
@ -1,198 +0,0 @@ |
|||
/** |
|||
* 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.monitoring.transport; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.RandomStringUtils; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
|||
import org.springframework.context.ApplicationContext; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.monitoring.client.TbClient; |
|||
import org.thingsboard.monitoring.client.WsClient; |
|||
import org.thingsboard.monitoring.client.WsClientFactory; |
|||
import org.thingsboard.monitoring.config.DeviceConfig; |
|||
import org.thingsboard.monitoring.config.MonitoringTargetConfig; |
|||
import org.thingsboard.monitoring.config.TransportType; |
|||
import org.thingsboard.monitoring.config.service.TransportMonitoringConfig; |
|||
import org.thingsboard.monitoring.data.Latencies; |
|||
import org.thingsboard.monitoring.data.MonitoredServiceKey; |
|||
import org.thingsboard.monitoring.service.MonitoringReporter; |
|||
import org.thingsboard.monitoring.util.ResourceUtils; |
|||
import org.thingsboard.monitoring.util.TbStopWatch; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.TbResource; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; |
|||
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; |
|||
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.data.DeviceData; |
|||
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public final class TransportMonitoringService { |
|||
|
|||
private final List<TransportMonitoringConfig> configs; |
|||
private final List<TransportHealthChecker<?>> transportHealthCheckers = new LinkedList<>(); |
|||
private final List<UUID> devices = new LinkedList<>(); |
|||
|
|||
private final TbClient tbClient; |
|||
private final WsClientFactory wsClientFactory; |
|||
private final TbStopWatch stopWatch; |
|||
private final MonitoringReporter reporter; |
|||
private final ApplicationContext applicationContext; |
|||
private ScheduledExecutorService scheduler; |
|||
@Value("${monitoring.transports.monitoring_rate_ms}") |
|||
private int monitoringRateMs; |
|||
|
|||
@PostConstruct |
|||
private void init() { |
|||
configs.forEach(config -> { |
|||
config.getTargets().stream() |
|||
.filter(target -> StringUtils.isNotBlank(target.getBaseUrl())) |
|||
.peek(target -> checkMonitoringTarget(config, target, tbClient)) |
|||
.forEach(target -> { |
|||
TransportHealthChecker<?> transportHealthChecker = applicationContext.getBean(config.getTransportType().getServiceClass(), config, target); |
|||
transportHealthCheckers.add(transportHealthChecker); |
|||
devices.add(target.getDevice().getId()); |
|||
}); |
|||
}); |
|||
scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("monitoring-executor")); |
|||
} |
|||
|
|||
@EventListener(ApplicationReadyEvent.class) |
|||
public void startMonitoring() { |
|||
scheduler.scheduleWithFixedDelay(() -> { |
|||
try { |
|||
log.debug("Starting transports check"); |
|||
stopWatch.start(); |
|||
String accessToken = tbClient.logIn(); |
|||
reporter.reportLatency(Latencies.LOG_IN, stopWatch.getTime()); |
|||
|
|||
try (WsClient wsClient = wsClientFactory.createClient(accessToken)) { |
|||
wsClient.subscribeForTelemetry(devices, TransportHealthChecker.TEST_TELEMETRY_KEY).waitForReply(); |
|||
|
|||
for (TransportHealthChecker<?> transportHealthChecker : transportHealthCheckers) { |
|||
transportHealthChecker.check(wsClient); |
|||
} |
|||
} |
|||
reporter.reportLatencies(tbClient); |
|||
log.debug("Finished transports check"); |
|||
} catch (Throwable error) { |
|||
try { |
|||
reporter.serviceFailure(MonitoredServiceKey.GENERAL, error); |
|||
} catch (Throwable reportError) { |
|||
log.error("Error occurred during service failure reporting", reportError); |
|||
} |
|||
} |
|||
}, 0, monitoringRateMs, TimeUnit.MILLISECONDS); |
|||
} |
|||
|
|||
private void checkMonitoringTarget(TransportMonitoringConfig config, MonitoringTargetConfig target, TbClient tbClient) { |
|||
DeviceConfig deviceConfig = target.getDevice(); |
|||
tbClient.logIn(); |
|||
|
|||
DeviceId deviceId; |
|||
if (deviceConfig == null || deviceConfig.getId() == null) { |
|||
String deviceName = String.format("[%s] Monitoring device (%s)", config.getTransportType(), target.getBaseUrl()); |
|||
Device device = tbClient.getTenantDevice(deviceName) |
|||
.orElseGet(() -> { |
|||
log.info("Creating new device '{}'", deviceName); |
|||
return createDevice(config.getTransportType(), deviceName, tbClient); |
|||
}); |
|||
deviceId = device.getId(); |
|||
target.getDevice().setId(deviceId.toString()); |
|||
} else { |
|||
deviceId = new DeviceId(deviceConfig.getId()); |
|||
} |
|||
|
|||
log.info("Using device {} for {} monitoring", deviceId, config.getTransportType()); |
|||
DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(deviceId) |
|||
.orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + deviceId)); |
|||
target.getDevice().setCredentials(credentials); |
|||
} |
|||
|
|||
private Device createDevice(TransportType transportType, String name, TbClient tbClient) { |
|||
Device device = new Device(); |
|||
device.setName(name); |
|||
|
|||
DeviceCredentials credentials = new DeviceCredentials(); |
|||
credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20)); |
|||
|
|||
DeviceData deviceData = new DeviceData(); |
|||
deviceData.setConfiguration(new DefaultDeviceConfiguration()); |
|||
if (transportType != TransportType.LWM2M) { |
|||
device.setType("default"); |
|||
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); |
|||
credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
|||
} else { |
|||
tbClient.getResources(new PageLink(1, 0, "lwm2m monitoring")).getData() |
|||
.stream().findFirst() |
|||
.orElseGet(() -> { |
|||
TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); |
|||
log.info("Creating LwM2M resource"); |
|||
return tbClient.saveResource(newResource); |
|||
}); |
|||
String profileName = "LwM2M Monitoring"; |
|||
DeviceProfile profile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData() |
|||
.stream().findFirst() |
|||
.orElseGet(() -> { |
|||
DeviceProfile newProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class); |
|||
newProfile.setName(profileName); |
|||
log.info("Creating LwM2M device profile"); |
|||
return tbClient.saveDeviceProfile(newProfile); |
|||
}); |
|||
device.setType(profileName); |
|||
device.setDeviceProfileId(profile.getId()); |
|||
deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); |
|||
|
|||
credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); |
|||
LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials(); |
|||
NoSecClientCredential client = new NoSecClientCredential(); |
|||
client.setEndpoint(credentials.getCredentialsId()); |
|||
lwm2mCreds.setClient(client); |
|||
LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials(); |
|||
bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential()); |
|||
bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential()); |
|||
lwm2mCreds.setBootstrap(bootstrap); |
|||
credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds)); |
|||
} |
|||
return tbClient.saveDeviceWithCredentials(device, credentials).get(); |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue