31 changed files with 505 additions and 134 deletions
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* 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.service.sync.vc; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.vc.EntityVersion; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class VoidGitRequest extends PendingGitRequest<Void> { |
||||
|
|
||||
|
public VoidGitRequest(TenantId tenantId) { |
||||
|
super(tenantId); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
/** |
||||
|
* 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.service.sync.vc; |
||||
|
|
||||
|
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; |
||||
|
import org.springframework.context.ApplicationListener; |
||||
|
|
||||
|
public interface ClusterVersionControlService extends ApplicationListener<PartitionChangeEvent> { |
||||
|
} |
||||
@ -0,0 +1,219 @@ |
|||||
|
/** |
||||
|
* 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.service.sync.vc; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
|
import org.springframework.context.annotation.Lazy; |
||||
|
import org.springframework.context.event.EventListener; |
||||
|
import org.springframework.core.annotation.Order; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
||||
|
import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
import org.thingsboard.server.common.msg.queue.ServiceType; |
||||
|
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; |
||||
|
import org.thingsboard.server.queue.TbQueueCallback; |
||||
|
import org.thingsboard.server.queue.TbQueueConsumer; |
||||
|
import org.thingsboard.server.queue.TbQueueMsgMetadata; |
||||
|
import org.thingsboard.server.queue.TbQueueProducer; |
||||
|
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
||||
|
import org.thingsboard.server.queue.discovery.NotificationsTopicService; |
||||
|
import org.thingsboard.server.queue.discovery.PartitionService; |
||||
|
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; |
||||
|
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
||||
|
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; |
||||
|
import org.thingsboard.server.queue.provider.TbVersionControlQueueFactory; |
||||
|
import org.thingsboard.server.queue.util.DataDecodingEncodingService; |
||||
|
import org.thingsboard.server.queue.util.TbVersionControlComponent; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
@Slf4j |
||||
|
@TbVersionControlComponent |
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class DefaultClusterVersionControlService extends TbApplicationEventListener<PartitionChangeEvent> implements ClusterVersionControlService { |
||||
|
|
||||
|
private final TbServiceInfoProvider serviceInfoProvider; |
||||
|
private final TbVersionControlQueueFactory queueFactory; |
||||
|
private final DataDecodingEncodingService encodingService; |
||||
|
private final GitRepositoryService vcService; |
||||
|
private final NotificationsTopicService notificationsTopicService; |
||||
|
|
||||
|
private volatile ExecutorService consumerExecutor; |
||||
|
private volatile TbQueueConsumer<TbProtoQueueMsg<ToVersionControlServiceMsg>> consumer; |
||||
|
private volatile TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> producer; |
||||
|
private volatile boolean stopped = false; |
||||
|
|
||||
|
@Value("${queue.vc.poll-interval:25}") |
||||
|
private long pollDuration; |
||||
|
@Value("${queue.vc.pack-processing-timeout:60000}") |
||||
|
private long packProcessingTimeout; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("vc-consumer")); |
||||
|
producer = queueFactory.createTbCoreNotificationsMsgProducer(); |
||||
|
consumer = queueFactory.createToVersionControlMsgConsumer(); |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
public void stop() { |
||||
|
stopped = true; |
||||
|
if (consumer != null) { |
||||
|
consumer.unsubscribe(); |
||||
|
} |
||||
|
if (consumerExecutor != null) { |
||||
|
consumerExecutor.shutdownNow(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onTbApplicationEvent(PartitionChangeEvent event) { |
||||
|
//TODO: cleanup repositories that we no longer manage in this node.
|
||||
|
consumer.subscribe(event.getPartitions()); |
||||
|
} |
||||
|
|
||||
|
@EventListener(ApplicationReadyEvent.class) |
||||
|
@Order(value = 2) |
||||
|
public void onApplicationEvent(ApplicationReadyEvent event) { |
||||
|
consumerExecutor.execute(() -> consumerLoop(consumer)); |
||||
|
} |
||||
|
|
||||
|
void consumerLoop(TbQueueConsumer<TbProtoQueueMsg<ToVersionControlServiceMsg>> consumer) { |
||||
|
while (!stopped && !consumer.isStopped()) { |
||||
|
try { |
||||
|
List<TbProtoQueueMsg<ToVersionControlServiceMsg>> msgs = consumer.poll(pollDuration); |
||||
|
if (msgs.isEmpty()) { |
||||
|
continue; |
||||
|
} |
||||
|
for (TbProtoQueueMsg<ToVersionControlServiceMsg> msgWrapper : msgs) { |
||||
|
ToVersionControlServiceMsg msg = msgWrapper.getValue(); |
||||
|
if (msg.hasClearRepositoryRequest()) { |
||||
|
handleClearRepositoryCommand(new VersionControlRequestCtx(msg, null)); |
||||
|
} else { |
||||
|
VersionControlRequestCtx ctx = new VersionControlRequestCtx(msg, getEntitiesVersionControlSettings(msg)); |
||||
|
if (msg.hasTestRepositoryRequest()) { |
||||
|
handleTestRepositoryCommand(ctx); |
||||
|
} else if (msg.hasInitRepositoryRequest()) { |
||||
|
handleInitRepositoryCommand(ctx); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// ConcurrentMap<UUID, TbProtoQueueMsg<ToIntegrationExecutorDownlinkMsg>> pendingMap = msgs.stream().collect(
|
||||
|
// Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
|
||||
|
// CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
|
||||
|
// TbPackProcessingContext<TbProtoQueueMsg<ToIntegrationExecutorDownlinkMsg>> ctx = new TbPackProcessingContext<>(
|
||||
|
// processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
|
||||
|
// pendingMap.forEach((id, msg) -> {
|
||||
|
// log.trace("[{}] Creating downlink callback for message: {}", id, msg.getValue());
|
||||
|
// TbCallback callback = new TbPackCallback<>(id, ctx);
|
||||
|
// try {
|
||||
|
// handleDownlink(id, msg, callback);
|
||||
|
// } catch (Throwable e) {
|
||||
|
// log.warn("[{}] Failed to process notification: {}", id, msg, e);
|
||||
|
// callback.onFailure(e);
|
||||
|
// }
|
||||
|
// });
|
||||
|
// if (!processingTimeoutLatch.await(processingTimeout, TimeUnit.MILLISECONDS)) {
|
||||
|
// ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process downlink: {}", id, msg.getValue()));
|
||||
|
// ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process downlink: {}", id, msg.getValue()));
|
||||
|
// }
|
||||
|
consumer.commit(); |
||||
|
} catch (Exception e) { |
||||
|
if (!stopped) { |
||||
|
log.warn("Failed to obtain version control requests from queue.", e); |
||||
|
try { |
||||
|
Thread.sleep(pollDuration); |
||||
|
} catch (InterruptedException e2) { |
||||
|
log.trace("Failed to wait until the server has capacity to handle new version control messages", e2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
log.info("TB Version Control request consumer stopped."); |
||||
|
} |
||||
|
|
||||
|
private void handleClearRepositoryCommand(VersionControlRequestCtx ctx) { |
||||
|
try { |
||||
|
vcService.clearRepository(ctx.getTenantId()); |
||||
|
reply(ctx, Optional.empty()); |
||||
|
} catch (Exception e) { |
||||
|
log.debug("[{}] Failed to connect to the repository: ", ctx, e); |
||||
|
reply(ctx, Optional.of(e)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void handleInitRepositoryCommand(VersionControlRequestCtx ctx) { |
||||
|
try { |
||||
|
vcService.initRepository(ctx.getTenantId(), ctx.getSettings()); |
||||
|
reply(ctx, Optional.empty()); |
||||
|
} catch (Exception e) { |
||||
|
log.debug("[{}] Failed to connect to the repository: ", ctx, e); |
||||
|
reply(ctx, Optional.of(e)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void handleTestRepositoryCommand(VersionControlRequestCtx ctx) { |
||||
|
try { |
||||
|
vcService.testRepository(ctx.getTenantId(), ctx.getSettings()); |
||||
|
reply(ctx, Optional.empty()); |
||||
|
} catch (Exception e) { |
||||
|
log.debug("[{}] Failed to connect to the repository: ", ctx, e); |
||||
|
reply(ctx, Optional.of(e)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void reply(VersionControlRequestCtx ctx, Optional<Exception> e) { |
||||
|
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, ctx.getNodeId()); |
||||
|
TransportProtos.VersionControlResponseMsg.Builder builder = TransportProtos.VersionControlResponseMsg.newBuilder() |
||||
|
.setRequestIdMSB(ctx.getRequestId().getMostSignificantBits()) |
||||
|
.setRequestIdLSB(ctx.getRequestId().getLeastSignificantBits()); |
||||
|
if (e.isPresent()) { |
||||
|
builder.setError(e.get().getMessage()); |
||||
|
} else { |
||||
|
builder.setGenericResponse(TransportProtos.GenericRepositoryResponseMsg.newBuilder().build()); |
||||
|
} |
||||
|
ToCoreNotificationMsg msg = ToCoreNotificationMsg.newBuilder().setVcResponseMsg(builder).build(); |
||||
|
log.trace("PUSHING msg: {} to: {}", msg, tpi); |
||||
|
producer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); |
||||
|
} |
||||
|
|
||||
|
private EntitiesVersionControlSettings getEntitiesVersionControlSettings(ToVersionControlServiceMsg msg) { |
||||
|
Optional<EntitiesVersionControlSettings> settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray()); |
||||
|
if (settingsOpt.isPresent()) { |
||||
|
return settingsOpt.get(); |
||||
|
} else { |
||||
|
log.warn("Failed to parse VC settings: {}", msg.getVcSettings()); |
||||
|
throw new RuntimeException("Failed to parse vc settings!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,52 +0,0 @@ |
|||||
/** |
|
||||
* 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.service.sync.vc; |
|
||||
|
|
||||
import org.thingsboard.server.common.data.EntityType; |
|
||||
import org.thingsboard.server.common.data.id.EntityId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
|
||||
import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; |
|
||||
import org.thingsboard.server.common.data.sync.vc.EntityVersion; |
|
||||
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
public interface GitVersionControlService { |
|
||||
|
|
||||
void testRepository(TenantId tenantId, EntitiesVersionControlSettings settings); |
|
||||
|
|
||||
void initRepository(TenantId tenantId, EntitiesVersionControlSettings settings); |
|
||||
|
|
||||
void clearRepository(TenantId tenantId); |
|
||||
|
|
||||
List<EntityVersion> listVersions(TenantId tenantId, String branch); |
|
||||
|
|
||||
List<EntityVersion> listVersions(TenantId tenantId, String branch, EntityType entityType); |
|
||||
|
|
||||
List<EntityVersion> listVersions(TenantId tenantId, String branch, EntityId entityId); |
|
||||
|
|
||||
List<VersionedEntityInfo> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, EntityType entityType); |
|
||||
|
|
||||
List<VersionedEntityInfo> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId); |
|
||||
|
|
||||
List<String> listBranches(TenantId tenantId); |
|
||||
|
|
||||
EntityExportData<?> getEntity(TenantId tenantId, String versionId, EntityId entityId); |
|
||||
|
|
||||
List<EntityExportData<?>> getEntities(TenantId tenantId, String branch, String versionId, EntityType entityType, int offset, int limit); |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,49 @@ |
|||||
|
/** |
||||
|
* 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.service.sync.vc; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@RequiredArgsConstructor |
||||
|
@Data |
||||
|
public class VersionControlRequestCtx { |
||||
|
private final String nodeId; |
||||
|
private final UUID requestId; |
||||
|
private final TenantId tenantId; |
||||
|
private final EntitiesVersionControlSettings settings; |
||||
|
|
||||
|
public VersionControlRequestCtx(ToVersionControlServiceMsg msg, EntitiesVersionControlSettings settings) { |
||||
|
this.nodeId = msg.getNodeId(); |
||||
|
this.requestId = new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB()); |
||||
|
this.tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); |
||||
|
this.settings = settings; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "VersionControlRequestCtx{" + |
||||
|
"nodeId='" + nodeId + '\'' + |
||||
|
", requestId=" + requestId + |
||||
|
", tenantId=" + tenantId + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue