Skip to content

Commit

Permalink
chore: start adding cache support
Browse files Browse the repository at this point in the history
  • Loading branch information
jcosentino11 committed Jun 28, 2024
1 parent b4c43cd commit 631cd15
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import com.aws.greengrass.clientdevices.auth.connectivity.HostAddress;
import com.aws.greengrass.clientdevices.auth.iot.dto.CertificateV1DTO;
import com.aws.greengrass.clientdevices.auth.iot.dto.ThingAssociationV1DTO;
import com.aws.greengrass.clientdevices.auth.iot.dto.ThingDescriptionV1DTO;
import com.aws.greengrass.clientdevices.auth.iot.dto.ThingV1DTO;
import com.aws.greengrass.config.Node;
import com.aws.greengrass.config.Topic;
Expand Down Expand Up @@ -204,6 +206,22 @@ public void removeCertificateV1(String certificateId) {
}
}

public void putThingAssociationV1(ThingAssociationV1DTO thingAssociationV1DTO) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'thingAssociationV1DTO' is never used.
// TODO
}

public ThingAssociationV1DTO getThingAssociationV1() {
return null; // TODO
}

public void putThingDescriptionV1(ThingDescriptionV1DTO thingDescriptionV1DTO) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'thingDescriptionV1DTO' is never used.
// TODO
}

public ThingDescriptionV1DTO getThingDescriptionV1(String thingName) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'thingName' is never used.
return null; // TODO
}

private Topics getOrRepairTopics(Topics root, String... path) {
try {
return root.lookupTopics(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import software.amazon.awssdk.services.iot.IotClient;
import software.amazon.awssdk.services.iot.model.DescribeThingRequest;

import java.util.Collections;
import java.util.Map;
import javax.inject.Inject;

Expand All @@ -38,10 +39,11 @@ class Default implements IotCoreClient {
@SuppressWarnings("PMD.AvoidCatchingGenericException")
public Map<String, String> getThingAttributes(String thingName) throws CloudServiceInteractionException {
try (IotClient client = iotClientFactory.getClient()) {
return client.describeThing(DescribeThingRequest.builder()
Map<String, String> attributes = client.describeThing(DescribeThingRequest.builder()
.thingName(thingName)
.build())
.attributes();
return attributes == null ? Collections.emptyMap() : attributes;
} catch (DeviceConfigurationException e) {
throw new CloudServiceInteractionException("Failed to construct IoT Core client", e);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

package com.aws.greengrass.clientdevices.auth.iot;

import com.aws.greengrass.clientdevices.auth.configuration.RuntimeConfiguration;
import com.aws.greengrass.clientdevices.auth.exception.CloudServiceInteractionException;
import com.aws.greengrass.clientdevices.auth.infra.NetworkStateProvider;
import com.aws.greengrass.clientdevices.auth.iot.dto.ThingAssociationV1DTO;
import com.aws.greengrass.clientdevices.auth.iot.dto.ThingDescriptionV1DTO;
import com.aws.greengrass.logging.api.Logger;
import com.aws.greengrass.logging.impl.LogManager;
import software.amazon.awssdk.services.greengrassv2.model.AssociatedClientDevice;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -28,6 +32,13 @@ public class ThingAttributesCache {

private static final Logger logger = LogManager.getLogger(ThingAttributesCache.class);

private static final long DEFAULT_REFRESH_DELAY_SECONDS =
TimeUnit.MINUTES.toSeconds(1);
private static final long DEFAULT_THING_ASSOCIATION_TRUST_DURATION_SECONDS =
TimeUnit.MINUTES.toSeconds(5);
private static final long DEFAULT_THING_DESCRIPTION_TRUST_DURATION_SECONDS =
TimeUnit.MINUTES.toSeconds(10);

// set once during component install
private static final AtomicReference<ThingAttributesCache> INSTANCE = new AtomicReference<>();
private final AtomicReference<CountDownLatch> initialized = new AtomicReference<>();
Expand All @@ -41,6 +52,8 @@ public class ThingAttributesCache {
private final NetworkStateProvider networkStateProvider;
private ScheduledFuture<?> refreshTask;

private final RuntimeConfiguration runtimeConfiguration;

public static Optional<ThingAttributesCache> instance() {
return Optional.ofNullable(INSTANCE.get());
}
Expand All @@ -55,16 +68,19 @@ public static void setInstance(ThingAttributesCache cache) {
* @param iotCoreClient iot core client
* @param iotAuthClient iot auth client
* @param networkStateProvider network state provider
* @param runtimeConfiguration runtime configuration
* @param ses scheduled executor service
*/
@Inject
public ThingAttributesCache(IotCoreClient iotCoreClient,
IotAuthClient iotAuthClient,
NetworkStateProvider networkStateProvider,
RuntimeConfiguration runtimeConfiguration,
ScheduledExecutorService ses) {
this.iotCoreClient = iotCoreClient;
this.iotAuthClient = iotAuthClient;
this.networkStateProvider = networkStateProvider;
this.runtimeConfiguration = runtimeConfiguration;
this.ses = ses;
}

Expand Down Expand Up @@ -102,8 +118,9 @@ private void markAsInitialized() {
*/
public void startPeriodicRefresh() {
stopPeriodicRefresh();
// TODO configurable delay
refreshTask = ses.scheduleWithFixedDelay(this::refresh, 0L, 1L, TimeUnit.MINUTES);
// TODO pull from configuration
refreshTask = ses.scheduleWithFixedDelay(this::refresh, 0L,
DEFAULT_REFRESH_DELAY_SECONDS, TimeUnit.SECONDS);
}

/**
Expand All @@ -117,46 +134,91 @@ public void stopPeriodicRefresh() {
}

private void refresh() {
if (networkStateProvider.getConnectionState() == NetworkStateProvider.ConnectionState.NETWORK_DOWN) {
// TODO cache attributes on disk and load here, handle case if device restarts while offline
logger.atTrace().log("network down, unable to refresh thing-attribute cache");
return;
}
logger.atTrace().log("beginning thing-attribute cache refresh");
getAssociatedThingNames().ifPresent(thingNames -> {
for (String thingName : thingNames) {
if (Thread.currentThread().isInterrupted()) {
return;
}
fetchDeviceAttributes(thingName).ifPresent(attrs -> {
getThingAttributes(thingName).ifPresent(attrs -> {
logger.atInfo().kv("thing", thingName).log("attributes refreshed for device");
attributesByThing.put(thingName, new ConcurrentHashMap<>(attrs));
});
}
// TODO handle case where some fetches fail
// TODO it's currently possible that not all thing attributes were successfully
// fetched at this point, meaning that CDA will have started and devices
// will be rejected until subsequent refreshes as executed successfully.
markAsInitialized();
});
}

@SuppressWarnings("PMD.AvoidCatchingGenericException")
private Optional<Set<String>> getAssociatedThingNames() {
// use cached value, provided it's not stale
ThingAssociationV1DTO dto = runtimeConfiguration.getThingAssociationV1();
// TODO pull from configuration
if (dto != null && dto.getLastFetched().plusSeconds(DEFAULT_THING_ASSOCIATION_TRUST_DURATION_SECONDS)
.isBefore(LocalDateTime.now())) {
logger.atTrace().log("Using locally cached thing associations");
return Optional.ofNullable(dto.getAssociatedThingNames());
}

if (networkStateProvider.getConnectionState() == NetworkStateProvider.ConnectionState.NETWORK_DOWN) {
logger.atTrace().log("Network down, unable to fetch thing associations from cloud");
return Optional.empty();
}

// otherwise fetch new associations from cloud and write to cache
logger.atTrace().log("Fetching thing associations from cloud");
Optional<Set<String>> associatedThingNames = fetchAssociatedThingNames();
associatedThingNames.ifPresent(names ->
runtimeConfiguration.putThingAssociationV1(new ThingAssociationV1DTO(names, LocalDateTime.now())));
return associatedThingNames;
}

@SuppressWarnings("PMD.AvoidCatchingGenericException")
private Optional<Set<String>> fetchAssociatedThingNames() {
try {
return Optional.of(iotAuthClient.getThingsAssociatedWithCoreDevice()
.flatMap(List::stream)
.map(AssociatedClientDevice::thingName)
.collect(Collectors.toSet()));
} catch (Exception e) {
logger.atWarn()
.cause(e)
.log("Unable to find associated things");
return Optional.empty();
}
}

private Optional<Map<String, String>> fetchDeviceAttributes(String thingName) {
private Optional<Map<String, String>> getThingAttributes(String thingName) {
// use cached value, provided it's not stale
ThingDescriptionV1DTO dto = runtimeConfiguration.getThingDescriptionV1(thingName);
// TODO pull from configuration
if (dto != null && dto.getLastFetched().plusSeconds(DEFAULT_THING_DESCRIPTION_TRUST_DURATION_SECONDS)
.isBefore(LocalDateTime.now())) {
logger.atTrace().log("Using locally cached thing description");
return Optional.ofNullable(dto.getAttributes());
}

if (networkStateProvider.getConnectionState() == NetworkStateProvider.ConnectionState.NETWORK_DOWN) {
logger.atTrace().log("Network down, unable to fetch thing description from cloud");
return Optional.empty();
}

// otherwise fetch new description from cloud and write to cache
logger.atTrace().log("Fetching thing description from cloud");
Optional<Map<String, String>> attributes = fetchThingAttributes(thingName);
attributes.ifPresent(attrs ->
runtimeConfiguration.putThingDescriptionV1(new ThingDescriptionV1DTO(thingName, attrs, LocalDateTime.now())));
return attributes;
}

private Optional<Map<String, String>> fetchThingAttributes(String thingName) {
try {
return Optional.ofNullable(iotCoreClient.getThingAttributes(thingName));
return Optional.of(iotCoreClient.getThingAttributes(thingName));
} catch (CloudServiceInteractionException e) {
logger.atWarn()
.cause(e)
.kv("thing", thingName)
.log("Unable to get thing attributes");
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.aws.greengrass.clientdevices.auth.iot.dto;

import lombok.AllArgsConstructor;
import lombok.Value;

import java.time.LocalDateTime;
import java.util.Set;

@Value
@AllArgsConstructor
public class ThingAssociationV1DTO {
Set<String> associatedThingNames;
LocalDateTime lastFetched;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.aws.greengrass.clientdevices.auth.iot.dto;

import lombok.AllArgsConstructor;
import lombok.Value;

import java.time.LocalDateTime;
import java.util.Map;

@Value
@AllArgsConstructor
public class ThingDescriptionV1DTO {
String thingName;
Map<String, String> attributes;
LocalDateTime lastFetched;
}

0 comments on commit 631cd15

Please sign in to comment.