49 changed files with 1801 additions and 39 deletions
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.dao.queue; |
|||
|
|||
import org.thingsboard.server.common.data.TenantProfile; |
|||
import org.thingsboard.server.common.data.id.QueueId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.queue.Queue; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface QueueService { |
|||
|
|||
Queue saveQueue(Queue queue); |
|||
|
|||
void deleteQueue(TenantId tenantId, QueueId queueId); |
|||
|
|||
List<Queue> findQueuesByTenantId(TenantId tenantId); |
|||
|
|||
PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink); |
|||
|
|||
List<Queue> findAllMainQueues(); |
|||
|
|||
List<Queue> findAllQueues(); |
|||
|
|||
Queue findQueueById(TenantId tenantId, QueueId queueId); |
|||
|
|||
Queue findQueueByTenantIdAndName(TenantId tenantId, String name); |
|||
|
|||
Queue createDefaultMainQueue(TenantProfile tenantProfile, TenantId tenantId); |
|||
|
|||
void deleteQueuesByTenantId(TenantId tenantId); |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.id; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public class QueueId extends UUIDBased implements EntityId { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@JsonCreator |
|||
public QueueId(@JsonProperty("id") UUID id) { |
|||
super(id); |
|||
} |
|||
|
|||
public static QueueId fromString(String queueId) { |
|||
return new QueueId(UUID.fromString(queueId)); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.QUEUE; |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.queue; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class ProcessingStrategy { |
|||
private ProcessingStrategyType type; |
|||
private int retries; |
|||
private double failurePercentage; |
|||
private long pauseBetweenRetries; |
|||
private long maxPauseBetweenRetries; |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Copyright © 2016-2020 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.queue; |
|||
|
|||
public enum ProcessingStrategyType { |
|||
SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.queue; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.HasTenantId; |
|||
import org.thingsboard.server.common.data.id.QueueId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
@Data |
|||
public class Queue extends BaseData<QueueId> implements HasName, HasTenantId { |
|||
private TenantId tenantId; |
|||
private String name; |
|||
private String topic; |
|||
private int pollInterval; |
|||
private int partitions; |
|||
private long packProcessingTimeout; |
|||
private SubmitStrategy submitStrategy; |
|||
private ProcessingStrategy processingStrategy; |
|||
|
|||
public Queue() { |
|||
} |
|||
|
|||
public Queue(QueueId id) { |
|||
super(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.queue; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class SubmitStrategy { |
|||
private SubmitStrategyType type; |
|||
private int batchSize; |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.queue; |
|||
|
|||
public enum SubmitStrategyType { |
|||
BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.dao.model.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.hibernate.annotations.Type; |
|||
import org.hibernate.annotations.TypeDef; |
|||
import org.thingsboard.server.common.data.id.QueueId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.queue.ProcessingStrategy; |
|||
import org.thingsboard.server.common.data.queue.Queue; |
|||
import org.thingsboard.server.common.data.queue.SubmitStrategy; |
|||
import org.thingsboard.server.dao.DaoUtil; |
|||
import org.thingsboard.server.dao.model.BaseSqlEntity; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.util.mapping.JsonStringType; |
|||
|
|||
import javax.persistence.Column; |
|||
import javax.persistence.Entity; |
|||
import javax.persistence.Table; |
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Entity |
|||
@TypeDef(name = "json", typeClass = JsonStringType.class) |
|||
@Table(name = ModelConstants.QUEUE_COLUMN_FAMILY_NAME) |
|||
public class QueueEntity extends BaseSqlEntity<Queue> { |
|||
|
|||
private static final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
@Column(name = ModelConstants.QUEUE_TENANT_ID_PROPERTY) |
|||
private UUID tenantId; |
|||
|
|||
@Column(name = ModelConstants.QUEUE_NAME_PROPERTY) |
|||
private String name; |
|||
|
|||
@Column(name = ModelConstants.QUEUE_TOPIC_PROPERTY) |
|||
private String topic; |
|||
@Column(name = ModelConstants.QUEUE_POLL_INTERVAL_PROPERTY) |
|||
private int pollInterval; |
|||
|
|||
@Column(name = ModelConstants.QUEUE_PARTITIONS_PROPERTY) |
|||
private int partitions; |
|||
|
|||
@Column(name = ModelConstants.QUEUE_PACK_PROCESSING_TIMEOUT_PROPERTY) |
|||
private long packProcessingTimeout; |
|||
|
|||
@Type(type = "json") |
|||
@Column(name = ModelConstants.QUEUE_SUBMIT_STRATEGY_PROPERTY) |
|||
private JsonNode submitStrategy; |
|||
|
|||
@Type(type = "json") |
|||
@Column(name = ModelConstants.QUEUE_PROCESSING_STRATEGY_PROPERTY) |
|||
private JsonNode processingStrategy; |
|||
|
|||
public QueueEntity() { |
|||
} |
|||
|
|||
public QueueEntity(Queue queue) { |
|||
if (queue.getId() != null) { |
|||
this.setId(queue.getId().getId()); |
|||
} |
|||
this.createdTime = queue.getCreatedTime(); |
|||
this.tenantId = DaoUtil.getId(queue.getTenantId()); |
|||
this.name = queue.getName(); |
|||
this.topic = queue.getTopic(); |
|||
this.pollInterval = queue.getPollInterval(); |
|||
this.partitions = queue.getPartitions(); |
|||
this.packProcessingTimeout = queue.getPackProcessingTimeout(); |
|||
this.submitStrategy = mapper.valueToTree(queue.getSubmitStrategy()); |
|||
this.processingStrategy = mapper.valueToTree(queue.getProcessingStrategy()); |
|||
} |
|||
|
|||
@Override |
|||
public Queue toData() { |
|||
Queue queue = new Queue(new QueueId(getUuid())); |
|||
queue.setCreatedTime(createdTime); |
|||
queue.setTenantId(new TenantId(tenantId)); |
|||
queue.setName(name); |
|||
queue.setTopic(topic); |
|||
queue.setPollInterval(pollInterval); |
|||
queue.setPartitions(partitions); |
|||
queue.setPackProcessingTimeout(packProcessingTimeout); |
|||
queue.setSubmitStrategy(mapper.convertValue(this.submitStrategy, SubmitStrategy.class)); |
|||
queue.setProcessingStrategy(mapper.convertValue(this.processingStrategy, ProcessingStrategy.class)); |
|||
return queue; |
|||
} |
|||
} |
|||
@ -0,0 +1,333 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.dao.queue; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
import org.thingsboard.server.common.data.TenantProfile; |
|||
import org.thingsboard.server.common.data.id.QueueId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.queue.ProcessingStrategy; |
|||
import org.thingsboard.server.common.data.queue.ProcessingStrategyType; |
|||
import org.thingsboard.server.common.data.queue.Queue; |
|||
import org.thingsboard.server.common.data.queue.SubmitStrategy; |
|||
import org.thingsboard.server.common.data.queue.SubmitStrategyType; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.dao.entity.AbstractEntityService; |
|||
import org.thingsboard.server.dao.exception.DataValidationException; |
|||
import org.thingsboard.server.dao.service.DataValidator; |
|||
import org.thingsboard.server.dao.service.PaginatedRemover; |
|||
import org.thingsboard.server.dao.service.Validator; |
|||
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
|||
import org.thingsboard.server.dao.tenant.TenantDao; |
|||
import org.thingsboard.server.queue.TbQueueAdmin; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
public class BaseQueueService extends AbstractEntityService implements QueueService { |
|||
|
|||
@Autowired |
|||
private QueueDao queueDao; |
|||
|
|||
@Autowired |
|||
private TenantDao tenantDao; |
|||
|
|||
@Autowired |
|||
private TbTenantProfileCache tenantProfileCache; |
|||
|
|||
@Autowired(required = false) |
|||
private TbQueueAdmin tbQueueAdmin; |
|||
|
|||
// @Autowired(required = false)
|
|||
// private TbQueueClusterService queueClusterService;
|
|||
|
|||
// @Autowired
|
|||
// private QueueStatsService queueStatsService;
|
|||
|
|||
@Override |
|||
@Transactional |
|||
public Queue saveQueue(Queue queue) { |
|||
log.trace("Executing createOrUpdateQueue [{}]", queue); |
|||
queueValidator.validate(queue, Queue::getTenantId); |
|||
Queue savedQueue; |
|||
if (queue.getId() == null) { |
|||
savedQueue = createQueue(queue); |
|||
} else { |
|||
savedQueue = updateQueue(queue); |
|||
} |
|||
|
|||
// if (queueClusterService != null) {
|
|||
// queueClusterService.onQueueChange(savedQueue, null);
|
|||
// }
|
|||
return savedQueue; |
|||
} |
|||
|
|||
private Queue createQueue(Queue queue) { |
|||
Queue createdQueue = queueDao.save(queue.getTenantId(), queue); |
|||
if (tbQueueAdmin != null) { |
|||
for (int i = 0; i < queue.getPartitions(); i++) { |
|||
tbQueueAdmin.createTopicIfNotExists(new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName()); |
|||
} |
|||
} |
|||
return createdQueue; |
|||
} |
|||
|
|||
private Queue updateQueue(Queue queue) { |
|||
Queue oldQueue = queueDao.findById(queue.getTenantId(), queue.getUuidId()); |
|||
Queue updatedQueue = queueDao.save(queue.getTenantId(), queue); |
|||
|
|||
int oldPartitions = oldQueue.getPartitions(); |
|||
int currentPartitions = queue.getPartitions(); |
|||
|
|||
//TODO: 3.2 remove if partitions can't be deleted.
|
|||
// if (currentPartitions != oldPartitions && tbQueueAdmin != null) {
|
|||
// queueClusterService.onQueueDelete(queue, null);
|
|||
// if (currentPartitions > oldPartitions) {
|
|||
// log.info("Added [{}] new partitions to [{}] queue", currentPartitions - oldPartitions, queue.getName());
|
|||
// for (int i = oldPartitions; i < currentPartitions; i++) {
|
|||
// tbQueueAdmin.createTopicIfNotExists(new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
|
|||
// }
|
|||
// } else {
|
|||
// log.info("Removed [{}] partitions from [{}] queue", oldPartitions - currentPartitions, queue.getName());
|
|||
// for (int i = currentPartitions; i < oldPartitions; i++) {
|
|||
// tbQueueAdmin.deleteTopic(new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
|
|||
return updatedQueue; |
|||
} |
|||
|
|||
@Override |
|||
@Transactional |
|||
public void deleteQueue(TenantId tenantId, QueueId queueId) { |
|||
log.trace("Executing deleteQueue, queueId: [{}]", queueId); |
|||
Queue queue = findQueueById(tenantId, queueId); |
|||
// if (queueClusterService != null) {
|
|||
// queueClusterService.onQueueDelete(queue, null);
|
|||
// }
|
|||
// queueStatsService.deleteQueueStatsByQueueId(tenantId, queueId);
|
|||
boolean result = queueDao.removeById(tenantId, queueId.getId()); |
|||
if (result && tbQueueAdmin != null) { |
|||
for (int i = 0; i < queue.getPartitions(); i++) { |
|||
String fullTopicName = new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName(); |
|||
log.debug("Deleting queue [{}]", fullTopicName); |
|||
try { |
|||
tbQueueAdmin.deleteTopic(fullTopicName); |
|||
} catch (Exception e) { |
|||
log.error("Failed to delete queue [{}]", fullTopicName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public List<Queue> findQueuesByTenantId(TenantId tenantId) { |
|||
log.trace("Executing findQueues, tenantId: [{}]", tenantId); |
|||
return queueDao.findAllByTenantId(getSystemOrIsolatedTenantId(tenantId)); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink) { |
|||
log.trace("Executing findQueues pageLink [{}]", pageLink); |
|||
Validator.validatePageLink(pageLink); |
|||
return queueDao.findQueuesByTenantId(getSystemOrIsolatedTenantId(tenantId), pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public List<Queue> findAllMainQueues() { |
|||
log.trace("Executing findAllMainQueues"); |
|||
return queueDao.findAllMainQueues(); |
|||
} |
|||
|
|||
@Override |
|||
public List<Queue> findAllQueues() { |
|||
log.trace("Executing findAllQueues"); |
|||
return queueDao.findAllQueues(); |
|||
} |
|||
|
|||
@Override |
|||
public Queue findQueueById(TenantId tenantId, QueueId queueId) { |
|||
log.trace("Executing findQueueById, queueId: [{}]", queueId); |
|||
return queueDao.findById(tenantId, queueId.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public Queue findQueueByTenantIdAndName(TenantId tenantId, String queueName) { |
|||
log.trace("Executing findQueueByTenantIdAndName, tenantId: [{}] queueName: [{}]", tenantId, queueName); |
|||
return queueDao.findQueueByTenantIdAndName(getSystemOrIsolatedTenantId(tenantId), queueName); |
|||
} |
|||
|
|||
@Override |
|||
public void deleteQueuesByTenantId(TenantId tenantId) { |
|||
Validator.validateId(tenantId, "Incorrect tenant id for delete queues request."); |
|||
tenantQueuesRemover.removeEntities(tenantId, tenantId); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional |
|||
public Queue createDefaultMainQueue(TenantProfile tenantProfile, TenantId tenantId) { |
|||
Queue mainQueue = new Queue(); |
|||
mainQueue.setTenantId(tenantId); |
|||
mainQueue.setName("Main"); |
|||
mainQueue.setTopic("tb_rule_engine.main"); |
|||
mainQueue.setPollInterval(25); |
|||
mainQueue.setPartitions(Math.max(tenantProfile.getMaxNumberOfPartitionsPerQueue(), 1)); |
|||
mainQueue.setPackProcessingTimeout(60000); |
|||
SubmitStrategy mainQueueSubmitStrategy = new SubmitStrategy(); |
|||
mainQueueSubmitStrategy.setType(SubmitStrategyType.BURST); |
|||
mainQueueSubmitStrategy.setBatchSize(1000); |
|||
mainQueue.setSubmitStrategy(mainQueueSubmitStrategy); |
|||
ProcessingStrategy mainQueueProcessingStrategy = new ProcessingStrategy(); |
|||
mainQueueProcessingStrategy.setType(ProcessingStrategyType.SKIP_ALL_FAILURES); |
|||
mainQueueProcessingStrategy.setRetries(3); |
|||
mainQueueProcessingStrategy.setFailurePercentage(0); |
|||
mainQueueProcessingStrategy.setPauseBetweenRetries(3); |
|||
mainQueueProcessingStrategy.setMaxPauseBetweenRetries(3); |
|||
mainQueue.setProcessingStrategy(mainQueueProcessingStrategy); |
|||
return saveQueue(mainQueue); |
|||
} |
|||
|
|||
private DataValidator<Queue> queueValidator = |
|||
new DataValidator<>() { |
|||
|
|||
@Override |
|||
protected void validateCreate(TenantId tenantId, Queue queue) { |
|||
if (queueDao.findQueueByTenantIdAndTopic(tenantId, queue.getTopic()) != null) { |
|||
throw new DataValidationException(String.format("Queue with topic: %s already exists!", queue.getTopic())); |
|||
} |
|||
if (queueDao.findQueueByTenantIdAndName(tenantId, queue.getName()) != null) { |
|||
throw new DataValidationException(String.format("Queue with name: %s already exists!", queue.getName())); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void validateUpdate(TenantId tenantId, Queue queue) { |
|||
Queue foundQueue = queueDao.findById(tenantId, queue.getUuidId()); |
|||
if (queueDao.findById(tenantId, queue.getUuidId()) == null) { |
|||
throw new DataValidationException(String.format("Queue with id: %s does not exists!", queue.getId())); |
|||
} |
|||
if (!foundQueue.getName().equals(queue.getName())) { |
|||
throw new DataValidationException("Queue name can't be changed!"); |
|||
} |
|||
if (!foundQueue.getTopic().equals(queue.getTopic())) { |
|||
throw new DataValidationException("Queue topic can't be changed!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void validateDataImpl(TenantId tenantId, Queue queue) { |
|||
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) { |
|||
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); |
|||
|
|||
if (!tenantProfile.isIsolatedTbRuleEngine()) { |
|||
throw new DataValidationException("Tenant should be isolated!"); |
|||
} |
|||
|
|||
if (queue.getId() == null) { |
|||
List<Queue> existingQueues = findQueuesByTenantId(tenantId); |
|||
if (existingQueues.size() >= tenantProfile.getMaxNumberOfQueues()) { |
|||
throw new DataValidationException("The limit for creating new queue has been exceeded!"); |
|||
} |
|||
} |
|||
|
|||
if (queue.getPartitions() > tenantProfile.getMaxNumberOfPartitionsPerQueue()) { |
|||
throw new DataValidationException(String.format("Queue partitions can't be more then %d", tenantProfile.getMaxNumberOfPartitionsPerQueue())); |
|||
} |
|||
} |
|||
|
|||
if (StringUtils.isEmpty(queue.getName())) { |
|||
throw new DataValidationException("Queue name should be specified!"); |
|||
} |
|||
if (StringUtils.isBlank(queue.getTopic())) { |
|||
throw new DataValidationException("Queue topic should be non empty and without spaces!"); |
|||
} |
|||
if (queue.getPollInterval() < 1) { |
|||
throw new DataValidationException("Queue poll interval should be more then 0!"); |
|||
} |
|||
if (queue.getPartitions() < 1) { |
|||
throw new DataValidationException("Queue partitions should be more then 0!"); |
|||
} |
|||
if (queue.getPackProcessingTimeout() < 1) { |
|||
throw new DataValidationException("Queue pack processing timeout should be more then 0!"); |
|||
} |
|||
|
|||
SubmitStrategy submitStrategy = queue.getSubmitStrategy(); |
|||
if (submitStrategy == null) { |
|||
throw new DataValidationException("Queue submit strategy can't be null!"); |
|||
} |
|||
if (submitStrategy.getType() == null) { |
|||
throw new DataValidationException("Queue submit strategy type can't be null!"); |
|||
} |
|||
if (submitStrategy.getType() == SubmitStrategyType.BATCH && submitStrategy.getBatchSize() < 1) { |
|||
throw new DataValidationException("Queue submit strategy batch size should be more then 0!"); |
|||
} |
|||
ProcessingStrategy processingStrategy = queue.getProcessingStrategy(); |
|||
if (processingStrategy == null) { |
|||
throw new DataValidationException("Queue processing strategy can't be null!"); |
|||
} |
|||
if (processingStrategy.getType() == null) { |
|||
throw new DataValidationException("Queue processing strategy type can't be null!"); |
|||
} |
|||
if (processingStrategy.getRetries() < 0) { |
|||
throw new DataValidationException("Queue processing strategy retries can't be less then 0!"); |
|||
} |
|||
if (processingStrategy.getFailurePercentage() < 0 || processingStrategy.getFailurePercentage() > 100) { |
|||
throw new DataValidationException("Queue processing strategy failure percentage should be in a range from 0 to 100!"); |
|||
} |
|||
if (processingStrategy.getPauseBetweenRetries() < 0) { |
|||
throw new DataValidationException("Queue processing strategy pause between retries can't be less then 0!"); |
|||
} |
|||
if (processingStrategy.getMaxPauseBetweenRetries() < processingStrategy.getPauseBetweenRetries()) { |
|||
throw new DataValidationException("Queue processing strategy MAX pause between retries can't be less then pause between retries!"); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
private PaginatedRemover<TenantId, Queue> tenantQueuesRemover = |
|||
new PaginatedRemover<>() { |
|||
|
|||
@Override |
|||
protected PageData<Queue> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { |
|||
return queueDao.findQueuesByTenantId(id, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
protected void removeEntity(TenantId tenantId, Queue entity) { |
|||
deleteQueue(tenantId, entity.getId()); |
|||
} |
|||
}; |
|||
|
|||
private TenantId getSystemOrIsolatedTenantId(TenantId tenantId) { |
|||
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) { |
|||
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); |
|||
if (tenantProfile.isIsolatedTbRuleEngine()) { |
|||
return tenantId; |
|||
} |
|||
} |
|||
|
|||
return TenantId.SYS_TENANT_ID; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.dao.queue; |
|||
|
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.queue.Queue; |
|||
import org.thingsboard.server.dao.Dao; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface QueueDao extends Dao<Queue> { |
|||
Queue findQueueByTenantIdAndTopic(TenantId tenantId, String topic); |
|||
|
|||
Queue findQueueByTenantIdAndName(TenantId tenantId, String name); |
|||
|
|||
List<Queue> findAllMainQueues(); |
|||
|
|||
List<Queue> findAllQueues(); |
|||
|
|||
List<Queue> findAllByTenantId(TenantId tenantId); |
|||
|
|||
PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink); |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.dao.sql.queue; |
|||
|
|||
import com.google.common.collect.Lists; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.repository.CrudRepository; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.queue.Queue; |
|||
import org.thingsboard.server.dao.DaoUtil; |
|||
import org.thingsboard.server.dao.model.sql.QueueEntity; |
|||
import org.thingsboard.server.dao.queue.QueueDao; |
|||
import org.thingsboard.server.dao.sql.JpaAbstractDao; |
|||
|
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
@Component |
|||
public class JpaQueueDao extends JpaAbstractDao<QueueEntity, Queue> implements QueueDao { |
|||
|
|||
@Autowired |
|||
private QueueRepository queueRepository; |
|||
|
|||
@Override |
|||
protected Class<QueueEntity> getEntityClass() { |
|||
return QueueEntity.class; |
|||
} |
|||
|
|||
@Override |
|||
protected CrudRepository<QueueEntity, UUID> getCrudRepository() { |
|||
return queueRepository; |
|||
} |
|||
|
|||
@Override |
|||
public Queue findQueueByTenantIdAndTopic(TenantId tenantId, String topic) { |
|||
return DaoUtil.getData(queueRepository.findByTenantIdAndTopic(tenantId.getId(), topic)); |
|||
} |
|||
|
|||
@Override |
|||
public Queue findQueueByTenantIdAndName(TenantId tenantId, String name) { |
|||
return DaoUtil.getData(queueRepository.findByTenantIdAndName(tenantId.getId(), name)); |
|||
} |
|||
|
|||
@Override |
|||
public List<Queue> findAllByTenantId(TenantId tenantId) { |
|||
List<QueueEntity> entities = queueRepository.findByTenantId(tenantId.getId()); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
@Override |
|||
public List<Queue> findAllMainQueues() { |
|||
List<QueueEntity> entities = Lists.newArrayList(queueRepository.findAllByName("Main")); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
@Override |
|||
public List<Queue> findAllQueues() { |
|||
List<QueueEntity> entities = Lists.newArrayList(queueRepository.findAll()); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink) { |
|||
return DaoUtil.toPageData(queueRepository |
|||
.findByTenantId(tenantId.getId(), Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.dao.sql.queue; |
|||
|
|||
import org.springframework.data.domain.Page; |
|||
import org.springframework.data.domain.Pageable; |
|||
import org.springframework.data.jpa.repository.Query; |
|||
import org.springframework.data.repository.CrudRepository; |
|||
import org.springframework.data.repository.query.Param; |
|||
import org.thingsboard.server.dao.model.sql.QueueEntity; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
public interface QueueRepository extends CrudRepository<QueueEntity, UUID> { |
|||
QueueEntity findByTenantIdAndTopic(UUID tenantId, String topic); |
|||
|
|||
QueueEntity findByTenantIdAndName(UUID tenantId, String name); |
|||
|
|||
List<QueueEntity> findByTenantId(UUID tenantId); |
|||
|
|||
@Query("SELECT q FROM QueueEntity q WHERE q.tenantId = :tenantId " + |
|||
"AND LOWER(q.name) LIKE LOWER(CONCAT(:textSearch, '%'))") |
|||
Page<QueueEntity> findByTenantId(@Param("tenantId") UUID tenantId, |
|||
@Param("textSearch") String textSearch, |
|||
Pageable pageable); |
|||
|
|||
List<QueueEntity> findAllByName(String name); |
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2022 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. |
|||
|
|||
--> |
|||
<div class="tb-details-buttons" fxLayout.xs="column"> |
|||
<button mat-raised-button color="primary" fxFlex.xs |
|||
[disabled]="(isLoading$ | async)" |
|||
(click)="onEntityAction($event, 'delete')" |
|||
[fxShow]="!hideDelete() && !isEdit"> |
|||
{{'queue.delete' | translate }} |
|||
</button> |
|||
</div> |
|||
|
|||
<div class="mat-padding" fxLayout="column"> |
|||
<form [formGroup]="entityForm"> |
|||
<fieldset [disabled]="(isLoading$ | async) || !isEdit"> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>admin.queue-name</mat-label> |
|||
<input matInput formControlName="name" required> |
|||
<mat-error *ngIf="entityForm.get('name').hasError('required')"> |
|||
{{ 'queue.name-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.poll-interval</mat-label> |
|||
<input type="number" matInput formControlName="pollInterval" required> |
|||
<mat-error *ngIf="entityForm.get('pollInterval').hasError('required')"> |
|||
{{ 'queue.poll-interval-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('pollInterval').hasError('min') && |
|||
!entityForm.get('pollInterval').hasError('required')"> |
|||
{{ 'queue.poll-interval-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.partitions</mat-label> |
|||
<input type="number" matInput formControlName="partitions" required> |
|||
<mat-error *ngIf="entityForm.get('partitions').hasError('required')"> |
|||
{{ 'queue.partitions-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('partitions').hasError('min') && |
|||
!entityForm.get('partitions').hasError('required')"> |
|||
{{ 'queue.partitions-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.processing-timeout</mat-label> |
|||
<input type="number" matInput formControlName="packProcessingTimeout" required> |
|||
<mat-error *ngIf="entityForm.get('packProcessingTimeout').hasError('required')"> |
|||
{{ 'queue.pack-processing-timeout-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('packProcessingTimeout').hasError('min') && |
|||
!entityForm.get('packProcessingTimeout').hasError('required')"> |
|||
{{ 'queue.pack-processing-timeout-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
|
|||
<div class="mat-accordion-container"> |
|||
|
|||
<mat-accordion [multi]="true"> |
|||
<mat-expansion-panel #panel1 hideToggle> |
|||
<mat-expansion-panel-header> |
|||
<mat-panel-title> |
|||
<mat-label translate>queue.submit-strategy</mat-label> |
|||
<mat-icon>{{panel1.expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
<div formGroupName="submitStrategy"> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.submit-strategy</mat-label> |
|||
<mat-select formControlName="type" required> |
|||
<mat-option *ngFor="let strategy of submitStrategies" [value]="strategy"> |
|||
{{ strategy }} |
|||
</mat-option> |
|||
</mat-select> |
|||
<mat-error *ngIf="entityForm.get('submitStrategy.type').hasError('required')"> |
|||
{{ 'queue.submit-strategy-type-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block" *ngIf="hideBatchSize"> |
|||
<mat-label translate>queue.batch-size</mat-label> |
|||
<input type="number" matInput formControlName="batchSize" required> |
|||
<mat-error *ngIf="entityForm.get('submitStrategy.batchSize').hasError('required')"> |
|||
{{ 'queue.batch-size-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('submitStrategy.batchSize').hasError('min') && |
|||
!entityForm.get('submitStrategy.batchSize').hasError('required')"> |
|||
{{ 'queue.batch-size-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</div> |
|||
</mat-expansion-panel> |
|||
<mat-expansion-panel #panel2 hideToggle> |
|||
<mat-expansion-panel-header> |
|||
<mat-panel-title> |
|||
<mat-label translate>queue.processing-strategy</mat-label> |
|||
<mat-icon>{{panel2.expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
|
|||
<div formGroupName="processingStrategy"> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.processing-strategy</mat-label> |
|||
<mat-select formControlName="type" required> |
|||
<mat-option *ngFor="let strategy of processingStrategies" [value]="strategy"> |
|||
{{ strategy }} |
|||
</mat-option> |
|||
</mat-select> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.type').hasError('required')"> |
|||
{{ 'queue.processing-strategy-type-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.retries</mat-label> |
|||
<input type="number" matInput formControlName="retries" required> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.retries').hasError('required')"> |
|||
{{ 'queue.retries-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.retries').hasError('min') && |
|||
!entityForm.get('processingStrategy.retries').hasError('required')"> |
|||
{{ 'queue.retries-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.failure-percentage</mat-label> |
|||
<input type="number" matInput formControlName="failurePercentage" required> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.failurePercentage').hasError('required')"> |
|||
{{ 'queue.failure-percentage-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.failurePercentage').hasError('min') && |
|||
!entityForm.get('processingStrategy.failurePercentage').hasError('required') && |
|||
!entityForm.get('processingStrategy.failurePercentage').hasError('max')"> |
|||
{{ 'queue.failure-percentage-min-value' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.failurePercentage').hasError('max') && |
|||
!entityForm.get('processingStrategy.failurePercentage').hasError('required') && |
|||
!entityForm.get('processingStrategy.failurePercentage').hasError('min')"> |
|||
{{ 'queue.failure-percentage-max-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.pause-between-retries</mat-label> |
|||
<input type="number" matInput formControlName="pauseBetweenRetries" required> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.pauseBetweenRetries').hasError('required')"> |
|||
{{ 'queue.pause-between-retries-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.pauseBetweenRetries').hasError('min') && |
|||
!entityForm.get('processingStrategy.pauseBetweenRetries').hasError('required')"> |
|||
{{ 'queue.pause-between-retries-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>queue.max-pause-between-retries</mat-label> |
|||
<input type="number" matInput formControlName="maxPauseBetweenRetries" required> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.maxPauseBetweenRetries').hasError('required')"> |
|||
{{ 'queue.max-pause-between-retries-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('processingStrategy.maxPauseBetweenRetries').hasError('min') && |
|||
!entityForm.get('processingStrategy.maxPauseBetweenRetries').hasError('required')"> |
|||
{{ 'queue.max-pause-between-retries-min-value' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</div> |
|||
</mat-expansion-panel> |
|||
</mat-accordion> |
|||
</div> |
|||
</fieldset> |
|||
</form> |
|||
</div> |
|||
@ -0,0 +1,59 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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. |
|||
*/ |
|||
:host ::ng-deep { |
|||
|
|||
.mat-expansion-panel:not([class*='mat-elevation-z']) { |
|||
box-shadow: none; |
|||
} |
|||
|
|||
.mat-accordion-container { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.mat-expansion-panel { |
|||
|
|||
&:hover { |
|||
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 5px 5px -3px rgba(0, 0, 0, 0.2); |
|||
} |
|||
|
|||
&-header { |
|||
height: 56px; |
|||
border: 1px solid #f2f2f2; |
|||
|
|||
&-title { |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
&.mat-expanded { |
|||
border: none; |
|||
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 5px 5px -3px rgba(0, 0, 0, 0.2); |
|||
|
|||
.mat-expansion-panel { |
|||
|
|||
&-content { |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
&-body { |
|||
padding-bottom: 10px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
///
|
|||
/// Copyright © 2016-2022 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.
|
|||
///
|
|||
|
|||
import { ChangeDetectorRef, Component, Inject } from '@angular/core'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
import { EntityComponent } from '@home/components/entity/entity.component'; |
|||
import { QueueInfo, QueueProcessingStrategyTypes, QueueSubmitStrategyTypes } from '@shared/models/queue.models'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
|||
import set = Reflect.set; |
|||
import { distinctUntilChanged } from 'rxjs/operators'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-queue', |
|||
templateUrl: './queue.component.html', |
|||
styleUrls: ['./queue.component.scss'] |
|||
}) |
|||
export class QueueComponent extends EntityComponent<QueueInfo> { |
|||
entityForm: FormGroup; |
|||
|
|||
entityType = EntityType; |
|||
submitStrategies: string[] = []; |
|||
processingStrategies: string[] = []; |
|||
|
|||
QueueSubmitStrategyTypes = QueueSubmitStrategyTypes; |
|||
hideBatchSize: boolean = false; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected translate: TranslateService, |
|||
@Inject('entity') protected entityValue: QueueInfo, |
|||
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<QueueInfo>, |
|||
protected cd: ChangeDetectorRef, |
|||
public fb: FormBuilder) { |
|||
super(store, fb, entityValue, entitiesTableConfigValue, cd); |
|||
this.submitStrategies = Object.values(QueueSubmitStrategyTypes); |
|||
this.processingStrategies = Object.values(QueueProcessingStrategyTypes); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
super.ngOnInit(); |
|||
this.entityForm.get('submitStrategy').get('type').valueChanges.subscribe(() => { |
|||
this.submitStrategyTypeChanged(); |
|||
}); |
|||
} |
|||
|
|||
buildForm(entity: QueueInfo): FormGroup { |
|||
return this.fb.group( |
|||
{ |
|||
name: [entity ? entity.name : '', [Validators.required]], |
|||
pollInterval: [ |
|||
entity && entity.pollInterval ? entity.pollInterval : 25, |
|||
[Validators.min(1), Validators.required] |
|||
], |
|||
partitions: [ |
|||
entity && entity.partitions ? entity.partitions : 10, |
|||
[Validators.min(1), Validators.required] |
|||
], |
|||
packProcessingTimeout: [ |
|||
entity && entity.packProcessingTimeout ? entity.packProcessingTimeout : 2000, |
|||
[Validators.min(1), Validators.required] |
|||
], |
|||
submitStrategy: this.fb.group({ |
|||
type: [entity ? entity.submitStrategy?.type : null, [Validators.required]], |
|||
batchSize: [ |
|||
entity && entity.submitStrategy?.batchSize ? entity.submitStrategy?.batchSize : 1000, |
|||
[Validators.min(1), Validators.required] |
|||
], |
|||
}), |
|||
processingStrategy: this.fb.group({ |
|||
type: [entity ? entity.processingStrategy?.type : null, [Validators.required]], |
|||
retries: [ |
|||
entity && entity.processingStrategy?.retries ? entity.processingStrategy?.retries : 3, |
|||
[Validators.min(0), Validators.required] |
|||
], |
|||
failurePercentage: [ |
|||
entity && entity.processingStrategy?.failurePercentage ? entity.processingStrategy?.failurePercentage : 0, |
|||
[Validators.min(0), Validators.required, Validators.max(100)] |
|||
], |
|||
pauseBetweenRetries: [ |
|||
entity && entity.processingStrategy?.pauseBetweenRetries ? entity.processingStrategy?.pauseBetweenRetries : 3, |
|||
[Validators.min(1), Validators.required] |
|||
], |
|||
maxPauseBetweenRetries: [ |
|||
entity && entity.processingStrategy?.maxPauseBetweenRetries ? entity.processingStrategy?.maxPauseBetweenRetries : 3, |
|||
[Validators.min(1), Validators.required] |
|||
], |
|||
}) |
|||
} |
|||
); |
|||
} |
|||
|
|||
hideDelete() { |
|||
if (this.entitiesTableConfig) { |
|||
return !this.entitiesTableConfig.deleteEnabled(this.entity); |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
updateForm(entity: QueueInfo) { |
|||
this.entityForm.patchValue({ |
|||
name: entity.name, |
|||
pollInterval: entity.pollInterval, |
|||
partitions: entity.partitions, |
|||
packProcessingTimeout: entity.packProcessingTimeout, |
|||
submitStrategy: { |
|||
type: entity.submitStrategy?.type, |
|||
batchSize: entity.submitStrategy?.batchSize, |
|||
}, |
|||
processingStrategy: { |
|||
type: entity.processingStrategy?.type, |
|||
retries: entity.processingStrategy?.retries, |
|||
failurePercentage: entity.processingStrategy?.failurePercentage, |
|||
pauseBetweenRetries: entity.processingStrategy?.pauseBetweenRetries, |
|||
maxPauseBetweenRetries: entity.processingStrategy?.maxPauseBetweenRetries, |
|||
} |
|||
}, {emitEvent: true}); |
|||
|
|||
if (!this.isAdd) { |
|||
this.entityForm.get('name').disable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
submitStrategyTypeChanged() { |
|||
const form = this.entityForm.get("submitStrategy") as FormGroup; |
|||
const type: QueueSubmitStrategyTypes = form.get('type').value; |
|||
const batchSizeField = form.get('batchSize'); |
|||
if (type === QueueSubmitStrategyTypes.BATCH) { |
|||
batchSizeField.enable(); |
|||
batchSizeField.patchValue(1000); |
|||
this.hideBatchSize = true; |
|||
} else { |
|||
batchSizeField.patchValue(null); |
|||
batchSizeField.disable(); |
|||
this.hideBatchSize = false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
///
|
|||
/// Copyright © 2016-2022 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.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; |
|||
import { |
|||
EntityTableColumn, |
|||
EntityTableConfig |
|||
} from '@home/models/entity/entities-table-config.models'; |
|||
import { QueueInfo, ServiceType } from '@shared/models/queue.models'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { BroadcastService } from '@core/services/broadcast.service'; |
|||
import { CustomerService } from '@core/http/customer.service'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; |
|||
import { map, mergeMap, take } from 'rxjs/operators'; |
|||
import { Observable } from 'rxjs'; |
|||
import { EntityType, entityTypeResources, entityTypeTranslations } from '@app/shared/models/entity-type.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { QueueComponent } from './queue.component'; |
|||
import { QueueService } from '@core/http/queue.service'; |
|||
import { selectAuthUser } from '@core/auth/auth.selectors'; |
|||
|
|||
@Injectable() |
|||
export class QueuesTableConfigResolver implements Resolve<EntityTableConfig<QueueInfo>> { |
|||
|
|||
readonly queueType = ServiceType.TB_RULE_ENGINE; |
|||
|
|||
private readonly config: EntityTableConfig<QueueInfo> = new EntityTableConfig<QueueInfo>(); |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private broadcast: BroadcastService, |
|||
private queueService: QueueService, |
|||
private customerService: CustomerService, |
|||
private dialogService: DialogService, |
|||
private homeDialogs: HomeDialogsService, |
|||
private translate: TranslateService) { |
|||
|
|||
this.config.entityType = EntityType.QUEUE; |
|||
this.config.entityComponent = QueueComponent; |
|||
this.config.entityTranslations = entityTypeTranslations.get(EntityType.QUEUE); |
|||
this.config.entityResources = entityTypeResources.get(EntityType.QUEUE); |
|||
|
|||
this.config.deleteEntityTitle = queue => this.translate.instant('queue.delete-queue-title', {queueName: queue.name}); |
|||
this.config.deleteEntityContent = () => this.translate.instant('queue.delete-queue-text'); |
|||
this.config.deleteEntitiesTitle = count => this.translate.instant('queue.delete-queues-title', {count}); |
|||
this.config.deleteEntitiesContent = () => this.translate.instant('queue.delete-queues-text'); |
|||
} |
|||
|
|||
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<QueueInfo>> { |
|||
this.config.componentsData = { |
|||
queueType: this.queueType |
|||
}; |
|||
|
|||
return this.store.pipe(select(selectAuthUser), take(1)).pipe( |
|||
map(() => { |
|||
this.config.tableTitle = this.translate.instant('admin.queues'); |
|||
this.config.columns = this.configureColumns(); |
|||
this.configureEntityFunctions(); |
|||
return this.config; |
|||
}) |
|||
); |
|||
} |
|||
|
|||
configureColumns(): Array<EntityTableColumn<QueueInfo>> { |
|||
return [ |
|||
new EntityTableColumn<QueueInfo>('name', 'admin.queue-name', '25%'), |
|||
new EntityTableColumn<QueueInfo>('partitions', 'admin.queue-partitions', '25%'), |
|||
new EntityTableColumn<QueueInfo>('submitStrategy', 'admin.queue-submit-strategy', '25%', |
|||
(entity: QueueInfo) => { |
|||
return entity.submitStrategy.type; |
|||
}, |
|||
() => ({}), |
|||
false |
|||
), |
|||
new EntityTableColumn<QueueInfo>('processingStrategy', 'admin.queue-processing-strategy', '25%', |
|||
(entity: QueueInfo) => { |
|||
return entity.processingStrategy.type; |
|||
}, |
|||
() => ({}), |
|||
false |
|||
) |
|||
]; |
|||
} |
|||
|
|||
configureEntityFunctions(): void { |
|||
this.config.entitiesFetchFunction = pageLink => this.queueService.getTenantQueuesByServiceType(pageLink, this.queueType); |
|||
this.config.loadEntity = id => this.queueService.getQueueById(id.id); |
|||
this.config.saveEntity = queue => this.queueService.saveQueue(this.addTopicForQueue(queue), this.queueType).pipe( |
|||
mergeMap((savedQueue) => this.queueService.getQueueById(savedQueue.id.id) |
|||
)); |
|||
this.config.deleteEntity = id => this.queueService.deleteQueue(id.id); |
|||
this.config.deleteEnabled = (queue) => queue && queue.name !== 'Main'; |
|||
} |
|||
|
|||
private addTopicForQueue(queue: QueueInfo): QueueInfo { |
|||
const modifiedQueue = Object.assign({}, queue); |
|||
modifiedQueue.topic = `tb_rule_engine.${queue.name}`; |
|||
return modifiedQueue; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
///
|
|||
/// Copyright © 2016-2022 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.
|
|||
///
|
|||
|
|||
import { EntityId } from './entity-id'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
|
|||
export class QueueId implements EntityId { |
|||
entityType = EntityType.QUEUE; |
|||
id: string; |
|||
constructor(id: string) { |
|||
this.id = id; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue