diff --git a/.github/workflows/code-formatting.yml b/.github/workflows/code-formatting.yml new file mode 100644 index 0000000000..45897cace6 --- /dev/null +++ b/.github/workflows/code-formatting.yml @@ -0,0 +1,32 @@ +name: Code Formatting Check + +on: + pull_request: + branches: [ main ] + paths: + - '**.java' + - 'pom.xml' + - 'codestyle/**' + +jobs: + formatting-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'corretto' + + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Check code formatting + run: mvn spotless:check diff --git a/codestyle/eclipse-formatter.xml b/codestyle/eclipse-formatter.xml new file mode 100644 index 0000000000..7718461bd7 --- /dev/null +++ b/codestyle/eclipse-formatter.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index bd27736529..ab1be16d1a 100644 --- a/pom.xml +++ b/pom.xml @@ -445,34 +445,6 @@ - - maven-checkstyle-plugin - 3.1.0 - - - com.puppycrawl.tools - checkstyle - 8.29 - - - - true - codestyle/checkstyle.xml - warning - 0 - **/awssdk/**, **/eventstream/**, **/vendored/** - ${skipTests} - - - - validate - validate - - check - - - - maven-surefire-plugin 3.0.0-M7 @@ -846,6 +818,19 @@ + + com.diffplug.spotless + spotless-maven-plugin + 2.40.0 + + + + codestyle/eclipse-formatter.xml + + + + + diff --git a/src/main/java/com/aws/greengrass/authorization/AuthorizationHandler.java b/src/main/java/com/aws/greengrass/authorization/AuthorizationHandler.java index 49521d4675..6c93302d76 100644 --- a/src/main/java/com/aws/greengrass/authorization/AuthorizationHandler.java +++ b/src/main/java/com/aws/greengrass/authorization/AuthorizationHandler.java @@ -65,17 +65,15 @@ import static software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCServiceModel.STOP_COMPONENT; /** - * Main module which is responsible for handling AuthZ for Greengrass. This only manages - * the AuthZ configuration and performs lookups based on the config. Config is just a copy of - * customer config and this module does not try to optimize storage. For instance, - * if customer specifies same policy twice, we treat and store them separately. Components are - * identified by their service identifiers (component names) and operation/resources are assumed to be - * opaque strings. They are not treated as confidential and it should be the responsibility - * of the caller to use proxy identifiers for confidential data. Implementation optimizes for fast lookups - * and not for storage. + * Main module which is responsible for handling AuthZ for Greengrass. This only manages the AuthZ configuration and + * performs lookups based on the config. Config is just a copy of customer config and this module does not try to + * optimize storage. For instance, if customer specifies same policy twice, we treat and store them separately. + * Components are identified by their service identifiers (component names) and operation/resources are assumed to be + * opaque strings. They are not treated as confidential and it should be the responsibility of the caller to use proxy + * identifiers for confidential data. Implementation optimizes for fast lookups and not for storage. */ @Singleton -public class AuthorizationHandler { +public class AuthorizationHandler { public static final String ANY_REGEX = "*"; public static final String SECRETS_MANAGER_SERVICE_NAME = "aws.greengrass.SecretManager"; public static final String SHADOW_MANAGER_SERVICE_NAME = "aws.greengrass.ShadowManager"; @@ -83,14 +81,13 @@ public class AuthorizationHandler { private static final String CLI_SERVICE_NAME = "aws.greengrass.Cli"; public enum ResourceLookupPolicy { - STANDARD, - MQTT_STYLE + STANDARD, MQTT_STYLE } private static final Logger logger = LogManager.getLogger(AuthorizationHandler.class); private final ConcurrentHashMap> componentToOperationsMap = new ConcurrentHashMap<>(); - private final ConcurrentHashMap> - componentToAuthZConfig = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> componentToAuthZConfig = + new ConcurrentHashMap<>(); private final Kernel kernel; private final AuthorizationModule authModule; @@ -104,40 +101,35 @@ public enum ResourceLookupPolicy { * @param policyParser for parsing a given policy ACL */ @Inject - public AuthorizationHandler(Kernel kernel, AuthorizationModule authModule, - AuthorizationPolicyParser policyParser) { + public AuthorizationHandler(Kernel kernel, AuthorizationModule authModule, AuthorizationPolicyParser policyParser) { this.kernel = kernel; this.authModule = authModule; // Adding TES component and operation before it's default policies are fetched - componentToOperationsMap.put(TOKEN_EXCHANGE_SERVICE_TOPICS, new HashSet<>( - Collections.singletonList(AUTHZ_TES_OPERATION))); - componentToOperationsMap.put(PUB_SUB_SERVICE_NAME, new HashSet<>(Arrays.asList(PUBLISH_TO_TOPIC, - SUBSCRIBE_TO_TOPIC, ANY_REGEX))); - componentToOperationsMap.put(MQTT_PROXY_SERVICE_NAME, new HashSet<>(Arrays.asList(PUBLISH_TO_IOT_CORE, - SUBSCRIBE_TO_IOT_CORE, ANY_REGEX))); - componentToOperationsMap.put(SECRETS_MANAGER_SERVICE_NAME, new HashSet<>(Arrays.asList(GET_SECRET_VALUE, - ANY_REGEX))); + componentToOperationsMap.put(TOKEN_EXCHANGE_SERVICE_TOPICS, + new HashSet<>(Collections.singletonList(AUTHZ_TES_OPERATION))); + componentToOperationsMap.put(PUB_SUB_SERVICE_NAME, + new HashSet<>(Arrays.asList(PUBLISH_TO_TOPIC, SUBSCRIBE_TO_TOPIC, ANY_REGEX))); + componentToOperationsMap.put(MQTT_PROXY_SERVICE_NAME, + new HashSet<>(Arrays.asList(PUBLISH_TO_IOT_CORE, SUBSCRIBE_TO_IOT_CORE, ANY_REGEX))); + componentToOperationsMap.put(SECRETS_MANAGER_SERVICE_NAME, + new HashSet<>(Arrays.asList(GET_SECRET_VALUE, ANY_REGEX))); componentToOperationsMap.put(SHADOW_MANAGER_SERVICE_NAME, new HashSet<>(Arrays.asList(GET_THING_SHADOW, UPDATE_THING_SHADOW, DELETE_THING_SHADOW, LIST_NAMED_SHADOWS_FOR_THING, ANY_REGEX))); - componentToOperationsMap.put(LIFECYCLE_SERVICE_NAME, new HashSet<>(Arrays.asList(PAUSE_COMPONENT, - RESUME_COMPONENT, ANY_REGEX))); + componentToOperationsMap.put(LIFECYCLE_SERVICE_NAME, + new HashSet<>(Arrays.asList(PAUSE_COMPONENT, RESUME_COMPONENT, ANY_REGEX))); componentToOperationsMap.put(CLIENT_DEVICE_AUTH_SERVICE_NAME, - new HashSet<>(Arrays.asList(SUBSCRIBE_TO_CERTIFICATE_UPDATES, - VERIFY_CLIENT_DEVICE_IDENTITY, - GET_CLIENT_DEVICE_AUTH_TOKEN, - AUTHORIZE_CLIENT_DEVICE_ACTION, - ANY_REGEX))); - componentToOperationsMap.put(CLI_SERVICE_NAME, new HashSet<>(Arrays.asList(GET_COMPONENT_DETAILS, - LIST_COMPONENTS, RESTART_COMPONENT, - STOP_COMPONENT, CREATE_LOCAL_DEPLOYMENT, - GET_LOCAL_DEPLOYMENT_STATUS, LIST_LOCAL_DEPLOYMENTS, - CREATE_DEBUG_PASSWORD, ANY_REGEX))); + new HashSet<>(Arrays.asList(SUBSCRIBE_TO_CERTIFICATE_UPDATES, VERIFY_CLIENT_DEVICE_IDENTITY, + GET_CLIENT_DEVICE_AUTH_TOKEN, AUTHORIZE_CLIENT_DEVICE_ACTION, ANY_REGEX))); + componentToOperationsMap.put(CLI_SERVICE_NAME, + new HashSet<>(Arrays.asList(GET_COMPONENT_DETAILS, LIST_COMPONENTS, RESTART_COMPONENT, STOP_COMPONENT, + CREATE_LOCAL_DEPLOYMENT, GET_LOCAL_DEPLOYMENT_STATUS, LIST_LOCAL_DEPLOYMENTS, + CREATE_DEBUG_PASSWORD, ANY_REGEX))); componentToOperationsMap.put(PUT_COMPONENT_METRIC_SERVICE_NAME, new HashSet<>(Arrays.asList(PUT_COMPONENT_METRIC, ANY_REGEX))); - Map> componentNameToPolicies = policyParser.parseAllAuthorizationPolicies( - kernel); - //Load default policies + Map> componentNameToPolicies = + policyParser.parseAllAuthorizationPolicies(kernel); + // Load default policies componentNameToPolicies.putAll(getDefaultPolicies()); for (Map.Entry> acl : componentNameToPolicies.entrySet()) { @@ -153,19 +145,20 @@ public AuthorizationHandler(Kernel kernel, AuthorizationModule authModule, return false; } - //If there is a childChanged event, it has to be the 'accessControl' Topic that has bubbled up - //If there is a childRemoved event, it could be the component is removed, or either the - //'accessControl' Topic or/the 'parameters' Topics that has bubbled up, so we need to handle and - //filter out all other WhatHappeneds + // If there is a childChanged event, it has to be the 'accessControl' Topic that has bubbled up + // If there is a childRemoved event, it could be the component is removed, or either the + // 'accessControl' Topic or/the 'parameters' Topics that has bubbled up, so we need to handle and + // filter out all other WhatHappeneds if (WhatHappened.childRemoved.equals(why) || WhatHappened.removed.equals(why)) { // Either a service or a parameter block or acl subkey - if (!newv.parent.getName().equals(SERVICES_NAMESPACE_TOPIC) && !newv.getName() - .equals(CONFIGURATION_CONFIG_KEY) && !newv.getName().equals(ACCESS_CONTROL_NAMESPACE_TOPIC) + if (!newv.parent.getName().equals(SERVICES_NAMESPACE_TOPIC) + && !newv.getName().equals(CONFIGURATION_CONFIG_KEY) + && !newv.getName().equals(ACCESS_CONTROL_NAMESPACE_TOPIC) && !newv.childOf(ACCESS_CONTROL_NAMESPACE_TOPIC)) { return true; } - } else if (!newv.childOf(ACCESS_CONTROL_NAMESPACE_TOPIC) && !newv.getName() - .equals(ACCESS_CONTROL_NAMESPACE_TOPIC)) { + } else if (!newv.childOf(ACCESS_CONTROL_NAMESPACE_TOPIC) + && !newv.getName().equals(ACCESS_CONTROL_NAMESPACE_TOPIC)) { // for all other WhatHappened cases we only care about access control change return true; } @@ -180,18 +173,18 @@ public AuthorizationHandler(Kernel kernel, AuthorizationModule authModule, reloadedPolicies.putAll(getDefaultPolicies()); try (LockScope scope = LockScope.lock(rwLock.writeLock())) { - for (Map.Entry> primaryPolicyList - : componentToAuthZConfig.entrySet()) { + for (Map.Entry> primaryPolicyList : componentToAuthZConfig + .entrySet()) { String policyType = primaryPolicyList.getKey(); if (!reloadedPolicies.containsKey(policyType)) { - //If the policyType already exists and was not reparsed correctly and/or removed from - //the newly parsed list, delete it from our store since it is now an unwanted relic + // If the policyType already exists and was not reparsed correctly and/or removed from + // the newly parsed list, delete it from our store since it is now an unwanted relic componentToAuthZConfig.remove(policyType); authModule.deletePermissionsWithDestination(policyType); } } - //Now we reload the policies that reflect the current state of the Nucleus config + // Now we reload the policies that reflect the current state of the Nucleus config for (Map.Entry> acl : reloadedPolicies.entrySet()) { this.loadAuthorizationPolicies(acl.getKey(), acl.getValue(), true); } @@ -200,13 +193,12 @@ public AuthorizationHandler(Kernel kernel, AuthorizationModule authModule, } /** - * Check if the combination of destination, principal, operation and resource is allowed. - * A scenario where this method is called is for a request which originates from {@code principal} - * component destined for {@code destination} component, which needs access to {@code resource} - * using API {@code operation}. + * Check if the combination of destination, principal, operation and resource is allowed. A scenario where this + * method is called is for a request which originates from {@code principal} component destined for + * {@code destination} component, which needs access to {@code resource} using API {@code operation}. * * @param destination Destination component which is being accessed. - * @param permission container for principal, operation and resource. + * @param permission container for principal, operation and resource. * @param resourceLookupPolicy whether to match MQTT wildcards or not. * @return whether the input combination is a valid flow. * @throws AuthorizationException when flow is not authorized. @@ -222,10 +214,15 @@ public boolean isAuthorized(String destination, Permission permission, ResourceL // Lookup all possible allow configurations starting from most specific to least // This helps for access logs, as customer can figure out which policy is being hit. String[][] combinations = { - {destination, principal, operation, resource}, - {destination, principal, ANY_REGEX, resource}, - {destination, ANY_REGEX, operation, resource}, - {destination, ANY_REGEX, ANY_REGEX, resource}, + { + destination, principal, operation, resource + }, { + destination, principal, ANY_REGEX, resource + }, { + destination, ANY_REGEX, operation, resource + }, { + destination, ANY_REGEX, ANY_REGEX, resource + }, }; try (LockScope scope = LockScope.lock(rwLock.readLock())) { for (String[] combination : combinations) { @@ -234,21 +231,17 @@ public boolean isAuthorized(String destination, Permission permission, ResourceL .principal(combination[1]) .operation(combination[2]) .resource(combination[3]) - .build(), resourceLookupPolicy)) { - logger.atDebug().log("Hit policy with principal {}, operation {}, resource {}", - combination[1], - combination[2], - combination[3]); + .build(), + resourceLookupPolicy)) { + logger.atDebug() + .log("Hit policy with principal {}, operation {}, resource {}", combination[1], + combination[2], combination[3]); return true; } } } - throw new AuthorizationException( - String.format("Principal %s is not authorized to perform %s:%s on resource %s", - principal, - destination, - operation, - resource)); + throw new AuthorizationException(String.format("Principal %s is not authorized to perform %s:%s on resource %s", + principal, destination, operation, resource)); } public boolean isAuthorized(String destination, Permission permission) throws AuthorizationException { @@ -256,12 +249,12 @@ public boolean isAuthorized(String destination, Permission permission) throws Au } /** - * Get allowed resources for the combination of destination, principal and operation. - * Also returns resources covered by permissions with * operation/principal. + * Get allowed resources for the combination of destination, principal and operation. Also returns resources covered + * by permissions with * operation/principal. * * @param destination destination - * @param principal principal (cannot be *) - * @param operation operation (cannot be *) + * @param principal principal (cannot be *) + * @param operation operation (cannot be *) * @return list of allowed resources * @throws AuthorizationException when arguments are invalid */ @@ -278,18 +271,16 @@ public Set getAuthorizedResources(String destination, @NonNull String pr } /** - * Register a component with AuthZ module. This registers an Greengrass component with authorization module. - * This is required to register list of operations supported by a component especially for 3P component - * in future, whose operations might not be known at bootstrap. - * Operations are identifiers which the components intend to match for incoming requests by calling - * {@link #isAuthorized(String, Permission)} isAuthorized} method. + * Register a component with AuthZ module. This registers an Greengrass component with authorization module. This is + * required to register list of operations supported by a component especially for 3P component in future, whose + * operations might not be known at bootstrap. Operations are identifiers which the components intend to match for + * incoming requests by calling {@link #isAuthorized(String, Permission)} isAuthorized} method. * * @param componentName Name of the component to be registered. - * @param operations Set of operations the component needs to register with AuthZ. + * @param operations Set of operations the component needs to register with AuthZ. * @throws AuthorizationException If component is already registered. */ - public void registerComponent(String componentName, Set operations) - throws AuthorizationException { + public void registerComponent(String componentName, Set operations) throws AuthorizationException { if (Utils.isEmpty(operations) || Utils.isEmpty(componentName)) { throw new AuthorizationException("Invalid arguments for registerComponent()"); } @@ -299,16 +290,14 @@ public void registerComponent(String componentName, Set operations) /** * Loads authZ policies for a single component for future auth lookups. The policies should not have confidential - * values. This method assumes that the component names for principal and destination, - * the operations and resources must not be secret and can be logged or shared if required. - * If the isUpdate flag is specified, this method will clear the existing policies for a component before - * refreshing with the updated list. + * values. This method assumes that the component names for principal and destination, the operations and resources + * must not be secret and can be logged or shared if required. If the isUpdate flag is specified, this method will + * clear the existing policies for a component before refreshing with the updated list. * * @param componentName Destination component which intends to supply auth policies - * @param policies List of policies. All policies are treated as separate - * and no merging or joins happen. Duplicated policies would result in duplicated - * permissions but would not impact functionality. - * @param isUpdate If this load request is to update existing policies for a component. + * @param policies List of policies. All policies are treated as separate and no merging or joins happen. Duplicated + * policies would result in duplicated permissions but would not impact functionality. + * @param isUpdate If this load request is to update existing policies for a component. */ public void loadAuthorizationPolicies(String componentName, List policies, boolean isUpdate) { if (policies == null) { @@ -318,16 +307,17 @@ public void loadAuthorizationPolicies(String componentName, List operations = policy.getOperations(); if (Utils.isEmpty(operations)) { - throw new AuthorizationException("Malformed policy with invalid/empty operations: " - + policy.getPolicyId()); + throw new AuthorizationException("Malformed policy with invalid/empty operations: " + policy.getPolicyId()); } Set supportedOps = componentToOperationsMap.get(componentName); @@ -400,12 +391,11 @@ private void isComponentRegistered(String componentName) throws AuthorizationExc } } - private void isOperationValid(String componentName, String operation) - throws AuthorizationException { + private void isOperationValid(String componentName, String operation) throws AuthorizationException { isComponentRegistered(componentName); if (!componentToOperationsMap.get(componentName).contains(operation)) { - throw new AuthorizationException(String.format("Component %s not registered for operation %s", - componentName, operation)); + throw new AuthorizationException( + String.format("Component %s not registered for operation %s", componentName, operation)); } } @@ -422,8 +412,10 @@ private void validatePrincipals(AuthorizationPolicy policy) throws Authorization throw new AuthorizationException("Malformed policy with invalid/empty principal: " + policy.getPolicyId()); } // check if principal is a valid EG component - List unknownSources = principals.stream().filter(s -> !s.equals(ANY_REGEX)).filter(s -> - kernel.findServiceTopic(s) == null).collect(Collectors.toList()); + List unknownSources = principals.stream() + .filter(s -> !s.equals(ANY_REGEX)) + .filter(s -> kernel.findServiceTopic(s) == null) + .collect(Collectors.toList()); if (!unknownSources.isEmpty()) { throw new AuthorizationException( @@ -431,11 +423,8 @@ private void validatePrincipals(AuthorizationPolicy policy) throws Authorization } } - private void addPermission(String destination, - String policyId, - Set principals, - Set operations, - Set resources) throws AuthorizationException { + private void addPermission(String destination, String policyId, Set principals, Set operations, + Set resources) throws AuthorizationException { // Method assumes that all inputs are valid now for (String principal : principals) { for (String operation : operations) { @@ -452,14 +441,15 @@ private void addPermission(String destination, .resource(resource) .build()); } catch (AuthorizationException e) { - logger.atError("load-authorization-config-add-resource-error").setCause(e) + logger.atError("load-authorization-config-add-resource-error") + .setCause(e) .kv("policyId", policyId) .kv("component", principal) .kv("operation", operation) .kv("IPC service", destination) .kv("resource", resource) - .log("Error while adding permission for component {} " - + "to IPC Service {}", principal, destination); + .log("Error while adding permission for component {} " + "to IPC Service {}", + principal, destination); } } } @@ -469,15 +459,18 @@ private void addPermission(String destination, private List getDefaultPolicyForService(String serviceName) { String defaultPolicyDesc = "Default policy for " + serviceName; - return Collections.singletonList(AuthorizationPolicy.builder().policyId(UUID.randomUUID().toString()) - .policyDescription(defaultPolicyDesc).principals(new HashSet<>(Collections.singletonList("*"))) - .operations(new HashSet<>(Collections.singletonList(serviceName))).build()); + return Collections.singletonList(AuthorizationPolicy.builder() + .policyId(UUID.randomUUID().toString()) + .policyDescription(defaultPolicyDesc) + .principals(new HashSet<>(Collections.singletonList("*"))) + .operations(new HashSet<>(Collections.singletonList(serviceName))) + .build()); } private Map> getDefaultPolicies() { Map> allDefaultPolicies = new HashMap<>(); - //Create the default policy for TES + // Create the default policy for TES allDefaultPolicies.put(TOKEN_EXCHANGE_SERVICE_TOPICS, getDefaultPolicyForService(AUTHZ_TES_OPERATION)); return allDefaultPolicies; diff --git a/src/main/java/com/aws/greengrass/authorization/AuthorizationIPCAgent.java b/src/main/java/com/aws/greengrass/authorization/AuthorizationIPCAgent.java index 68b9121cbe..523ac8c64f 100644 --- a/src/main/java/com/aws/greengrass/authorization/AuthorizationIPCAgent.java +++ b/src/main/java/com/aws/greengrass/authorization/AuthorizationIPCAgent.java @@ -44,7 +44,8 @@ public ValidateAuthorizationTokenOperationHandler getValidateAuthorizationTokenO @SuppressWarnings("PMD.PreserveStackTrace") class ValidateAuthorizationTokenOperationHandler - extends GeneratedAbstractValidateAuthorizationTokenOperationHandler { + extends + GeneratedAbstractValidateAuthorizationTokenOperationHandler { private final String serviceName; protected ValidateAuthorizationTokenOperationHandler(OperationContinuationHandlerContext context) { @@ -65,8 +66,9 @@ public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { @Override public ValidateAuthorizationTokenResponse handleRequest(ValidateAuthorizationTokenRequest request) { if (!AUTHORIZED_COMPONENTS.contains(serviceName)) { - logger.atDebug("service-unauthorized-error").log("{} is not authorized to perform {}", - serviceName, this.getOperationModelContext().getOperationName()); + logger.atDebug("service-unauthorized-error") + .log("{} is not authorized to perform {}", serviceName, + this.getOperationModelContext().getOperationName()); throw new UnauthorizedError(String.format("%s is not authorized to perform %s", serviceName, this.getOperationModelContext().getOperationName())); } @@ -74,12 +76,14 @@ public ValidateAuthorizationTokenResponse handleRequest(ValidateAuthorizationTok try { authenticationHandler.doAuthentication(request.getToken()); response.setIsValid(true); - logger.atDebug("authorization-validated").log("Authorization validated for {} for {}", - serviceName, this.getOperationModelContext().getOperationName()); + logger.atDebug("authorization-validated") + .log("Authorization validated for {} for {}", serviceName, + this.getOperationModelContext().getOperationName()); return response; } catch (UnauthenticatedException e) { - logger.atDebug("invalid-token-error").log("Invalid token used when trying to authorize {} " - + "to perform {}", serviceName, this.getOperationModelContext().getOperationName()); + logger.atDebug("invalid-token-error") + .log("Invalid token used when trying to authorize {} " + "to perform {}", serviceName, + this.getOperationModelContext().getOperationName()); throw new InvalidTokenError(e.getMessage()); } } diff --git a/src/main/java/com/aws/greengrass/authorization/AuthorizationModule.java b/src/main/java/com/aws/greengrass/authorization/AuthorizationModule.java index fc0a69f8e9..c8d660949d 100644 --- a/src/main/java/com/aws/greengrass/authorization/AuthorizationModule.java +++ b/src/main/java/com/aws/greengrass/authorization/AuthorizationModule.java @@ -23,42 +23,43 @@ import static com.aws.greengrass.authorization.WildcardTrie.wildcardChar; /** - * Simple permission table which stores permissions. A permission is a - * 4 value set of destination,principal,operation,resource. + * Simple permission table which stores permissions. A permission is a 4 value set of + * destination,principal,operation,resource. */ public class AuthorizationModule { // Destination, Principal, Operation, Resource - Map>> resourceAuthZCompleteMap = - new DefaultConcurrentHashMap<>(() -> new DefaultConcurrentHashMap<>(() -> - new DefaultConcurrentHashMap<>(WildcardTrie::new))); + Map>> resourceAuthZCompleteMap = new DefaultConcurrentHashMap<>( + () -> new DefaultConcurrentHashMap<>(() -> new DefaultConcurrentHashMap<>(WildcardTrie::new))); Map>>> rawResourceList = new DefaultConcurrentHashMap<>( () -> new DefaultConcurrentHashMap<>(() -> new DefaultConcurrentHashMap<>(CopyOnWriteArraySet::new))); /** * Add permission for the given input set. + * * @param destination destination entity * @param permission set of principal, operation, resource. * @throws AuthorizationException when arguments are invalid */ public void addPermission(String destination, Permission permission) throws AuthorizationException { // resource is allowed to be null - if (Utils.isEmpty(permission.getPrincipal()) - || Utils.isEmpty(destination) + if (Utils.isEmpty(permission.getPrincipal()) || Utils.isEmpty(destination) || Utils.isEmpty(permission.getOperation())) { throw new AuthorizationException("Invalid arguments"); } String resource = permission.getResource(); validateResource(resource); - resourceAuthZCompleteMap.get(destination).get(permission.getPrincipal()).get(permission.getOperation()).add( - resource); - rawResourceList.get(destination).get(permission.getPrincipal()).get(permission.getOperation()).add( - resource); + resourceAuthZCompleteMap.get(destination) + .get(permission.getPrincipal()) + .get(permission.getOperation()) + .add(resource); + rawResourceList.get(destination).get(permission.getPrincipal()).get(permission.getOperation()).add(resource); } /** - * Only allow '?' if it's escaped. You can only escape special characters ('*', '$', '?'). - * Any occurrence of '${' is only valid if it holds a single valid special character ('*', '$', '?') inside it - * and ends with '}'. (eg: "${*}" is valid, "${c}" is invalid, "${c" is invalid, ${*bc} is invalid) + * Only allow '?' if it's escaped. You can only escape special characters ('*', '$', '?'). Any occurrence of '${' is + * only valid if it holds a single valid special character ('*', '$', '?') inside it and ends with '}'. (eg: "${*}" + * is valid, "${c}" is invalid, "${c" is invalid, ${*bc} is invalid) + * * @param resource resource to be validated */ private void validateResource(String resource) throws AuthorizationException { @@ -75,12 +76,12 @@ private void validateResource(String resource) throws AuthorizationException { if (currentChar == escapeChar && i + 1 < length && resource.charAt(i + 1) == '{') { char actualChar = getActualChar(resource.substring(i)); if (actualChar == nullChar) { - throw new AuthorizationException("Resource contains an invalid escape sequence. " - + "You can use ${*}, ${$}, or ${?}"); + throw new AuthorizationException( + "Resource contains an invalid escape sequence. " + "You can use ${*}, ${$}, or ${?}"); } if (!isSpecialChar(actualChar)) { - throw new AuthorizationException("Resource contains an invalid escape " - + "sequence: ${" + actualChar + "}. You can use ${*}, ${$}, or ${?}"); + throw new AuthorizationException("Resource contains an invalid escape " + "sequence: ${" + + actualChar + "}. You can use ${*}, ${$}, or ${?}"); } // skip next 3 characters as they are accounted for in escape sequence i = i + 3; @@ -96,9 +97,9 @@ boolean isSpecialChar(char actualChar) { return actualChar == wildcardChar || actualChar == escapeChar || actualChar == singleCharWildcard; } - /** * Clear the permission list for a given destination. This is used when updating policies for a component. + * * @param destination destination value */ public void deletePermissionsWithDestination(String destination) { @@ -108,6 +109,7 @@ public void deletePermissionsWithDestination(String destination) { /** * Check if the combination of destination,principal,operation,resource exists in the table. + * * @param destination destination value * @param permission set of principal, operation and resource. * @param resourceLookupPolicy whether to match MQTT wildcards or not. @@ -117,8 +119,7 @@ public void deletePermissionsWithDestination(String destination) { @SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts") public boolean isPresent(String destination, Permission permission, ResourceLookupPolicy resourceLookupPolicy) throws AuthorizationException { - if (Utils.isEmpty(permission.getPrincipal()) - || Utils.isEmpty(destination) + if (Utils.isEmpty(permission.getPrincipal()) || Utils.isEmpty(destination) || Utils.isEmpty(permission.getOperation())) { throw new AuthorizationException("Invalid arguments"); } @@ -132,8 +133,8 @@ public boolean isPresent(String destination, Permission permission, ResourceLook if (destMap.containsKey(permission.getPrincipal())) { Map principalMap = destMap.get(permission.getPrincipal()); if (principalMap.containsKey(permission.getOperation())) { - return principalMap.get(permission.getOperation()).matches(permission.getResource(), - resourceLookupPolicy); + return principalMap.get(permission.getOperation()) + .matches(permission.getResource(), resourceLookupPolicy); } } } @@ -145,19 +146,19 @@ public boolean isPresent(String destination, Permission permission) throws Autho } /** - * Get resources for combination of destination, principal and operation. - * Also returns resources covered by permissions with * operation/principal. + * Get resources for combination of destination, principal and operation. Also returns resources covered by + * permissions with * operation/principal. * * @param destination destination - * @param principal principal (cannot be *) - * @param operation operation (cannot be *) + * @param principal principal (cannot be *) + * @param operation operation (cannot be *) * @return list of allowed resources * @throws AuthorizationException when arguments are invalid */ public Set getResources(String destination, String principal, String operation) throws AuthorizationException { - if (Utils.isEmpty(destination) || Utils.isEmpty(principal) || Utils.isEmpty(operation) || principal - .equals(ANY_REGEX) || operation.equals(ANY_REGEX)) { + if (Utils.isEmpty(destination) || Utils.isEmpty(principal) || Utils.isEmpty(operation) + || principal.equals(ANY_REGEX) || operation.equals(ANY_REGEX)) { throw new AuthorizationException("Invalid arguments"); } diff --git a/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicy.java b/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicy.java index f5f86a2636..27b511a1a5 100644 --- a/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicy.java +++ b/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicy.java @@ -21,10 +21,13 @@ @AllArgsConstructor @NoArgsConstructor public class AuthorizationPolicy implements Comparable { - @NonNull String policyId; + @NonNull + String policyId; String policyDescription; - @NonNull Set principals; - @NonNull Set operations; + @NonNull + Set principals; + @NonNull + Set operations; Set resources; @Override diff --git a/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyConfig.java b/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyConfig.java index 62293e23ee..2b5dfaa0bb 100644 --- a/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyConfig.java +++ b/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyConfig.java @@ -21,7 +21,10 @@ @AllArgsConstructor @NoArgsConstructor public class AuthorizationPolicyConfig { - @NonNull String policyDescription; - @NonNull Set operations; - @NonNull Set resources; + @NonNull + String policyDescription; + @NonNull + Set operations; + @NonNull + Set resources; } diff --git a/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyParser.java b/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyParser.java index 5d01b6c18c..30862d16dd 100644 --- a/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyParser.java +++ b/src/main/java/com/aws/greengrass/authorization/AuthorizationPolicyParser.java @@ -32,11 +32,11 @@ public final class AuthorizationPolicyParser { private static final Logger logger = LogManager.getLogger(AuthorizationPolicyParser.class); private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); + /** - * Given a kernel object, construct and return a map of AuthorizationPolicy objects that may exist, - * grouped into lists of the same destination component. - * This is used only upon kernel startup, to initialize all policies. - * Never returns null. + * Given a kernel object, construct and return a map of AuthorizationPolicy objects that may exist, grouped into + * lists of the same destination component. This is used only upon kernel startup, to initialize all policies. Never + * returns null. * * @param kernel Kernel * @return {@Map} of {@String} keys and {@List} of {@AuthorizationPolicy}'s as values" @@ -52,7 +52,7 @@ public Map> parseAllAuthorizationPolicies(Kern return primaryAuthorizationPolicyMap; } - //For each component + // For each component for (Node service : allServices) { if (service == null) { @@ -66,24 +66,24 @@ public Map> parseAllAuthorizationPolicies(Kern Topics serviceConfig = (Topics) service; String componentName = Kernel.findServiceForNode(serviceConfig); - Node accessControlMapTopic = serviceConfig - .findNode(CONFIGURATION_CONFIG_KEY, ACCESS_CONTROL_NAMESPACE_TOPIC); + Node accessControlMapTopic = + serviceConfig.findNode(CONFIGURATION_CONFIG_KEY, ACCESS_CONTROL_NAMESPACE_TOPIC); if (accessControlMapTopic == null) { continue; } // Retrieve all policies, mapped to each policy type - Map> componentAuthorizationPolicyMap = parseAllPoliciesForComponent( - accessControlMapTopic, componentName); + Map> componentAuthorizationPolicyMap = + parseAllPoliciesForComponent(accessControlMapTopic, componentName); // For each policy type (e.g. aws.greengrass.ipc.pubsub) - for (Map.Entry> policyTypeList : - componentAuthorizationPolicyMap.entrySet()) { + for (Map.Entry> policyTypeList : componentAuthorizationPolicyMap + .entrySet()) { String policyType = policyTypeList.getKey(); List policyList = policyTypeList.getValue(); - //If multiple components have policies for the same policy type + // If multiple components have policies for the same policy type primaryAuthorizationPolicyMap.computeIfAbsent(policyType, k -> new ArrayList<>()).addAll(policyList); } } @@ -92,47 +92,27 @@ public Map> parseAllAuthorizationPolicies(Kern /** * Given a accessControlMapTopic Topic, construct and return a map of AuthorizationPolicy objects that may exist - * only for that component config, grouped into lists of the same destination component. - * Never returns null. + * only for that component config, grouped into lists of the same destination component. Never returns null. * * @param accessControlTopic Topic access control configuration * @param sourceComponent String the source component which has the access control config - * @return {@Map} of {@String} keys and {@List} of {@AuthorizationPolicy}'s as values" + * @return {@Map} of {@String} keys and {@List} of {@AuthorizationPolicy}'s as values" */ private Map> parseAllPoliciesForComponent(Node accessControlTopic, - String sourceComponent) { + String sourceComponent) { Map> authorizationPolicyMap = new HashMap<>(); Map> accessControlMap = new HashMap<>(); /* - Parse the config which is in the following format where first level key denotes the destination - component principal and second level denotes the policy object keyed by a unique ID - accessControl: - aws.greengrass.ipc.pubsub: - policyId1: - policyDescription: access to pubsub topics 1 - operations: - - publish - - subscribe - resources: - - /topic/1/# - - /longer/topic/example/ - policyId2: - policyDescription: access to pubsub topics 2 - operations: - - publish - resources: - - /publishOnlyTopic - aws.greengrass.secretsManager: - policyId3: - policyDescription: access to secrets - operations: - - getsecret - resources: - - secret1 - */ + * Parse the config which is in the following format where first level key denotes the destination component + * principal and second level denotes the policy object keyed by a unique ID accessControl: + * aws.greengrass.ipc.pubsub: policyId1: policyDescription: access to pubsub topics 1 operations: - publish - + * subscribe resources: - /topic/1/# - /longer/topic/example/ policyId2: policyDescription: access to pubsub + * topics 2 operations: - publish resources: - /publishOnlyTopic aws.greengrass.secretsManager: policyId3: + * policyDescription: access to secrets operations: - getsecret resources: - secret1 + */ try { if (accessControlTopic instanceof Topics) { - accessControlMap = OBJECT_MAPPER.convertValue(((Topics)accessControlTopic).toPOJO(), + accessControlMap = OBJECT_MAPPER.convertValue(((Topics) accessControlTopic).toPOJO(), new TypeReference>>() { }); } else if (accessControlTopic instanceof Topic) { @@ -146,7 +126,8 @@ private Map> parseAllPoliciesForComponent(Node sourceComponent); } } catch (IllegalArgumentException | IOException e) { - logger.atError("load-authorization-config-deserialization-error").setCause(e) + logger.atError("load-authorization-config-deserialization-error") + .setCause(e) .log("Unable to deserialize access control map {} for {}", accessControlTopic.toString(), sourceComponent); return authorizationPolicyMap; @@ -158,8 +139,8 @@ private Map> parseAllPoliciesForComponent(Node String destinationComponent = accessControl.getKey(); Map accessControlValue = accessControl.getValue(); - List newAuthorizationPolicyList = parseAuthorizationPolicyConfig( - sourceComponent, accessControlValue); + List newAuthorizationPolicyList = + parseAuthorizationPolicyConfig(sourceComponent, accessControlValue); authorizationPolicyMap.put(destinationComponent, newAuthorizationPolicyList); } @@ -167,15 +148,15 @@ private Map> parseAllPoliciesForComponent(Node } /** - * Given a destination specific ACL object, construct and return a List of AuthorizationPolicy objects - * that may exist. Never returns null. + * Given a destination specific ACL object, construct and return a List of AuthorizationPolicy objects that may + * exist. Never returns null. * - * @param componentName String name of the component which has the configuration - * @param accessControlConfig access control config for a specific destination + * @param componentName String name of the component which has the configuration + * @param accessControlConfig access control config for a specific destination * @return {@List} of {@AuthorizationPolicy}'s */ - private List - parseAuthorizationPolicyConfig(String componentName, Map accessControlConfig) { + private List parseAuthorizationPolicyConfig(String componentName, + Map accessControlConfig) { List newAuthorizationPolicyList = new ArrayList<>(); // Iterate through each policy @@ -183,8 +164,7 @@ private Map> parseAllPoliciesForComponent(Node AuthorizationPolicyConfig policyConfig = policyEntry.getValue(); if (Utils.isEmpty(policyConfig.getOperations())) { String errorMessage = "Policy operations are missing or invalid"; - logger.atError("load-authorization-missing-policy-component-operations") - .log(errorMessage); + logger.atError("load-authorization-missing-policy-component-operations").log(errorMessage); continue; } diff --git a/src/main/java/com/aws/greengrass/authorization/Permission.java b/src/main/java/com/aws/greengrass/authorization/Permission.java index a4a5a3696a..0c6315d3db 100644 --- a/src/main/java/com/aws/greengrass/authorization/Permission.java +++ b/src/main/java/com/aws/greengrass/authorization/Permission.java @@ -12,7 +12,9 @@ @Builder @Value public class Permission { - @NonNull String principal; - @NonNull String operation; + @NonNull + String principal; + @NonNull + String operation; String resource; } diff --git a/src/main/java/com/aws/greengrass/authorization/WildcardTrie.java b/src/main/java/com/aws/greengrass/authorization/WildcardTrie.java index 9295ad2ad3..2558d4a1da 100644 --- a/src/main/java/com/aws/greengrass/authorization/WildcardTrie.java +++ b/src/main/java/com/aws/greengrass/authorization/WildcardTrie.java @@ -12,15 +12,12 @@ import java.util.Map; /** - * A Wildcard trie node which contains properties to identify the Node and a map of all it's children. - * - isTerminal: If the node is a terminal node while adding a resource. It might not necessarily be a leaf node as we - * are adding multiple resources having same prefix but terminating on different points. - * - isTerminalLevel: If the node is the last level before a valid use "#" wildcard (eg: "abc/123/#", 123/ would be the - * terminalLevel). - * - isWildcard: If current Node is a valid glob wildcard (*) - * - isMQTTWildcard: If current Node is a valid MQTT wildcard (#, +) - * - matchAll: if current node should match everything. Could be MQTTWildcard or a wildcard and will always be a - * terminal Node. + * A Wildcard trie node which contains properties to identify the Node and a map of all it's children. - isTerminal: If + * the node is a terminal node while adding a resource. It might not necessarily be a leaf node as we are adding + * multiple resources having same prefix but terminating on different points. - isTerminalLevel: If the node is the last + * level before a valid use "#" wildcard (eg: "abc/123/#", 123/ would be the terminalLevel). - isWildcard: If current + * Node is a valid glob wildcard (*) - isMQTTWildcard: If current Node is a valid MQTT wildcard (#, +) - matchAll: if + * current node should match everything. Could be MQTTWildcard or a wildcard and will always be a terminal Node. */ public class WildcardTrie { protected static final String GLOB_WILDCARD = "*"; @@ -35,21 +32,18 @@ public class WildcardTrie { protected static final char singleLevelWildcardChar = MQTT_SINGLELEVEL_WILDCARD.charAt(0); protected static final char levelSeparatorChar = MQTT_LEVEL_SEPARATOR.charAt(0); - private boolean isTerminal; private boolean isTerminalLevel; private boolean isWildcard; private boolean isMQTTWildcard; private boolean matchAll; - private final Map children = - new DefaultConcurrentHashMap<>(WildcardTrie::new); + private final Map children = new DefaultConcurrentHashMap<>(WildcardTrie::new); /** - * Add allowed resources for a particular operation. - * - A new node is created for every occurrence of a wildcard (*, #, +). - * - Only nodes with valid usage of wildcards are marked with isWildcard or isMQTTWildcard. - * - Any other characters are grouped together to form a node. - * - Just a '*' or '#' creates a Node setting matchAll to true and would match all resources + * Add allowed resources for a particular operation. - A new node is created for every occurrence of a wildcard (*, + * #, +). - Only nodes with valid usage of wildcards are marked with isWildcard or isMQTTWildcard. - Any other + * characters are grouped together to form a node. - Just a '*' or '#' creates a Node setting matchAll to true and + * would match all resources * * @param subject resource pattern */ @@ -162,8 +156,8 @@ private WildcardTrie add(String subject, boolean isTerminal) { } /** - * The method tries to parse the given string using escape sequence ${c} (where c is a character to be escaped) - * and returns the character c if the pattern is matched. In any other scenario it returns null character ('\0') + * The method tries to parse the given string using escape sequence ${c} (where c is a character to be escaped) and + * returns the character c if the pattern is matched. In any other scenario it returns null character ('\0') * * @param str string provided to get */ @@ -183,7 +177,9 @@ static char getActualChar(String str) { * * @param str string to match. */ - @SuppressWarnings({"PMD.UselessParentheses", "PMD.CollapsibleIfStatements"}) + @SuppressWarnings({ + "PMD.UselessParentheses", "PMD.CollapsibleIfStatements" + }) public boolean matchesStandard(String str) { if (str == null) { return true; @@ -239,12 +235,14 @@ public boolean matchesStandard(String str) { } /** - * Match given string to the corresponding allowed resources trie. MQTT wildcards are processed only if - * its a valid usage, otherwise treated as normal characters. + * Match given string to the corresponding allowed resources trie. MQTT wildcards are processed only if its a valid + * usage, otherwise treated as normal characters. * * @param str string to match */ - @SuppressWarnings({"PMD.UselessParentheses", "PMD.CollapsibleIfStatements"}) + @SuppressWarnings({ + "PMD.UselessParentheses", "PMD.CollapsibleIfStatements" + }) public boolean matchesMQTT(String str) { if (str == null) { return true; @@ -269,9 +267,8 @@ public boolean matchesMQTT(String str) { WildcardTrie value = e.getValue(); // Process *, # and + wildcards (only process MQTT wildcards that have valid usages) - if ((value.isWildcard && key.equals(GLOB_WILDCARD)) - || (value.isMQTTWildcard && (key.equals(MQTT_SINGLELEVEL_WILDCARD) - || key.equals(MQTT_MULTILEVEL_WILDCARD)))) { + if ((value.isWildcard && key.equals(GLOB_WILDCARD)) || (value.isMQTTWildcard + && (key.equals(MQTT_SINGLELEVEL_WILDCARD) || key.equals(MQTT_MULTILEVEL_WILDCARD)))) { hasMatch = value.matchesMQTT(str); continue; } @@ -286,8 +283,8 @@ public boolean matchesMQTT(String str) { } // Check if it's terminalLevel to allow matching of string without "/" in the end - // "abc/#" should match "abc". - // "abc/*xy/#" should match "abc/12xy" + // "abc/#" should match "abc". + // "abc/*xy/#" should match "abc/12xy" String terminalKey = key.substring(0, key.length() - 1); if (value.isTerminalLevel) { if (str.equals(terminalKey)) { @@ -312,9 +309,8 @@ public boolean matchesMQTT(String str) { if (isMQTTWildcard) { int foundChildIndex = str.indexOf(key); // Matched characters inside + should not contain a "/" - while (foundChildIndex >= 0 - && foundChildIndex < str.length() - && (str.substring(0,foundChildIndex).indexOf(MQTT_LEVEL_SEPARATOR) == -1)) { + while (foundChildIndex >= 0 && foundChildIndex < str.length() + && (str.substring(0, foundChildIndex).indexOf(MQTT_LEVEL_SEPARATOR) == -1)) { matchingChildren.put(str.substring(foundChildIndex + keyLength), value); foundChildIndex = str.indexOf(key, foundChildIndex + 1); } @@ -332,7 +328,6 @@ public boolean matchesMQTT(String str) { } public boolean matches(String str, ResourceLookupPolicy lookupPolicy) { - return lookupPolicy == ResourceLookupPolicy.MQTT_STYLE ? matchesMQTT(str) - : matchesStandard(str); + return lookupPolicy == ResourceLookupPolicy.MQTT_STYLE ? matchesMQTT(str) : matchesStandard(str); } } diff --git a/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java b/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java index d593cd4e1f..4cfaf6ae5b 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java +++ b/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java @@ -72,16 +72,16 @@ public class ConfigStoreIPCEventStreamAgent { private static final String KEY_NOT_FOUND_ERROR_MESSAGE = "Key not found"; private static final String SERVICE_NAME = "service-name"; @Getter(AccessLevel.PACKAGE) - private final ConcurrentHashMap>> - configUpdateListeners = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> configUpdateListeners = + new ConcurrentHashMap<>(); @Getter(AccessLevel.PACKAGE) private final ConcurrentHashMap>> configValidationListeners = new ConcurrentHashMap<>(); // Map of component + deployment id --> future to complete with validation status received from service in response // to validate event @Getter(AccessLevel.PACKAGE) - private final Map, CompletableFuture> - configValidationReportFutures = new ConcurrentHashMap<>(); + private final Map, CompletableFuture> configValidationReportFutures = + new ConcurrentHashMap<>(); @Inject @Setter(AccessLevel.PACKAGE) @@ -112,7 +112,8 @@ public SendConfigurationValidityReportOperationHandler getSendConfigurationValid } class SendConfigurationValidityReportOperationHandler - extends GeneratedAbstractSendConfigurationValidityReportOperationHandler { + extends + GeneratedAbstractSendConfigurationValidityReportOperationHandler { private final String serviceName; protected SendConfigurationValidityReportOperationHandler(OperationContinuationHandlerContext context) { @@ -279,20 +280,18 @@ public UpdateConfigurationResponse handleRequest(UpdateConfigurationRequest requ Topics topics = configTopics.lookupTopics(keyPath); updateTime = topics.getModtime(); topics.updateFromMap((Map) value, - new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, - updateTime)); + new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, updateTime)); } else if (node instanceof Topic) { - Topic topic = (Topic)node; + Topic topic = (Topic) node; try { topic.parent.updateFromMap(Collections.singletonMap(topic.getName(), value), - new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, - updateTime)); + new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, updateTime)); } catch (IllegalArgumentException e) { throw new InvalidArgumentsError(e.getMessage()); } } else { - Topics topics = (Topics)node; - topics.updateFromMap((Map)value, + Topics topics = (Topics) node; + topics.updateFromMap((Map) value, new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, updateTime)); } Node updatedNode = configTopics.findNode(keyPath); @@ -320,11 +319,14 @@ private void validateRequest(UpdateConfigurationRequest request) { if (request.getKeyPath() != null) { keyPath = request.getKeyPath().toArray(new String[0]); } - if (keyPath.length == 0 && request.getValueToMerge().keySet().stream() - .anyMatch(key -> restrictedConfigurationFields.contains(key)) - || keyPath.length != 0 && restrictedConfigurationFields.contains(request.getKeyPath().get(0))) { - throw new InvalidArgumentsError("Config update is not allowed for following fields " - + restrictedConfigurationFields); + if (keyPath.length == 0 + && request.getValueToMerge() + .keySet() + .stream() + .anyMatch(key -> restrictedConfigurationFields.contains(key)) + || keyPath.length != 0 && restrictedConfigurationFields.contains(request.getKeyPath().get(0))) { + throw new InvalidArgumentsError( + "Config update is not allowed for following fields " + restrictedConfigurationFields); } Topics serviceTopics = kernel.findServiceTopic(serviceName); @@ -334,16 +336,18 @@ private void validateRequest(UpdateConfigurationRequest request) { Topics configTopics = serviceTopics.lookupTopics(CONFIGURATION_CONFIG_KEY); Node node = configTopics.findNode(keyPath); if (node != null && !(node instanceof Topic) && !(node instanceof Topics)) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError() + .kv(SERVICE_NAME, serviceName) .log("Somehow Node has an unknown type {}", node.getClass()); - throw new InvalidArgumentsError("Node corresponding to keypath " - + request.getKeyPath().toString() + " has an unknown type"); + throw new InvalidArgumentsError( + "Node corresponding to keypath " + request.getKeyPath().toString() + " has an unknown type"); } } } public class ConfigurationUpdateOperationHandler - extends GeneratedAbstractSubscribeToConfigurationUpdateOperationHandler { + extends + GeneratedAbstractSubscribeToConfigurationUpdateOperationHandler { private final String serviceName; private Node subscribedToNode; private Watcher subscribedToWatcher; @@ -356,7 +360,8 @@ public ConfigurationUpdateOperationHandler(OperationContinuationHandlerContext c @Override protected void onStreamClosed() { - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("Stream closed for subscribeToConfigurationUpdate for {}", serviceName); if (subscribedToNode != null) { subscribedToNode.remove(subscribedToWatcher); @@ -393,7 +398,8 @@ public SubscribeToConfigurationUpdateResponse handleRequest(SubscribeToConfigura throw new ResourceNotFoundError(KEY_NOT_FOUND_ERROR_MESSAGE); } - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("{} subscribed to configuration update", serviceName); subscribedToNode = subscribeTo; subscribedToWatcher = watcher.get(); @@ -415,8 +421,7 @@ public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { } private Optional registerWatcher(Node subscribeTo, String componentName) { - ChildChanged watcher = - (whatHappened, node) -> handleConfigNodeUpdate(whatHappened, node, componentName); + ChildChanged watcher = (whatHappened, node) -> handleConfigNodeUpdate(whatHappened, node, componentName); if (subscribeTo instanceof Topics) { ((Topics) subscribeTo).subscribe(watcher); @@ -430,8 +435,9 @@ private Optional registerWatcher(Node subscribeTo, String componentName private void handleConfigNodeUpdate(WhatHappened whatHappened, Node changedNode, String componentName) { // Blocks from sending an event on subscription, or events IPC subscriber isn't interested in knowing about - if (changedNode == null || WhatHappened.initialized.equals(whatHappened) || WhatHappened.timestampUpdated - .equals(whatHappened) || WhatHappened.interiorAdded.equals(whatHappened)) { + if (changedNode == null || WhatHappened.initialized.equals(whatHappened) + || WhatHappened.timestampUpdated.equals(whatHappened) + || WhatHappened.interiorAdded.equals(whatHappened)) { return; } // Avoid race conditions when subscribing to IPC and the subscription response hasn't been sent yet @@ -457,7 +463,8 @@ private Consumer sendConfigUpdateToListener(String componentName) { valueChangedEvent.setComponentName(componentName); valueChangedEvent.setKeyPath(Arrays.asList(changedKeyPath)); configurationUpdateEvents.setConfigurationUpdateEvent(valueChangedEvent); - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("Sending component {}'s updated config key {}", componentName, changedKeyPath); this.sendStreamEvent(configurationUpdateEvents); @@ -474,7 +481,8 @@ private Node getNodeToSubscribeTo(Topics configurationTopics, List keyPa } class ValidateConfigurationUpdatesOperationHandler - extends GeneratedAbstractSubscribeToValidateConfigurationUpdatesOperationHandler { + extends + GeneratedAbstractSubscribeToValidateConfigurationUpdatesOperationHandler { private final String serviceName; @@ -511,7 +519,8 @@ private BiConsumer> sendConfigValidationEvent() { validationEvent.setConfiguration(configuration); validationEvent.setDeploymentId(deploymentId); events.setValidateConfigurationUpdateEvent(validationEvent); - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("Requesting validation for component config {}", configuration); this.sendStreamEvent(events); @@ -523,16 +532,15 @@ private BiConsumer> sendConfigValidationEvent() { * Trigger a validate event to service/component, typically used during deployments. * * @param componentName service/component to send validate event to - * @param deploymentId deployment id which is being validated + * @param deploymentId deployment id which is being validated * @param configuration new component configuration to validate - * @param reportFuture future to track validation report in response to the event + * @param reportFuture future to track validation report in response to the event * @return true if the service has registered a validator, false if not * @throws ValidateEventRegistrationException throws when triggering requested validation event failed */ @SuppressWarnings("PMD.AvoidCatchingGenericException") public boolean validateConfiguration(String componentName, String deploymentId, Map configuration, - CompletableFuture reportFuture) - throws ValidateEventRegistrationException { + CompletableFuture reportFuture) throws ValidateEventRegistrationException { for (Map.Entry>> e : configValidationListeners.entrySet()) { if (e.getKey().equals(componentName)) { Pair componentToDeploymentId = new Pair<>(componentName, deploymentId); @@ -542,7 +550,7 @@ public boolean validateConfiguration(String componentName, String deploymentId, return true; } catch (Exception ex) { // TODO: [P41211196]: Retries, timeouts & and better exception handling in sending server event to - // components + // components configValidationReportFutures.remove(componentToDeploymentId); throw new ValidateEventRegistrationException(ex); } @@ -555,13 +563,13 @@ public boolean validateConfiguration(String componentName, String deploymentId, * Abandon tracking for report of configuration validation event. Can be used by the caller in the case of timeouts * or other errors. * - * @param deploymentId the deployment id which is being validated + * @param deploymentId the deployment id which is being validated * @param componentName component name to abandon validation for - * @param reportFuture tracking future for validation report to abandon + * @param reportFuture tracking future for validation report to abandon * @return true if abandon request was successful */ public boolean discardValidationReportTracker(String deploymentId, String componentName, - CompletableFuture reportFuture) { + CompletableFuture reportFuture) { return configValidationReportFutures.remove(new Pair<>(componentName, deploymentId), reportFuture); } } diff --git a/src/main/java/com/aws/greengrass/builtin/services/lifecycle/LifecycleIPCEventStreamAgent.java b/src/main/java/com/aws/greengrass/builtin/services/lifecycle/LifecycleIPCEventStreamAgent.java index 9aa98892df..d1c816519d 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/lifecycle/LifecycleIPCEventStreamAgent.java +++ b/src/main/java/com/aws/greengrass/builtin/services/lifecycle/LifecycleIPCEventStreamAgent.java @@ -71,12 +71,12 @@ public class LifecycleIPCEventStreamAgent { // Listeners registered from generic external components (through IPC client) @Getter(AccessLevel.PACKAGE) - private final ConcurrentHashMap>> - componentUpdateListeners = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> componentUpdateListeners = + new ConcurrentHashMap<>(); // Listeners registered from plugins - private final ConcurrentHashMap>> - componentUpdateListenersInternal = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> componentUpdateListenersInternal = + new ConcurrentHashMap<>(); // When a PreComponentUpdateEvent is pushed to components, a future is created for each component. When the // component responds with DeferComponentUpdateRequest the future is marked as complete. The caller of @@ -113,8 +113,7 @@ public PauseComponentHandler getPauseComponentHandler(OperationContinuationHandl return new PauseComponentHandler(context); } - public ResumeComponentHandler getResumeComponentHandler( - OperationContinuationHandlerContext context) { + public ResumeComponentHandler getResumeComponentHandler(OperationContinuationHandlerContext context) { return new ResumeComponentHandler(context); } @@ -162,9 +161,9 @@ public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { } - class SubscribeToComponentUpdateOperationHandler - extends GeneratedAbstractSubscribeToComponentUpdatesOperationHandler { + extends + GeneratedAbstractSubscribeToComponentUpdatesOperationHandler { private final String serviceName; @@ -192,7 +191,8 @@ public SubscribeToComponentUpdatesResponse handleRequest(SubscribeToComponentUpd componentUpdateListeners.get(serviceName).add(this); }); } catch (ServiceLoadException e) { - log.atWarn().kv(COMPONENT_NAME, serviceName) + log.atWarn() + .kv(COMPONENT_NAME, serviceName) .log("Got subscribe to component update request from a component that is" + " not found in Greengrass"); ResourceNotFoundError rnf = new ResourceNotFoundError(); @@ -212,17 +212,15 @@ public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { } - /** * Subscribe to component update events internally, e.g. from a plugin. * - * @param serviceName name of the service that is subscribing + * @param serviceName name of the service that is subscribing * @param updateEventCallback callback to invoke for sending update events * @throws ServiceLoadException when the requesting service cannot be located */ public void subscribeToComponentUpdateInternal(String serviceName, - Consumer updateEventCallback) - throws ServiceLoadException { + Consumer updateEventCallback) throws ServiceLoadException { subscribeToComponentUpdate(serviceName, () -> { componentUpdateListenersInternal.putIfAbsent(serviceName, new HashSet<>()); componentUpdateListenersInternal.get(serviceName).add(updateEventCallback); @@ -268,13 +266,13 @@ public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { /** * Defer a component update. * - * @param request DeferComponentUpdateRequest object + * @param request DeferComponentUpdateRequest object * @param serviceName nam of the service deferring the update * @throws InvalidArgumentsError if service name or deployment id inputs are invalid */ public void deferComponentUpdate(DeferComponentUpdateRequest request, String serviceName) { - if (!componentUpdateListeners.containsKey(serviceName) && !componentUpdateListenersInternal.containsKey( - serviceName)) { + if (!componentUpdateListeners.containsKey(serviceName) + && !componentUpdateListenersInternal.containsKey(serviceName)) { throw new InvalidArgumentsError("Component is not subscribed to component update events"); } if (request.getDeploymentId() == null) { @@ -286,8 +284,9 @@ public void deferComponentUpdate(DeferComponentUpdateRequest request, String ser if (deferComponentUpdateRequestFuture == null) { throw new ServiceError("Time limit to respond to PreComponentUpdateEvent exceeded"); } else { - log.atDebug().log("Processing deployment deferral from {} for deployment {}", serviceName, - request.getDeploymentId()); + log.atDebug() + .log("Processing deployment deferral from {} for deployment {}", serviceName, + request.getDeploymentId()); deferComponentUpdateRequestFuture.complete(request); } } @@ -323,7 +322,9 @@ public List> sendPreComponentUpdateEvent( subscribeHandler.sendStreamEvent(events) .get(DEFAULT_STREAM_MESSAGE_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (Exception e) { - log.atError().setCause(e).kv(COMPONENT_NAME, serviceName) + log.atError() + .setCause(e) + .kv(COMPONENT_NAME, serviceName) .log("Failed to send the pre component update on stream"); deferUpdateFuturesMap.remove(serviceAndDeployment); return; @@ -351,7 +352,9 @@ public List> sendPreComponentUpdateEvent( try { subscribeHandler.accept(events); } catch (Exception e) { - log.atError().setCause(e).kv(COMPONENT_NAME, serviceName) + log.atError() + .setCause(e) + .kv(COMPONENT_NAME, serviceName) .log("Failed to send the pre component update on stream"); deferUpdateFuturesMap.remove(serviceAndDeployment); return; @@ -364,7 +367,7 @@ public List> sendPreComponentUpdateEvent( } private ComponentUpdatePolicyEvents makePreUpdateEvents(String serviceName, - PreComponentUpdateEvent preComponentUpdateEvent) { + PreComponentUpdateEvent preComponentUpdateEvent) { log.atTrace().kv(COMPONENT_NAME, serviceName).log("Sending preComponentUpdate event"); ComponentUpdatePolicyEvents componentUpdatePolicyEvents = new ComponentUpdatePolicyEvents(); componentUpdatePolicyEvents.setPreUpdateEvent(preComponentUpdateEvent); @@ -372,7 +375,7 @@ private ComponentUpdatePolicyEvents makePreUpdateEvents(String serviceName, } private ComponentUpdatePolicyEvents makePostUpdateEvents(String serviceName, - PostComponentUpdateEvent postComponentUpdateEvent) { + PostComponentUpdateEvent postComponentUpdateEvent) { ComponentUpdatePolicyEvents componentUpdatePolicyEvents = new ComponentUpdatePolicyEvents(); log.atDebug().kv(COMPONENT_NAME, serviceName).log("Sending postComponentUpdate event"); componentUpdatePolicyEvents.setPostUpdateEvent(postComponentUpdateEvent); @@ -397,7 +400,9 @@ public void sendPostComponentUpdateEvent(PostComponentUpdateEvent postComponentU subscribeHandler.sendStreamEvent(events) .get(DEFAULT_STREAM_MESSAGE_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (Exception e) { - log.atError().setCause(e).kv(COMPONENT_NAME, serviceName) + log.atError() + .setCause(e) + .kv(COMPONENT_NAME, serviceName) .log("Failed to send the post component update on stream"); } }); @@ -413,7 +418,9 @@ public void sendPostComponentUpdateEvent(PostComponentUpdateEvent postComponentU try { subscribeHandler.accept(events); } catch (Exception e) { - log.atError().setCause(e).kv(COMPONENT_NAME, serviceName) + log.atError() + .setCause(e) + .kv(COMPONENT_NAME, serviceName) .log("Failed to send the post component update on stream"); } }); @@ -457,8 +464,7 @@ public PauseComponentResponse handleRequest(PauseComponentRequest request) { } try { - doAuthorization(this.getOperationModelContext().getOperationName(), serviceName, - componentName); + doAuthorization(this.getOperationModelContext().getOperationName(), serviceName, componentName); } catch (AuthorizationException e) { throw new UnauthorizedError(e.getMessage()); } @@ -482,8 +488,8 @@ public PauseComponentResponse handleRequest(PauseComponentRequest request) { try { target.pause(); } catch (ServiceException e) { - throw new ServiceError(String.format("Failed to pause component %s due to : %s", - componentName, e.getMessage())); + throw new ServiceError(String.format("Failed to pause component %s due to : %s", componentName, + e.getMessage())); } } else { throw new InvalidArgumentsError(String.format("Component %s is not running", componentName)); diff --git a/src/main/java/com/aws/greengrass/builtin/services/mqttproxy/MqttProxyIPCAgent.java b/src/main/java/com/aws/greengrass/builtin/services/mqttproxy/MqttProxyIPCAgent.java index 37c7f1ea23..5fce4ccbf2 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/mqttproxy/MqttProxyIPCAgent.java +++ b/src/main/java/com/aws/greengrass/builtin/services/mqttproxy/MqttProxyIPCAgent.java @@ -91,7 +91,9 @@ protected void onStreamClosed() { } - @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.AvoidInstanceofChecksInCatchClause"}) + @SuppressWarnings({ + "PMD.PreserveStackTrace", "PMD.AvoidInstanceofChecksInCatchClause" + }) @Override public PublishToIoTCoreResponse handleRequest(PublishToIoTCoreRequest request) { return translateExceptions(() -> { @@ -115,13 +117,16 @@ public PublishToIoTCoreResponse handleRequest(PublishToIoTCoreRequest request) { .correlationData(request.getCorrelationData()) .responseTopic(request.getResponseTopic()) .messageExpiryIntervalSeconds(request.getMessageExpiryIntervalSeconds()) - .userProperties(request.getUserProperties() == null ? null : - request.getUserProperties().stream() + .userProperties(request.getUserProperties() == null + ? null + : request.getUserProperties() + .stream() .map((u) -> new UserProperty(u.getKey(), u.getValue())) .collect(Collectors.toList())) .payloadFormat( request.getPayloadFormat() == null || request.getPayloadFormat() == PayloadFormat.BYTES - ? Publish.PayloadFormatIndicator.BYTES : Publish.PayloadFormatIndicator.UTF8) + ? Publish.PayloadFormatIndicator.BYTES + : Publish.PayloadFormatIndicator.UTF8) .build(); try { @@ -130,8 +135,8 @@ public PublishToIoTCoreResponse handleRequest(PublishToIoTCoreRequest request) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } - throw new ServiceError(String.format("Publish to topic %s failed: %s", topic, - Utils.getUltimateMessage(e))); + throw new ServiceError( + String.format("Publish to topic %s failed: %s", topic, Utils.getUltimateMessage(e))); } return new PublishToIoTCoreResponse(); @@ -174,7 +179,10 @@ protected void onStreamClosed() { return null; }); } catch (MqttRequestException e) { - LOGGER.atError().cause(e).kv(TOPIC_KEY, subscribedTopic).kv(COMPONENT_NAME, serviceName) + LOGGER.atError() + .cause(e) + .kv(TOPIC_KEY, subscribedTopic) + .kv(COMPONENT_NAME, serviceName) .log("Stream closed but unable to unsubscribe from topic"); } } @@ -185,7 +193,9 @@ public SubscribeToIoTCoreResponse handleRequest(SubscribeToIoTCoreRequest reques return null; } - @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException"}) + @SuppressWarnings({ + "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" + }) @Override public CompletableFuture handleRequestAsync(SubscribeToIoTCoreRequest request) { return translateExceptions(() -> { @@ -200,15 +210,17 @@ public CompletableFuture handleRequestAsync(Subscrib Consumer callback = this::forwardToSubscriber; com.aws.greengrass.mqttclient.v5.QOS qos = validateQoS(request.getQosAsString(), serviceName); - Subscribe subscribeRequest = Subscribe.builder().callback(callback).topic(topic) - .qos(qos).build(); + Subscribe subscribeRequest = Subscribe.builder().callback(callback).topic(topic).qos(qos).build(); try { subscribedTopic = topic; subscriptionCallback = callback; return mqttClient.subscribe(subscribeRequest).exceptionally((t) -> { - LOGGER.atError().cause(t).kv(TOPIC_KEY, topic).kv(COMPONENT_NAME, serviceName) + LOGGER.atError() + .cause(t) + .kv(TOPIC_KEY, topic) + .kv(COMPONENT_NAME, serviceName) .log("Unable to subscribe to topic"); throw new ServiceError(String.format("Subscribe to topic %s failed with error %s", topic, t)); }).thenApply((i) -> { @@ -221,15 +233,17 @@ public CompletableFuture handleRequestAsync(Subscrib } throw new ServiceError( - String.format("Subscribe to topic %s failed with error %s", topic, - rcString)) - .withContext(Utils.immutableMap("reasonString", i.getReasonString(), - "reasonCode", i.getReasonCode())); + String.format("Subscribe to topic %s failed with error %s", topic, rcString)) + .withContext(Utils.immutableMap("reasonString", i.getReasonString(), "reasonCode", + i.getReasonCode())); } return new SubscribeToIoTCoreResponse(); }); } catch (MqttRequestException e) { - LOGGER.atError().cause(e).kv(TOPIC_KEY, topic).kv(COMPONENT_NAME, serviceName) + LOGGER.atError() + .cause(e) + .kv(TOPIC_KEY, topic) + .kv(COMPONENT_NAME, serviceName) .log("Unable to subscribe to topic"); throw new ServiceError(String.format("Subscribe to topic %s failed with error %s", topic, e)); } @@ -247,20 +261,25 @@ public void afterHandleRequest() { } private void forwardToSubscriber(Publish m) { - IoTCoreMessage message = new IoTCoreMessage().withMessage( - new MQTTMessage().withTopicName(m.getTopic()).withPayload(m.getPayload()) - .withCorrelationData(m.getCorrelationData()) - .withMessageExpiryIntervalSeconds(m.getMessageExpiryIntervalSeconds()) - .withResponseTopic(m.getResponseTopic()).withRetain(m.isRetain()) - .withContentType(m.getContentType()) - .withPayloadFormat( - m.getPayloadFormat() == null - || m.getPayloadFormat() == Publish.PayloadFormatIndicator.BYTES - ? PayloadFormat.BYTES : PayloadFormat.UTF8).withUserProperties( - m.getUserProperties() == null ? null : m.getUserProperties().stream() - .map((u) -> new software.amazon.awssdk.aws.greengrass.model.UserProperty() - .withKey(u.getKey()).withValue(u.getValue())) - .collect(Collectors.toList()))); + IoTCoreMessage message = new IoTCoreMessage().withMessage(new MQTTMessage().withTopicName(m.getTopic()) + .withPayload(m.getPayload()) + .withCorrelationData(m.getCorrelationData()) + .withMessageExpiryIntervalSeconds(m.getMessageExpiryIntervalSeconds()) + .withResponseTopic(m.getResponseTopic()) + .withRetain(m.isRetain()) + .withContentType(m.getContentType()) + .withPayloadFormat( + m.getPayloadFormat() == null || m.getPayloadFormat() == Publish.PayloadFormatIndicator.BYTES + ? PayloadFormat.BYTES + : PayloadFormat.UTF8) + .withUserProperties(m.getUserProperties() == null + ? null + : m.getUserProperties() + .stream() + .map((u) -> new software.amazon.awssdk.aws.greengrass.model.UserProperty() + .withKey(u.getKey()) + .withValue(u.getValue())) + .collect(Collectors.toList()))); // Only allow forwarding messages if our initial response has been sent already. // If we don't do this, the callback may be invoked and send the streaming response @@ -268,8 +287,8 @@ private void forwardToSubscriber(Publish m) { if (subscriptionResponseSent.get()) { this.sendStreamEvent(message); } else { - LOGGER.warn("Not forwarding message on topic {} to {} " - + "because subscription response is not yet sent", + LOGGER.warn( + "Not forwarding message on topic {} to {} " + "because subscription response is not yet sent", m.getTopic(), serviceName); } } @@ -313,8 +332,7 @@ void doAuthorization(String opName, String serviceName, String topic) throws Aut AuthorizationHandler.ResourceLookupPolicy.MQTT_STYLE)) { return; } - throw new AuthorizationException( - String.format("Principal %s is not authorized to perform %s:%s on resource %s", serviceName, - MQTT_PROXY_SERVICE_NAME, opName, topic)); + throw new AuthorizationException(String.format("Principal %s is not authorized to perform %s:%s on resource %s", + serviceName, MQTT_PROXY_SERVICE_NAME, opName, topic)); } } diff --git a/src/main/java/com/aws/greengrass/builtin/services/pubsub/PubSubIPCEventStreamAgent.java b/src/main/java/com/aws/greengrass/builtin/services/pubsub/PubSubIPCEventStreamAgent.java index 36a8585093..d84d4b3351 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/pubsub/PubSubIPCEventStreamAgent.java +++ b/src/main/java/com/aws/greengrass/builtin/services/pubsub/PubSubIPCEventStreamAgent.java @@ -58,7 +58,7 @@ public class PubSubIPCEventStreamAgent { @Inject PubSubIPCEventStreamAgent(AuthorizationHandler authorizationHandler, - OrderedExecutorService orderedExecutorService) { + OrderedExecutorService orderedExecutorService) { this.authorizationHandler = authorizationHandler; this.orderedExecutorService = orderedExecutorService; } @@ -74,8 +74,8 @@ public PublishToTopicOperationHandler getPublishToTopicHandler(OperationContinua /** * Handle the subscription request from internal plugin services. * - * @param topic topic name. - * @param cb callback to be called for each published message + * @param topic topic name. + * @param cb callback to be called for each published message * @param serviceName name of the service subscribing. */ public void subscribe(String topic, Consumer cb, String serviceName) { @@ -96,8 +96,8 @@ public void subscribe(SubscribeRequest subscribeRequest) { /** * Unsubscribe from a topic for internal plugin services. * - * @param topic topic name. - * @param cb callback to remove from subscription + * @param topic topic name. + * @param cb callback to remove from subscription * @param serviceName name of the service unsubscribing. */ public void unsubscribe(String topic, Consumer cb, String serviceName) { @@ -118,9 +118,9 @@ public void unsubscribe(SubscribeRequest subscribeRequest) { /** * Publish a message to all subscribers. * - * @param topic publish topic. + * @param topic publish topic. * @param binaryMessage Binary message to publish. - * @param serviceName name of the service publishing the message. + * @param serviceName name of the service publishing the message. * @return response */ public PublishToTopicResponse publish(String topic, byte[] binaryMessage, String serviceName) { @@ -129,8 +129,7 @@ public PublishToTopicResponse publish(String topic, byte[] binaryMessage, String @SuppressWarnings("PMD.PreserveStackTrace") private PublishToTopicResponse handlePublishToTopicRequest(String topic, String serviceName, - Optional> jsonMessage, - Optional binaryMessage) { + Optional> jsonMessage, Optional binaryMessage) { if (topic == null) { throw new InvalidArgumentsError("Publish topic must not be null"); } @@ -147,9 +146,10 @@ private PublishToTopicResponse handlePublishToTopicRequest(String topic, String Set cbs = new HashSet<>(); contexts.forEach(context -> { // With RECEIVE_MESSAGES_FROM_OTHERS mode, message will not be sent back to its source component. - if (serviceName.equals(context.getSourceComponent()) && ReceiveMode.RECEIVE_MESSAGES_FROM_OTHERS - .equals(context.getReceiveMode())) { - log.atTrace().kv(COMPONENT_NAME, serviceName) + if (serviceName.equals(context.getSourceComponent()) + && ReceiveMode.RECEIVE_MESSAGES_FROM_OTHERS.equals(context.getReceiveMode())) { + log.atTrace() + .kv(COMPONENT_NAME, serviceName) .log("Message will not be sent back on topic {} in {} mode", topic, context.getReceiveMode().getValue()); } else { @@ -197,9 +197,8 @@ private void handleSubscribeToTopicRequest(SubscribeRequest subscribeRequest) { // TODO: [P32540011]: All IPC service requests need input validation String topic = subscribeRequest.getTopic(); validateSubTopic(topic); - SubscriptionCallback subscriptionCallback = - convertToSubscriptionCallback(topic, subscribeRequest.getServiceName(), - subscribeRequest.getReceiveMode(), subscribeRequest.getCallback()); + SubscriptionCallback subscriptionCallback = convertToSubscriptionCallback(topic, + subscribeRequest.getServiceName(), subscribeRequest.getReceiveMode(), subscribeRequest.getCallback()); if (listeners.add(subscribeRequest.getTopic(), subscriptionCallback)) { log.atDebug().kv(COMPONENT_NAME, subscribeRequest.getServiceName()).log("Subscribed to topic {}", topic); } @@ -207,11 +206,11 @@ private void handleSubscribeToTopicRequest(SubscribeRequest subscribeRequest) { private void handleUnsubscribeToTopicRequest(SubscribeRequest subscribeRequest) { String topic = subscribeRequest.getTopic(); - SubscriptionCallback subscriptionCallback = - convertToSubscriptionCallback(topic, subscribeRequest.getServiceName(), - subscribeRequest.getReceiveMode(), subscribeRequest.getCallback()); + SubscriptionCallback subscriptionCallback = convertToSubscriptionCallback(topic, + subscribeRequest.getServiceName(), subscribeRequest.getReceiveMode(), subscribeRequest.getCallback()); if (listeners.remove(topic, subscriptionCallback)) { - log.atDebug().kv(COMPONENT_NAME, subscribeRequest.getServiceName()) + log.atDebug() + .kv(COMPONENT_NAME, subscribeRequest.getServiceName()) .log("Unsubscribed from topic {}", topic); } } @@ -252,7 +251,6 @@ public PublishToTopicResponse handleRequest(PublishToTopicRequest publishRequest }); } - @Override public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { // NA @@ -288,8 +286,12 @@ public SubscribeToTopicResponse handleRequest(SubscribeToTopicRequest subscribeR throw new UnauthorizedError(e.getMessage()); } subscribeTopic = subscribeRequest.getTopic(); - request = SubscribeRequest.builder().topic(subscribeTopic).serviceName(serviceName) - .receiveMode(subscribeRequest.getReceiveMode()).callback(this).build(); + request = SubscribeRequest.builder() + .topic(subscribeTopic) + .serviceName(serviceName) + .receiveMode(subscribeRequest.getReceiveMode()) + .callback(this) + .build(); handleSubscribeToTopicRequest(request); return new SubscribeToTopicResponse(); }); @@ -321,7 +323,7 @@ private ReceiveMode validateReceiveMode(String topic, ReceiveMode receiveMode) { } private SubscriptionCallback convertToSubscriptionCallback(String topic, String serviceName, - ReceiveMode receiveMode, Object handler) { + ReceiveMode receiveMode, Object handler) { ReceiveMode validatedReceiveMode = validateReceiveMode(topic, receiveMode); return new SubscriptionCallback(serviceName, validatedReceiveMode, handler); } diff --git a/src/main/java/com/aws/greengrass/builtin/services/pubsub/SubscriptionTrie.java b/src/main/java/com/aws/greengrass/builtin/services/pubsub/SubscriptionTrie.java index 1be22dfc9b..87bdcefc1d 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/pubsub/SubscriptionTrie.java +++ b/src/main/java/com/aws/greengrass/builtin/services/pubsub/SubscriptionTrie.java @@ -57,7 +57,7 @@ public boolean containsKey(String topic) { * Remove entry for one callback. * * @param topic topic - * @param cb callback + * @param cb callback * @return if changed after removal */ public boolean remove(String topic, K cb) { @@ -68,7 +68,7 @@ public boolean remove(String topic, K cb) { * Remove entry for a set of callbacks. * * @param topic topic - * @param cbs callbacks + * @param cbs callbacks * @return if changed after removal */ public boolean remove(String topic, Set cbs) { @@ -87,17 +87,17 @@ private boolean canRemove(SubscriptionTrie node) { } /* - This method removes the requested callback from a topic and prunes the subscription trie recursively by - 1. navigating to the last node of the requested topic - 1.a. at this node, the method persists the result of the requested callback removal for the penultimate result - 1.b. returns true if requested callback was removed and if current topic node can be pruned - - 2. the previous topic node receives the result from 1.b. - 2.a. if true, remove child topic node and return if current node can be pruned - 2.b. if false, simply do nothing and return false implying current node cannot be pruned + * This method removes the requested callback from a topic and prunes the subscription trie recursively by 1. + * navigating to the last node of the requested topic 1.a. at this node, the method persists the result of the + * requested callback removal for the penultimate result 1.b. returns true if requested callback was removed and if + * current topic node can be pruned + * + * 2. the previous topic node receives the result from 1.b. 2.a. if true, remove child topic node and return if + * current node can be pruned 2.b. if false, simply do nothing and return false implying current node cannot be + * pruned */ private boolean removeRecursively(String[] topicNodes, SubscriptionTrie topicNode, Set cbs, int index, - AtomicBoolean subscriptionRemoved) { + AtomicBoolean subscriptionRemoved) { if (index == topicNodes.length) { subscriptionRemoved.set(topicNode.subscriptionCallbacks.removeAll(cbs)); return subscriptionRemoved.get() && canRemove(topicNode); @@ -128,7 +128,7 @@ public int size() { * Add a topic callback. * * @param topic topic - * @param cb callback + * @param cb callback * @return true */ public boolean add(String topic, K cb) { @@ -139,7 +139,7 @@ public boolean add(String topic, K cb) { * Add a topic and a set of callbacks. * * @param topic topic - * @param cbs callbacks + * @param cbs callbacks */ public boolean add(String topic, Set cbs) { SubscriptionTrie current = this; @@ -195,8 +195,8 @@ public Set get(String topic) { } /** - * Return whether a topic contains MQTT style wildcard. - * If true, + and # must occupy an entire level and # must be the last character. + * Return whether a topic contains MQTT style wildcard. If true, + and # must occupy an entire level and # must be + * the last character. * * @param topic topic * @return whether the topic is wildcard @@ -218,4 +218,3 @@ public static boolean isWildcard(String topic) { } } - diff --git a/src/main/java/com/aws/greengrass/builtin/services/telemetry/ComponentMetricIPCEventStreamAgent.java b/src/main/java/com/aws/greengrass/builtin/services/telemetry/ComponentMetricIPCEventStreamAgent.java index d242739a96..bfed09da0e 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/telemetry/ComponentMetricIPCEventStreamAgent.java +++ b/src/main/java/com/aws/greengrass/builtin/services/telemetry/ComponentMetricIPCEventStreamAgent.java @@ -75,11 +75,14 @@ protected void onStreamClosed() { // NA } - @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException"}) + @SuppressWarnings({ + "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" + }) @Override public PutComponentMetricResponse handleRequest(PutComponentMetricRequest componentMetricRequest) { return translateExceptions(() -> { - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("Received putComponentMetricRequest from component " + serviceName); // Authorize service name for given operation @@ -87,22 +90,25 @@ public PutComponentMetricResponse handleRequest(PutComponentMetricRequest compon try { doServiceAuthorization(opName, serviceName); } catch (AuthorizationException e) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError() + .kv(SERVICE_NAME, serviceName) .log("{} is not authorized to perform operation", serviceName); throw new UnauthorizedError(e.getMessage()); } - //validate - metric name length, value is non negative etc etc + // validate - metric name length, value is non negative etc etc List metricList = componentMetricRequest.getMetrics(); try { validateComponentMetricRequest(opName, serviceName, metricList); } catch (IllegalArgumentException e) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError() + .kv(SERVICE_NAME, serviceName) .log("invalid component metric request from {}", serviceName); throw new InvalidArgumentsError(e.getMessage()); } catch (AuthorizationException e) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError() + .kv(SERVICE_NAME, serviceName) .log("{} is not authorized to perform operation", serviceName); throw new UnauthorizedError(e.getMessage()); } @@ -112,11 +118,13 @@ public PutComponentMetricResponse handleRequest(PutComponentMetricRequest compon final String metricNamespace = serviceName; translateAndEmit(metricList, metricNamespace); } catch (IllegalArgumentException e) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError() + .kv(SERVICE_NAME, serviceName) .log("invalid component metric request from {}", serviceName); throw new InvalidArgumentsError(e.getMessage()); } catch (Exception ex) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError() + .kv(SERVICE_NAME, serviceName) .log("error while emitting metrics from {}", serviceName); throw new ServiceError(ex.getMessage()); } @@ -132,25 +140,26 @@ public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { // Translate request metrics to telemetry metrics and emit them private void translateAndEmit(List componentMetrics, - String metricNamespace) { + String metricNamespace) { final MetricFactory metricFactory = metricFactoryMap.computeIfAbsent(metricNamespace, k -> new MetricFactory(metricNamespace)); componentMetrics.forEach(metric -> { - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("Translating component metric to Telemetry metric" + metric.getName()); Metric telemetryMetric = getTelemetryMetric(metric, metricNamespace); - logger.atDebug().kv(SERVICE_NAME, serviceName) + logger.atDebug() + .kv(SERVICE_NAME, serviceName) .log("Publish Telemetry metric" + telemetryMetric.getName()); metricFactory.putMetricData(telemetryMetric); }); } } - // Creates telemetry metric object for given request metric private Metric getTelemetryMetric(software.amazon.awssdk.aws.greengrass.model.Metric metric, - String metricNamespace) { + String metricNamespace) { return Metric.builder() .namespace(metricNamespace) .name(metric.getName()) @@ -181,8 +190,7 @@ private TelemetryUnit valueOfIgnoreCase(String unitAsString) { // throw IllegalArgumentException if request params are invalid // throw AuthorizationException if request params don't match access control policy private void validateComponentMetricRequest(String opName, String serviceName, - List metrics) - throws AuthorizationException { + List metrics) throws AuthorizationException { if (Utils.isEmpty(metrics)) { throw new IllegalArgumentException( String.format("Null or Empty list of metrics found in PutComponentMetricRequest")); @@ -201,14 +209,14 @@ private void validateComponentMetricRequest(String opName, String serviceName, // throws AuthorizationException if not authorized private void doMetricAuthorization(String opName, String serviceName, String metricName) throws AuthorizationException { - if (AUTHORIZED_COMPONENTS.contains(serviceName) || authorizationHandler - .isAuthorized(PUT_COMPONENT_METRIC_SERVICE_NAME, Permission.builder() - .operation(opName).principal(serviceName).resource(metricName).build())) { + if (AUTHORIZED_COMPONENTS.contains(serviceName) + || authorizationHandler.isAuthorized(PUT_COMPONENT_METRIC_SERVICE_NAME, + Permission.builder().operation(opName).principal(serviceName).resource(metricName).build())) { return; } throw new AuthorizationException( String.format("Principal %s is not authorized to perform %s:%s with metric name %s", serviceName, - PUT_COMPONENT_METRIC_SERVICE_NAME, opName, metricName)); + PUT_COMPONENT_METRIC_SERVICE_NAME, opName, metricName)); } // Validate if serviceName is of format "aws.*" @@ -218,6 +226,6 @@ private void doServiceAuthorization(String opName, String serviceName) throws Au return; } throw new AuthorizationException(String.format("Principal %s is not authorized to perform %s:%s ", serviceName, - PUT_COMPONENT_METRIC_SERVICE_NAME, opName)); + PUT_COMPONENT_METRIC_SERVICE_NAME, opName)); } } diff --git a/src/main/java/com/aws/greengrass/componentmanager/ClientConfigurationUtils.java b/src/main/java/com/aws/greengrass/componentmanager/ClientConfigurationUtils.java index f2caddfc23..76fd25ff6d 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/ClientConfigurationUtils.java +++ b/src/main/java/com/aws/greengrass/componentmanager/ClientConfigurationUtils.java @@ -107,8 +107,7 @@ public static ApacheHttpClient.Builder getConfiguredClientBuilder(DeviceConfigur } private static void configureClientMutualTLS(ApacheHttpClient.Builder httpBuilder, - DeviceConfiguration deviceConfiguration) - throws TLSAuthException { + DeviceConfiguration deviceConfiguration) throws TLSAuthException { String rootCAPath = Coerce.toString(deviceConfiguration.getRootCAFilePath()); if (Utils.isEmpty(rootCAPath)) { return; diff --git a/src/main/java/com/aws/greengrass/componentmanager/ComponentManager.java b/src/main/java/com/aws/greengrass/componentmanager/ComponentManager.java index 2b273ce2ea..ae164524e5 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/ComponentManager.java +++ b/src/main/java/com/aws/greengrass/componentmanager/ComponentManager.java @@ -98,11 +98,12 @@ public class ComponentManager implements InjectionActions { private final NucleusPaths nucleusPaths; // Setter for unit tests @Setter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig clientExceptionRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1)) - .maxRetryInterval(Duration.ofMinutes(1)).maxAttempt(Integer.MAX_VALUE) - .retryableExceptions(Arrays.asList(SdkClientException.class, - RetryableServerErrorException.class)).build(); + private RetryUtils.RetryConfig clientExceptionRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1)) + .maxRetryInterval(Duration.ofMinutes(1)) + .maxAttempt(Integer.MAX_VALUE) + .retryableExceptions(Arrays.asList(SdkClientException.class, RetryableServerErrorException.class)) + .build(); @Inject @Setter @@ -112,19 +113,19 @@ public class ComponentManager implements InjectionActions { * PackageManager constructor. * * @param artifactDownloaderFactory artifactDownloaderFactory - * @param componentServiceHelper greengrassPackageServiceHelper - * @param executorService executorService - * @param componentStore componentStore - * @param kernel kernel - * @param unarchiver unarchiver - * @param deviceConfiguration deviceConfiguration - * @param nucleusPaths path library + * @param componentServiceHelper greengrassPackageServiceHelper + * @param executorService executorService + * @param componentStore componentStore + * @param kernel kernel + * @param unarchiver unarchiver + * @param deviceConfiguration deviceConfiguration + * @param nucleusPaths path library */ @Inject public ComponentManager(ArtifactDownloaderFactory artifactDownloaderFactory, - ComponentServiceHelper componentServiceHelper, ExecutorService executorService, - ComponentStore componentStore, Kernel kernel, Unarchiver unarchiver, - DeviceConfiguration deviceConfiguration, NucleusPaths nucleusPaths) { + ComponentServiceHelper componentServiceHelper, ExecutorService executorService, + ComponentStore componentStore, Kernel kernel, Unarchiver unarchiver, + DeviceConfiguration deviceConfiguration, NucleusPaths nucleusPaths) { this.artifactDownloaderFactory = artifactDownloaderFactory; this.componentServiceHelper = componentServiceHelper; this.executorService = executorService; @@ -137,15 +138,19 @@ public ComponentManager(ArtifactDownloaderFactory artifactDownloaderFactory, ComponentMetadata resolveComponentVersion(String componentName, Map versionRequirements) throws InterruptedException, PackagingException { - logger.atDebug().setEventType("resolve-component-version-start").kv(COMPONENT_STR, componentName) - .kv("versionRequirements", versionRequirements).log("Resolving component version starts"); + logger.atDebug() + .setEventType("resolve-component-version-start") + .kv(COMPONENT_STR, componentName) + .kv("versionRequirements", versionRequirements) + .log("Resolving component version starts"); // Find best local candidate Optional localCandidateOptional = findBestCandidateLocally(componentName, versionRequirements); if (localCandidateOptional.isPresent()) { - logger.atInfo().kv("LocalCandidateId", localCandidateOptional.get()) + logger.atInfo() + .kv("LocalCandidateId", localCandidateOptional.get()) .log("Found the best local candidate that satisfies the requirement."); } else { logger.atInfo().log("Can't find a local candidate that satisfies the requirement."); @@ -154,16 +159,18 @@ ComponentMetadata resolveComponentVersion(String componentName, Map versionRequirements, - ComponentIdentifier localCandidate) + Map versionRequirements, ComponentIdentifier localCandidate) throws PackagingException, InterruptedException { // Special handling for Nucleus component - skip download if same version if (DEFAULT_NUCLEUS_COMPONENT_NAME.equals(componentName) && localCandidate != null) { String currentNucleusVersion = deviceConfiguration.getNucleusVersion(); if (localCandidate.getVersion().toString().equals(currentNucleusVersion)) { - logger.atInfo().kv(COMPONENT_NAME, componentName) + logger.atInfo() + .kv(COMPONENT_NAME, componentName) .kv("version", currentNucleusVersion) .log("Skipping Nucleus download as the same version is already installed"); return localCandidate; @@ -225,18 +242,22 @@ private ComponentIdentifier negotiateVersionWithCloud(String componentName, VendorGuidance vendorGuidance = resolvedComponentVersion.vendorGuidance(); if (VendorGuidance.DISCONTINUED.equals(vendorGuidance)) { - logger.atWarn().kv(COMPONENT_NAME, componentName) + logger.atWarn() + .kv(COMPONENT_NAME, componentName) .kv("componentVersion", resolvedComponentVersion.componentVersion()) - .kv("versionRequirements", versionRequirements).log("This component version has been" - + " discontinued by its publisher. You can deploy this component version, but we" - + " recommend that you use a different version of this component"); + .kv("versionRequirements", versionRequirements) + .log("This component version has been" + + " discontinued by its publisher. You can deploy this component version, but we" + + " recommend that you use a different version of this component"); } } catch (InterruptedException e) { throw e; } catch (NoAvailableComponentVersionException e) { // Don't bother logging the full stacktrace when it is NoAvailableComponentVersionException since we // know the reason for that error - logger.atError().kv(COMPONENT_NAME, componentName).kv("versionRequirement", versionRequirements) + logger.atError() + .kv(COMPONENT_NAME, componentName) + .kv("versionRequirement", versionRequirements) .log("Failed to negotiate version with cloud and no local version to fall back to"); // If it is NoAvailableComponentVersionException then we do not need to set the cause, because we @@ -255,14 +276,16 @@ private ComponentIdentifier negotiateVersionWithCloud(String componentName, } } else { try { - resolvedComponentVersion = componentServiceHelper - .resolveComponentVersion(componentName, localCandidate.getVersion(), versionRequirements); + resolvedComponentVersion = componentServiceHelper.resolveComponentVersion(componentName, + localCandidate.getVersion(), versionRequirements); } catch (Exception e) { // Don't bother logging the full stacktrace when it is NoAvailableComponentVersionException since we // know the reason for that error - logger.atInfo().setCause(e instanceof NoAvailableComponentVersionException ? null : e) + logger.atInfo() + .setCause(e instanceof NoAvailableComponentVersionException ? null : e) .kv(COMPONENT_NAME, componentName) - .kv("versionRequirement", versionRequirements).kv("localVersion", localCandidate) + .kv("versionRequirement", versionRequirements) + .kv("localVersion", localCandidate) .log("Failed to negotiate version with cloud and fall back to use the local version"); return localCandidate; } @@ -288,20 +311,22 @@ private ComponentIdentifier negotiateVersionWithCloud(String componentName, return resolvedComponentId; } - private void storeRecipeDigestInConfigStoreForPlugin( com.amazon.aws.iot.greengrass.component.common.ComponentRecipe componentRecipe, String recipeContent) throws HashingAlgorithmUnavailableException { ComponentIdentifier componentIdentifier = new ComponentIdentifier(componentRecipe.getComponentName(), componentRecipe.getComponentVersion()); if (componentRecipe.getComponentType() != ComponentType.PLUGIN) { - logger.atDebug().kv(COMPONENT_STR, componentIdentifier) + logger.atDebug() + .kv(COMPONENT_STR, componentIdentifier) .log("Skip storing digest as component is not plugin"); return; } try { String digest = Digest.calculate(recipeContent); - kernel.getMain().getRuntimeConfig().lookup(Kernel.SERVICE_DIGEST_TOPIC_KEY, componentIdentifier.toString()) + kernel.getMain() + .getRuntimeConfig() + .lookup(Kernel.SERVICE_DIGEST_TOPIC_KEY, componentIdentifier.toString()) .withValue(digest); logger.atDebug().kv(COMPONENT_STR, componentIdentifier).kv("digest", digest).log("Saved plugin digest"); } catch (NoSuchAlgorithmException e) { @@ -322,29 +347,30 @@ public List unArchiveCurrentNucleusVersionArtifacts() throws PackageLoadin new ComponentIdentifier(DEFAULT_NUCLEUS_COMPONENT_NAME, new Semver(currentNucleusVersion)); List nucleusArtifactFileNames = componentStore.getArtifactFiles(nucleusComponentIdentifier, artifactDownloaderFactory); - return nucleusArtifactFileNames.stream() - .map(file -> { - try { - Path unarchivePath = - nucleusPaths.unarchiveArtifactPath(nucleusComponentIdentifier, getFileName(file)); - /* - Using a hard-coded ZIP un-archiver as today this code path is only used to un-archive a Nucleus - .zip artifact. - */ - unarchiver.unarchive(Unarchive.ZIP, file, unarchivePath); - return unarchivePath; - } catch (IOException e) { - logger.atDebug().setCause(e).kv("comp-id", nucleusComponentIdentifier) - .log("Could not un-archive Nucleus artifact"); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); + return nucleusArtifactFileNames.stream().map(file -> { + try { + Path unarchivePath = nucleusPaths.unarchiveArtifactPath(nucleusComponentIdentifier, getFileName(file)); + /* + * Using a hard-coded ZIP un-archiver as today this code path is only used to un-archive a Nucleus .zip + * artifact. + */ + unarchiver.unarchive(Unarchive.ZIP, file, unarchivePath); + return unarchivePath; + } catch (IOException e) { + logger.atDebug() + .setCause(e) + .kv("comp-id", nucleusComponentIdentifier) + .log("Could not un-archive Nucleus artifact"); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); } private Optional findBestCandidateLocally(String componentName, - Map versionRequirements) - throws PackagingException { - logger.atDebug().kv("ComponentName", componentName).kv("VersionRequirements", versionRequirements) + Map versionRequirements) throws PackagingException { + logger.atDebug() + .kv("ComponentName", componentName) + .kv("VersionRequirements", versionRequirements) .log("Searching for best candidate locally on the device."); Requirement req = mergeVersionRequirements(versionRequirements); @@ -353,7 +379,8 @@ private Optional findBestCandidateLocally(String componentN // use active one if compatible, otherwise check local available ones if (optionalActiveComponentId.isPresent()) { - logger.atInfo().kv("ComponentIdentifier", optionalActiveComponentId.get()) + logger.atInfo() + .kv("ComponentIdentifier", optionalActiveComponentId.get()) .log("Found running component which meets the requirement and use it."); return optionalActiveComponentId; @@ -395,15 +422,17 @@ public Future preparePackages(List pkgIds) { return executorService.submit(() -> { for (ComponentIdentifier componentIdentifier : pkgIds) { if (Thread.currentThread().isInterrupted()) { - logger.atInfo().log("Interrupted while preparing artifact for component {}.", - componentIdentifier.getName()); + logger.atInfo() + .log("Interrupted while preparing artifact for component {}.", + componentIdentifier.getName()); return null; } try { preparePackage(componentIdentifier); } catch (InterruptedException ie) { - logger.atInfo().log("Interrupted while preparing artifact for component {}.", - componentIdentifier.getName()); + logger.atInfo() + .log("Interrupted while preparing artifact for component {}.", + componentIdentifier.getName()); return null; } } @@ -412,12 +441,12 @@ public Future preparePackages(List pkgIds) { } /** - * Check if all plugins that are required to execute pre-merge steps for other components are included - * in the deployment. + * Check if all plugins that are required to execute pre-merge steps for other components are included in the + * deployment. * * @param componentIds deployment dependency closure * @throws MissingRequiredComponentsException when any required plugins are not included - * @throws PackageLoadingException when other errors occur + * @throws PackageLoadingException when other errors occur */ public void checkPreparePackagesPrerequisites(List componentIds) throws MissingRequiredComponentsException, PackageLoadingException { @@ -426,16 +455,16 @@ public void checkPreparePackagesPrerequisites(List componen if (!recipeOption.isPresent()) { throw new PackageLoadingException( String.format("Unexpected error - cannot find recipe for a component to be prepared - %s", - componentId), DeploymentErrorCode.LOCAL_RECIPE_NOT_FOUND); + componentId), + DeploymentErrorCode.LOCAL_RECIPE_NOT_FOUND); } - artifactDownloaderFactory - .checkDownloadPrerequisites(recipeOption.get().getArtifacts(), componentId, componentIds); + artifactDownloaderFactory.checkDownloadPrerequisites(recipeOption.get().getArtifacts(), componentId, + componentIds); } } - private void preparePackage(ComponentIdentifier componentIdentifier) - throws PackageLoadingException, PackageDownloadException, InvalidArtifactUriException, - InterruptedException { + private void preparePackage(ComponentIdentifier componentIdentifier) throws PackageLoadingException, + PackageDownloadException, InvalidArtifactUriException, InterruptedException { logger.atInfo().setEventType("prepare-package-start").kv(PACKAGE_IDENTIFIER, componentIdentifier).log(); try { ComponentRecipe pkg = componentStore.getPackageRecipe(componentIdentifier); @@ -455,22 +484,26 @@ void prepareArtifacts(ComponentIdentifier componentIdentifier, List errorMsg = downloader.checkDownloadable(); if (errorMsg.isPresent()) { @@ -493,8 +526,8 @@ void prepareArtifacts(ComponentIdentifier componentIdentifier, List getConfiguredMaxSize()) { throw new SizeLimitException(String.format( "Component store size limit reached: %d bytes existing, %d bytes needed" - + ", %d bytes maximum allowed total", storeContentSize, downloadSize, - getConfiguredMaxSize())); + + ", %d bytes maximum allowed total", + storeContentSize, downloadSize, getConfiguredMaxSize())); } } try { @@ -516,8 +549,8 @@ void prepareArtifacts(ComponentIdentifier componentIdentifier, List findActiveVersion(final String packageName) { GreengrassService service = kernel.locate(packageName); return Optional.ofNullable(getPackageVersionFromService(service)); } catch (ServiceLoadException e) { - logger.atDebug().addKeyValue(PACKAGE_NAME_KEY, packageName) + logger.atDebug() + .addKeyValue(PACKAGE_NAME_KEY, packageName) .log("Didn't find an active service for this package running in the Nucleus."); return Optional.empty(); } @@ -649,7 +686,7 @@ Semver getPackageVersionFromService(final GreengrassService service) { } private Optional findActiveAndSatisfiedComponent(String componentName, - Requirement requirement) { + Requirement requirement) { Optional activeVersionOptional = findActiveVersion(componentName); return activeVersionOptional.filter(requirement::isSatisfiedBy) diff --git a/src/main/java/com/aws/greengrass/componentmanager/ComponentServiceHelper.java b/src/main/java/com/aws/greengrass/componentmanager/ComponentServiceHelper.java index a547fa1fb0..3b7e4e5b71 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/ComponentServiceHelper.java +++ b/src/main/java/com/aws/greengrass/componentmanager/ComponentServiceHelper.java @@ -45,8 +45,7 @@ public class ComponentServiceHelper { private final PlatformResolver platformResolver; @Inject - public ComponentServiceHelper(GreengrassServiceClientFactory clientFactory, - PlatformResolver platformResolver) { + public ComponentServiceHelper(GreengrassServiceClientFactory clientFactory, PlatformResolver platformResolver) { this.clientFactory = clientFactory; this.platformResolver = platformResolver; } @@ -55,37 +54,44 @@ public ComponentServiceHelper(GreengrassServiceClientFactory clientFactory, * Resolve a component version with greengrass cloud service. The dependency resolution algorithm goes through the * dependencies node by node, so one component got resolve a time. * - * @param componentName component name to be resolve - * @param localCandidateVersion component local candidate version if available - * @param versionRequirements component dependents version requirement map + * @param componentName component name to be resolve + * @param localCandidateVersion component local candidate version if available + * @param versionRequirements component dependents version requirement map * @return resolved component version and recipe * @throws NoAvailableComponentVersionException if no applicable version available in cloud service * @throws Exception when not able to retrieve greengrassV2DataClient */ - @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.SignatureDeclareThrowsException"}) + @SuppressWarnings({ + "PMD.PreserveStackTrace", "PMD.SignatureDeclareThrowsException" + }) ResolvedComponentVersion resolveComponentVersion(String componentName, Semver localCandidateVersion, Map versionRequirements) throws NoAvailableComponentVersionException, Exception { - ComponentPlatform platform = ComponentPlatform.builder() - .attributes(platformResolver.getCurrentPlatform()).build(); - Map versionRequirementsInString = versionRequirements.entrySet().stream() + ComponentPlatform platform = + ComponentPlatform.builder().attributes(platformResolver.getCurrentPlatform()).build(); + Map versionRequirementsInString = versionRequirements.entrySet() + .stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString())); - ComponentCandidate candidate = ComponentCandidate.builder().componentName(componentName) + ComponentCandidate candidate = ComponentCandidate.builder() + .componentName(componentName) .componentVersion(localCandidateVersion == null ? null : localCandidateVersion.getValue()) - .versionRequirements(versionRequirementsInString).build(); + .versionRequirements(versionRequirementsInString) + .build(); ResolveComponentCandidatesRequest request = ResolveComponentCandidatesRequest.builder() .platform(platform) - .componentCandidates(Collections.singletonList(candidate)).build(); + .componentCandidates(Collections.singletonList(candidate)) + .build(); ResolveComponentCandidatesResponse result; Duration retryInterval = TestFeatureParameters.retrieveWithDefault(Duration.class, - CLIENT_RETRY_INTERVAL_MILLIS_FEATURE, - Duration.ofSeconds(30)); - RetryUtils.RetryConfig clientExceptionRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(retryInterval) - .maxRetryInterval(retryInterval).maxAttempt(CLIENT_RETRY_COUNT) - .retryableExceptions(Arrays.asList(DeviceConfigurationException.class)).build(); + CLIENT_RETRY_INTERVAL_MILLIS_FEATURE, Duration.ofSeconds(30)); + RetryUtils.RetryConfig clientExceptionRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(retryInterval) + .maxRetryInterval(retryInterval) + .maxAttempt(CLIENT_RETRY_COUNT) + .retryableExceptions(Arrays.asList(DeviceConfigurationException.class)) + .build(); try (GreengrassV2DataClient greengrassV2DataClient = RetryUtils.runWithRetry(clientExceptionRetryConfig, clientFactory::fetchGreengrassV2DataClient, "get-greengrass-v2-data-client", logger)) { @@ -93,29 +99,37 @@ ResolvedComponentVersion resolveComponentVersion(String componentName, Semver lo } catch (ResourceNotFoundException e) { if (e.getMessage() == null) { throw new NoAvailableComponentVersionException("No cloud component version satisfies the requirements.", - componentName, versionRequirements); + componentName, versionRequirements); } String message = e.getMessage(); if (message.contains("claim platform")) { - logger.atDebug().kv("componentName", componentName).kv("versionRequirements", versionRequirements) + logger.atDebug() + .kv("componentName", componentName) + .kv("versionRequirements", versionRequirements) .log("The version of component requested does not claim platform compatibility", e); - throw new IncompatiblePlatformClaimByComponentException("The version of component requested does not" - + " claim platform compatibility.", componentName, platformResolver.getCurrentPlatform()); + throw new IncompatiblePlatformClaimByComponentException( + "The version of component requested does not" + " claim platform compatibility.", componentName, + platformResolver.getCurrentPlatform()); } else if (message.contains("no usable version")) { - logger.atDebug().kv("componentName", componentName).kv("versionRequirements", versionRequirements) + logger.atDebug() + .kv("componentName", componentName) + .kv("versionRequirements", versionRequirements) .log("No applicable version found in cloud registry", e); throw new NoAvailableComponentVersionException("No cloud component version satisfies the requirements.", componentName, versionRequirements); } else { - logger.atDebug().kv("componentName", componentName).kv("versionRequirements", versionRequirements) + logger.atDebug() + .kv("componentName", componentName) + .kv("versionRequirements", versionRequirements) .log(e.getMessage(), e); throw new NoAvailableComponentVersionException(e.getMessage(), componentName, versionRequirements); } } catch (GreengrassV2DataException e) { if (RetryUtils.retryErrorCodes(e.statusCode())) { - throw new RetryableServerErrorException("Failed with retryable error " + e.statusCode() - + " when calling resolveComponentCandidates", e); + throw new RetryableServerErrorException( + "Failed with retryable error " + e.statusCode() + " when calling resolveComponentCandidates", + e); } throw e; } diff --git a/src/main/java/com/aws/greengrass/componentmanager/ComponentStore.java b/src/main/java/com/aws/greengrass/componentmanager/ComponentStore.java index 963617f0f1..17bbb8d3b1 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/ComponentStore.java +++ b/src/main/java/com/aws/greengrass/componentmanager/ComponentStore.java @@ -63,8 +63,8 @@ public class ComponentStore { private static final Logger logger = LogManager.getLogger(ComponentStore.class); private static final String LOG_KEY_RECIPE_METADATA_FILE_PATH = "RecipeMetadataFilePath"; - private static final String LOG_METADATA_INVALID = "Ignoring the local recipe metadata file and proceeding with " - + "dependency resolution"; + private static final String LOG_METADATA_INVALID = + "Ignoring the local recipe metadata file and proceeding with " + "dependency resolution"; private static final String RECIPE_SUFFIX = ".recipe"; private final NucleusPaths nucleusPaths; @@ -74,9 +74,9 @@ public class ComponentStore { /** * Constructor. It will initialize recipe, artifact and artifact decompressed directory. * - * @param nucleusPaths path library + * @param nucleusPaths path library * @param platformResolver platform resolver - * @param recipeLoader recipe loader + * @param recipeLoader recipe loader */ @Inject public ComponentStore(NucleusPaths nucleusPaths, PlatformResolver platformResolver, RecipeLoader recipeLoader) { @@ -88,9 +88,9 @@ public ComponentStore(NucleusPaths nucleusPaths, PlatformResolver platformResolv /** * Save the given component recipe object into component store on the disk. * - *

If the target recipe file exist, and its content is the same as the content to be written, it skip the - * file write operation. - * If content is different or the target recipe file does not exist, it will write to the file using YAML + *

+ * If the target recipe file exist, and its content is the same as the content to be written, it skip the file write + * operation. If content is different or the target recipe file does not exist, it will write to the file using YAML * serializer. *

* @@ -120,15 +120,15 @@ String saveComponentRecipe(@NonNull com.amazon.aws.iot.greengrass.component.comm return recipeContent; } catch (IOException e) { // TODO: [P41215929]: Better logging and exception messages in component store - throw new PackageLoadingException("Failed to save package recipe", e) - .withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR); + throw new PackageLoadingException("Failed to save package recipe", e).withErrorContext(e, + DeploymentErrorCode.IO_WRITE_ERROR); } } /** * Creates or updates a package recipe in the package store on the disk. * - * @param componentId the id for the component + * @param componentId the id for the component * @param recipeContent recipe content to save * @throws PackageLoadingException if fails to write the package recipe to disk. */ @@ -160,21 +160,20 @@ Optional findPackageRecipe(@NonNull ComponentIdentifier pkgId) * Validate whether given digest matches the component recipe on disk. * * @param componentIdentifier component whose recipe is read from disk - * @param expectedDigest expected digest for the recipe + * @param expectedDigest expected digest for the recipe * @return whether the expected digest matches the calculated digest on disk * @throws PackageLoadingException if unable to load recipe from disk */ public boolean validateComponentRecipeDigest(@NonNull ComponentIdentifier componentIdentifier, - String expectedDigest) throws PackageLoadingException { + String expectedDigest) throws PackageLoadingException { Optional recipeContent = findComponentRecipeContent(componentIdentifier); if (!recipeContent.isPresent()) { throw new PackageLoadingException("Recipe not found for component " + componentIdentifier.getName()); } String recipeContentStr = recipeContent.get(); if (Utils.isEmpty(recipeContentStr)) { - throw new PackageLoadingException( - String.format("Found empty recipe for component %s. File was likely corrupted", - componentIdentifier.getName())); + throw new PackageLoadingException(String.format( + "Found empty recipe for component %s. File was likely corrupted", componentIdentifier.getName())); } try { String digest = Digest.calculate(recipeContentStr); @@ -200,8 +199,8 @@ Optional findComponentRecipeContent(@NonNull ComponentIdentifier compone return Optional.of(new String(Files.readAllBytes(recipePath), StandardCharsets.UTF_8)); } catch (IOException e) { throw new PackageLoadingException( - String.format("Failed to read package recipe from disk with path: `%s`", recipePath), - e).withErrorContext(e, DeploymentErrorCode.IO_READ_ERROR); + String.format("Failed to read package recipe from disk with path: `%s`", recipePath), e) + .withErrorContext(e, DeploymentErrorCode.IO_READ_ERROR); } } @@ -217,8 +216,8 @@ public ComponentRecipe getPackageRecipe(@NonNull ComponentIdentifier pkgId) thro if (!optionalPackage.isPresent()) { // TODO: [P41215929]: Better logging and exception messages in component store - throw new PackageLoadingException(String.format( - "Failed to find usable recipe for current platform: %s, for package: '%s' in the " + throw new PackageLoadingException( + String.format("Failed to find usable recipe for current platform: %s, for package: '%s' in the " + "local package store", platformResolver.getCurrentPlatform(), pkgId), DeploymentErrorCode.LOCAL_RECIPE_NOT_FOUND); } @@ -233,7 +232,7 @@ public ComponentRecipe getPackageRecipe(@NonNull ComponentIdentifier pkgId) thro * @throws PackageLoadingException if deletion of the component failed */ void deleteComponent(@NonNull ComponentIdentifier compId, - @NonNull ArtifactDownloaderFactory artifactDownloaderFactory) + @NonNull ArtifactDownloaderFactory artifactDownloaderFactory) throws PackageLoadingException, InvalidArtifactUriException { logger.atDebug("delete-component-start").kv("componentIdentifier", compId).log(); IOException exception = null; @@ -245,8 +244,8 @@ void deleteComponent(@NonNull ComponentIdentifier compId, ComponentRecipe recipe = getPackageRecipe(compId); Path packageArtifactDirectory = resolveArtifactDirectoryPath(compId); for (ComponentArtifact artifact : recipe.getArtifacts()) { - ArtifactDownloader downloader = artifactDownloaderFactory - .getArtifactDownloader(compId, artifact, packageArtifactDirectory); + ArtifactDownloader downloader = + artifactDownloaderFactory.getArtifactDownloader(compId, artifact, packageArtifactDirectory); try { downloader.cleanup(); } catch (IOException e) { @@ -322,8 +321,7 @@ ComponentMetadata getPackageMetadata(@NonNull ComponentIdentifier pkgId) throws } Optional findBestMatchAvailableComponent(@NonNull String componentName, - @NonNull Requirement requirement) - throws PackageLoadingException { + @NonNull Requirement requirement) throws PackageLoadingException { List componentIdentifierList = listAvailableComponent(componentName, requirement); if (componentIdentifierList.isEmpty()) { @@ -335,19 +333,21 @@ Optional findBestMatchAvailableComponent(@NonNull String co /** * List available component (versions) that satisfies the requirement in descending order. + * * @param componentName target component's name - * @param requirement semver requirement + * @param requirement semver requirement * @return component id list contains all satisfied version, in descending order - * @throws PackageLoadingException when fails to read recipe directory or parse recipe file name + * @throws PackageLoadingException when fails to read recipe directory or parse recipe file name */ List listAvailableComponent(@NonNull String componentName, @NonNull Requirement requirement) throws PackageLoadingException { String componentNameHash = getHashOfComponentName(componentName); // target file name: {hash}@{semver}.recipe.yaml - File[] recipeFilesOfAllVersions = nucleusPaths.recipePath().toFile().listFiles( - (dir, name) -> name.endsWith(RECIPE_SUFFIX + FileSuffix.YAML_SUFFIX) && name - .startsWith(componentNameHash)); + File[] recipeFilesOfAllVersions = nucleusPaths.recipePath() + .toFile() + .listFiles((dir, name) -> name.endsWith(RECIPE_SUFFIX + FileSuffix.YAML_SUFFIX) + && name.startsWith(componentNameHash)); if (recipeFilesOfAllVersions == null || recipeFilesOfAllVersions.length == 0) { return new ArrayList<>(); @@ -409,8 +409,8 @@ public Path resolveArtifactDirectoryPath(@NonNull ComponentIdentifier componentI try { return nucleusPaths.artifactPath(componentIdentifier); } catch (IOException e) { - throw new PackageLoadingException("Unable to create artifact path", e) - .withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR); + throw new PackageLoadingException("Unable to create artifact path", e).withErrorContext(e, + DeploymentErrorCode.IO_WRITE_ERROR); } } @@ -423,8 +423,7 @@ public Path resolveArtifactDirectoryPath(@NonNull ComponentIdentifier componentI * @throws PackageLoadingException if creating the directory fails */ public List getArtifactFiles(@NonNull ComponentIdentifier componentIdentifier, - @NonNull ArtifactDownloaderFactory artifactDownloaderFactory) - throws PackageLoadingException { + @NonNull ArtifactDownloaderFactory artifactDownloaderFactory) throws PackageLoadingException { Optional componentRecipeContent = findComponentRecipeContent(componentIdentifier); if (!componentRecipeContent.isPresent()) { return Collections.emptyList(); @@ -438,8 +437,7 @@ public List getArtifactFiles(@NonNull ComponentIdentifier componentIdentif .getArtifactDownloader(componentIdentifier, artifact, packageArtifactDirectory) .getArtifactFile(); } catch (PackageLoadingException | InvalidArtifactUriException e) { - logger.atDebug().setCause(e).kv("comp-id", componentRecipeContent) - .log("Could not get artifact file"); + logger.atDebug().setCause(e).kv("comp-id", componentRecipeContent).log("Could not get artifact file"); return null; } }).filter(Objects::nonNull).collect(Collectors.toList()); @@ -469,13 +467,15 @@ public Path resolveRecipePath(@NonNull ComponentIdentifier componentIdentifier) */ public long getContentSize() throws PackageLoadingException { try { - try (LongStream lengths = Files.walk(nucleusPaths.componentStorePath()).map(Path::toFile) - .filter(File::isFile).mapToLong(File::length)) { + try (LongStream lengths = Files.walk(nucleusPaths.componentStorePath()) + .map(Path::toFile) + .filter(File::isFile) + .mapToLong(File::length)) { return lengths.sum(); } } catch (IOException e) { - throw new PackageLoadingException("Failed to access package store", e) - .withErrorContext(e, DeploymentErrorCode.IO_FILE_ATTRIBUTE_ERROR); + throw new PackageLoadingException("Failed to access package store", e).withErrorContext(e, + DeploymentErrorCode.IO_FILE_ATTRIBUTE_ERROR); } } @@ -513,7 +513,7 @@ private static Semver parseVersionFromRecipeFileName(String recipeFilename) thro * Saves recipe metadata to file. Overrides if the target file exists. * * @param componentIdentifier component id - * @param recipeMetadata metadata for the recipe + * @param recipeMetadata metadata for the recipe * @throws PackageLoadingException when failed write recipe metadata to file system. */ public void saveRecipeMetadata(ComponentIdentifier componentIdentifier, RecipeMetadata recipeMetadata) @@ -523,12 +523,14 @@ public void saveRecipeMetadata(ComponentIdentifier componentIdentifier, RecipeMe try { SerializerFactory.getFailSafeJsonObjectMapper().writeValue(metadataFile, recipeMetadata); } catch (IOException e) { - logger.atError().cause(e).kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) + logger.atError() + .cause(e) + .kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) .log("Failed to write recipe metadata file"); throw new PackageLoadingException( - String.format("Failed to write recipe metadata to file: '%s'", metadataFile.getAbsolutePath()), - e).withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR); + String.format("Failed to write recipe metadata to file: '%s'", metadataFile.getAbsolutePath()), e) + .withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR); } } @@ -557,15 +559,18 @@ public boolean componentMetadataRegionCheck(ComponentIdentifier localCandidate, // region matches return true; } else { - logger.atWarn().kv("componentName", localCandidate.toString()) - .kv("expectedRegion", region).kv("foundRegion", arnRegion.get()) + logger.atWarn() + .kv("componentName", localCandidate.toString()) + .kv("expectedRegion", region) + .kv("foundRegion", arnRegion.get()) .kv("metadataPath", metadataFile.getAbsolutePath()) .log("Component version arn in recipe metadata contains a different region from " + "nucleus config. " + LOG_METADATA_INVALID); return false; } } else { - logger.atWarn().kv("componentName", localCandidate.toString()) + logger.atWarn() + .kv("componentName", localCandidate.toString()) .kv("metadataPath", metadataFile.getAbsolutePath()) .log("Invalid region value for component version arn in recipe metadata. " + LOG_METADATA_INVALID); @@ -581,7 +586,9 @@ public boolean componentMetadataRegionCheck(ComponentIdentifier localCandidate, logger.atWarn().setCause(e).log("Failed to read metadata. " + LOG_METADATA_INVALID); } catch (IllegalArgumentException e) { // Failed to parse the Arn string - logger.atWarn().kv("componentName", localCandidate.toString()).setCause(e) + logger.atWarn() + .kv("componentName", localCandidate.toString()) + .setCause(e) .kv("metadataPath", metadataFile.getAbsolutePath()) .log("Failed to parse the component version arn in recipe metadata. " + LOG_METADATA_INVALID); } @@ -602,44 +609,50 @@ public RecipeMetadata getRecipeMetadata(ComponentIdentifier componentIdentifier) private RecipeMetadata getRecipeMetadata(File metadataFile) throws PackageLoadingException { if (!metadataFile.exists()) { // this may happen if it's a locally installed component and has no metadata - logger.atDebug().kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) + logger.atDebug() + .kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) .log("Recipe metadata file doesn't exist"); - throw new PackageLoadingException(String.format( - "Recipe metadata file doesn't exist. RecipeMetadataFilePath: '%s'", metadataFile.getAbsolutePath()), + throw new PackageLoadingException( + String.format("Recipe metadata file doesn't exist. RecipeMetadataFilePath: '%s'", + metadataFile.getAbsolutePath()), DeploymentErrorCode.LOCAL_RECIPE_METADATA_NOT_FOUND); } if (!metadataFile.isFile()) { - logger.atError().kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) + logger.atError() + .kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) .log("Failed to get recipe metadata because the path resolved to a folder"); - throw new PackageLoadingException(String.format( - "Failed to get recipe metadata because the path resolved to a folder. " + throw new PackageLoadingException( + String.format("Failed to get recipe metadata because the path resolved to a folder. " + "RecipeMetadataFilePath: '%s'", metadataFile.getAbsolutePath()), DeploymentErrorCode.LOCAL_RECIPE_METADATA_NOT_FOUND); } - try { return SerializerFactory.getFailSafeJsonObjectMapper().readValue(metadataFile, RecipeMetadata.class); } catch (JsonProcessingException e) { // log error because this is not expected to happen in any normal case - logger.atError().cause(e).kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) + logger.atError() + .cause(e) + .kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) .log("Recipe metadata is not valid JSON"); - throw new PackageLoadingException(String.format( - "Recipe metadata is not valid JSON: '%s'", metadataFile.getAbsolutePath()), e) + throw new PackageLoadingException( + String.format("Recipe metadata is not valid JSON: '%s'", metadataFile.getAbsolutePath()), e) .withErrorContext(e, DeploymentErrorCode.RECIPE_METADATA_PARSE_ERROR); } catch (IOException e) { // log error because this is not expected to happen in any normal case - logger.atError().cause(e).kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) + logger.atError() + .cause(e) + .kv(LOG_KEY_RECIPE_METADATA_FILE_PATH, metadataFile.getAbsolutePath()) .log("I/O error while reading recipe metadata"); - throw new PackageLoadingException(String.format( - "I/O error while reading recipe metadata. RecipeMetadataFilePath: '%s'", - metadataFile.getAbsolutePath()), e) - .withErrorContext(e, DeploymentErrorCode.IO_READ_ERROR); + throw new PackageLoadingException( + String.format("I/O error while reading recipe metadata. RecipeMetadataFilePath: '%s'", + metadataFile.getAbsolutePath()), + e).withErrorContext(e, DeploymentErrorCode.IO_READ_ERROR); } } diff --git a/src/main/java/com/aws/greengrass/componentmanager/DependencyResolver.java b/src/main/java/com/aws/greengrass/componentmanager/DependencyResolver.java index 1046b4adda..cf8e5d761b 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/DependencyResolver.java +++ b/src/main/java/com/aws/greengrass/componentmanager/DependencyResolver.java @@ -66,17 +66,16 @@ public class DependencyResolver { * conflicts between the components specified in the deployment document and the existing running components on the * device. * - * @param document deployment document + * @param document deployment document * @param otherGroupsToRootComponents root components associated with other groups * @return a list of components to be run on the device * @throws NoAvailableComponentVersionException no version of the component can fulfill the deployment - * @throws PackagingException for other component operation errors - * @throws InterruptedException InterruptedException + * @throws PackagingException for other component operation errors + * @throws InterruptedException InterruptedException */ @SuppressWarnings("PMD.AvoidCatchingGenericException") public List resolveDependencies(DeploymentDocument document, - Map> - otherGroupsToRootComponents) + Map> otherGroupsToRootComponents) throws NoAvailableComponentVersionException, PackagingException, InterruptedException { // A map of component version constraints {componentName => {dependentComponentName => versionConstraint}} to be @@ -93,7 +92,9 @@ public List resolveDependencies(DeploymentDocument document // both versions (if possible) for (DeploymentPackageConfiguration e : document.getDeploymentPackageConfigurationList()) { if (e.isRootComponent()) { - logger.atDebug().kv(COMPONENT_NAME_KEY, e.getPackageName()).kv(VERSION_KEY, e.getResolvedVersion()) + logger.atDebug() + .kv(COMPONENT_NAME_KEY, e.getPackageName()) + .kv(VERSION_KEY, e.getResolvedVersion()) .log("Found component configuration"); componentNameToVersionConstraints.putIfAbsent(e.getPackageName(), new HashMap<>()); try { @@ -101,10 +102,10 @@ public List resolveDependencies(DeploymentDocument document .put(document.getGroupName(), Requirement.buildNPM(e.getResolvedVersion())); } catch (Exception exception) { throw new PackagingException( - String.format("Unsupported component version '%s' for component '%s'. For pre-release " + String.format( + "Unsupported component version '%s' for component '%s'. For pre-release " + "versions, please start the version tag with a non-numeric character", - e.getResolvedVersion(), - e.getPackageName()), + e.getResolvedVersion(), e.getPackageName()), exception, COMPONENT_VERSION_NOT_VALID); } targetComponentsToResolve.add(e.getPackageName()); @@ -121,17 +122,17 @@ public List resolveDependencies(DeploymentDocument document combinedTargetComponents .addAll(getOtherGroupsTargetComponents(otherGroupsToRootComponents, componentNameToVersionConstraints)); - logger.atInfo().setEventType("resolve-all-group-dependencies-start") + logger.atInfo() + .setEventType("resolve-all-group-dependencies-start") .kv("allGroupTargets", combinedTargetComponents) .kv(COMPONENT_VERSION_REQUIREMENT_KEY, componentNameToVersionConstraints) .log("Start to resolve all groups dependencies"); // populate all groups target components dependencies // resolve updated version from the cloud, update version requirement map for (String targetComponent : combinedTargetComponents) { - resolveComponentDependencies(targetComponent, componentNameToVersionConstraints, - resolvedComponents, componentIncomingReferenceCount, - (name, requirements) -> - componentManager.resolveComponentVersion(name, requirements)); + resolveComponentDependencies(targetComponent, componentNameToVersionConstraints, resolvedComponents, + componentIncomingReferenceCount, + (name, requirements) -> componentManager.resolveComponentVersion(name, requirements)); } // detect circular dependencies for target components from the current deployment @@ -139,21 +140,23 @@ public List resolveDependencies(DeploymentDocument document detectCircularDependency(component, resolvedComponents); } - List resolvedComponentIdentifiers = resolvedComponents.values() - .stream().map(ComponentMetadata::getComponentIdentifier) + List resolvedComponentIdentifiers = resolvedComponents.values() + .stream() + .map(ComponentMetadata::getComponentIdentifier) .collect(Collectors.toList()); checkNonExplicitNucleusUpdate(targetComponentsToResolve, resolvedComponentIdentifiers); - logger.atInfo().setEventType("resolve-all-group-dependencies-finish") + logger.atInfo() + .setEventType("resolve-all-group-dependencies-finish") .kv("resolvedComponents", resolvedComponents) .kv(COMPONENT_VERSION_REQUIREMENT_KEY, componentNameToVersionConstraints) .log("Finish resolving all groups dependencies"); return new ArrayList<>(resolvedComponentIdentifiers); } - void checkNonExplicitNucleusUpdate(List targetComponents, - List resolvedComponents) throws PackagingException { + void checkNonExplicitNucleusUpdate(List targetComponents, List resolvedComponents) + throws PackagingException { List resolvedNucleusComponents = new ArrayList<>(); for (ComponentIdentifier componentIdentifier : resolvedComponents) { if (ComponentType.NUCLEUS.equals(componentStore.getPackageRecipe(componentIdentifier).getComponentType())) { @@ -163,13 +166,16 @@ void checkNonExplicitNucleusUpdate(List targetComponents, if (resolvedNucleusComponents.size() > 1) { throw new PackagingException( String.format("Deployment cannot have more than 1 component of type Nucleus " + "%s", - resolvedNucleusComponents), DeploymentErrorCode.MULTIPLE_NUCLEUS_RESOLVED_ERROR); + resolvedNucleusComponents), + DeploymentErrorCode.MULTIPLE_NUCLEUS_RESOLVED_ERROR); } if (resolvedNucleusComponents.isEmpty()) { return; } - Optional activeNucleusOption = kernel.orderedDependencies().stream() - .filter(s -> ComponentType.NUCLEUS.name().equals(s.getServiceType())).findFirst(); + Optional activeNucleusOption = kernel.orderedDependencies() + .stream() + .filter(s -> ComponentType.NUCLEUS.name().equals(s.getServiceType())) + .findFirst(); if (!activeNucleusOption.isPresent()) { return; } @@ -180,8 +186,8 @@ void checkNonExplicitNucleusUpdate(List targetComponents, DeploymentErrorCode.NUCLEUS_VERSION_NOT_FOUND); } Semver activeNucleusVersion = new Semver(activeNucleusVersionConfig); - ComponentIdentifier activeNucleusId = new ComponentIdentifier(activeNucleus.getServiceName(), - activeNucleusVersion); + ComponentIdentifier activeNucleusId = + new ComponentIdentifier(activeNucleus.getServiceName(), activeNucleusVersion); ComponentIdentifier resolvedNucleusId = resolvedNucleusComponents.get(0); if (!resolvedNucleusId.equals(activeNucleusId) && !targetComponents.contains(resolvedNucleusId.getName())) { @@ -196,10 +202,9 @@ void checkNonExplicitNucleusUpdate(List targetComponents, } } - private Set getOtherGroupsTargetComponents(Map> - otherGroupsRootComponents, - Map> - componentNameToVersionConstraints) { + private Set getOtherGroupsTargetComponents( + Map> otherGroupsRootComponents, + Map> componentNameToVersionConstraints) { Set targetComponents = new HashSet<>(); otherGroupsRootComponents.forEach((groupName, rootPackages) -> { rootPackages.forEach(component -> { @@ -213,12 +218,13 @@ private Set getOtherGroupsTargetComponents(Map> componentNameToVersionConstraints, - Map resolvedComponents, - Map componentIncomingReferenceCount, + private void resolveComponentDependencies(String targetComponentName, + Map> componentNameToVersionConstraints, + Map resolvedComponents, Map componentIncomingReferenceCount, ComponentResolver componentResolver) throws PackagingException, InterruptedException { - logger.atDebug().setEventType("traverse-dependencies-start").kv("targetComponent", targetComponentName) + logger.atDebug() + .setEventType("traverse-dependencies-start") + .kv("targetComponent", targetComponentName) .kv(COMPONENT_VERSION_REQUIREMENT_KEY, componentNameToVersionConstraints) .log("Start traversing dependencies"); Queue componentsToResolve = new LinkedList<>(); @@ -237,7 +243,9 @@ private void resolveComponentDependencies( ComponentMetadata previousVersion = resolvedComponents.put(componentToResolve, resolvedVersion); if (previousVersion != null && !previousVersion.equals(resolvedVersion)) { - logger.atDebug().kv("previousVersion", previousVersion).kv("newVersion", resolvedVersion) + logger.atDebug() + .kv("previousVersion", previousVersion) + .kv("newVersion", resolvedVersion) .log("The resolved version of the component changed, updating the dependency tree"); removeDependencies(previousVersion, resolvedComponents, componentIncomingReferenceCount, componentNameToVersionConstraints); @@ -248,24 +256,25 @@ private void resolveComponentDependencies( } for (Map.Entry dependency : resolvedVersion.getDependencies().entrySet()) { componentNameToVersionConstraints.putIfAbsent(dependency.getKey(), new HashMap<>()); - componentNameToVersionConstraints.get(dependency.getKey()).put(componentToResolve, - Requirement.buildNPM(dependency.getValue())); + componentNameToVersionConstraints.get(dependency.getKey()) + .put(componentToResolve, Requirement.buildNPM(dependency.getValue())); componentsToResolve.add(dependency.getKey()); } } - logger.atDebug().setEventType("traverse-dependencies-finish").kv("resolvedComponents", resolvedComponents) + logger.atDebug() + .setEventType("traverse-dependencies-finish") + .kv("resolvedComponents", resolvedComponents) .log("Finish traversing dependencies"); } /* - A component version is removed from the dependency tree, remove all dependencies of this version - which has an incoming reference count of 1 (i.e no other component has depends on them) + * A component version is removed from the dependency tree, remove all dependencies of this version which has an + * incoming reference count of 1 (i.e no other component has depends on them) */ private void removeDependencies(ComponentMetadata removedComponentVersion, - Map resolvedComponents, - Map componentIncomingReferenceCount, - Map> componentNameToVersionConstraints) { + Map resolvedComponents, Map componentIncomingReferenceCount, + Map> componentNameToVersionConstraints) { Queue componentsToRemove = new LinkedList<>(); componentsToRemove.add(removedComponentVersion); @@ -275,22 +284,21 @@ private void removeDependencies(ComponentMetadata removedComponentVersion, // removing version constraints from removed component componentNameToVersionConstraints.get(dependency.getKey()) .remove(removedComponent.getComponentIdentifier().getName()); - componentIncomingReferenceCount.compute(dependency.getKey(), - (key, value) -> { - if (value == null) { - return null; - } else if (value == 1) { - // only removedComponent depend on this component. This component can be removed. - ComponentMetadata component = resolvedComponents.remove(key); - logger.atDebug().kv("version", component).log("Removing component"); - // adding the component to componentsToRemove, to clean up its dependencies - componentsToRemove.add(component); - return null; - } else { - // count down the incoming reference count for the dependency - return value - 1; - } - }); + componentIncomingReferenceCount.compute(dependency.getKey(), (key, value) -> { + if (value == null) { + return null; + } else if (value == 1) { + // only removedComponent depend on this component. This component can be removed. + ComponentMetadata component = resolvedComponents.remove(key); + logger.atDebug().kv("version", component).log("Removing component"); + // adding the component to componentsToRemove, to clean up its dependencies + componentsToRemove.add(component); + return null; + } else { + // count down the incoming reference count for the dependency + return value - 1; + } + }); } } } @@ -304,12 +312,13 @@ private void detectCircularDependency(String targetComponent, Map dependencies = resolvedComponents.get(componentName).getDependencies().keySet(); componentDependencyMap.put(componentName, dependencies); - dependencies.stream().filter(dependency -> !componentDependencyMap.containsKey(dependency)) + dependencies.stream() + .filter(dependency -> !componentDependencyMap.containsKey(dependency)) .forEach(componentsToVisit::add); } int componentCount = componentDependencyMap.keySet().size(); - LinkedHashSet result = new DependencyOrder().computeOrderedDependencies( - componentDependencyMap.keySet(), componentDependencyMap::get); + LinkedHashSet result = new DependencyOrder() + .computeOrderedDependencies(componentDependencyMap.keySet(), componentDependencyMap::get); if (result.size() != componentCount) { throw new ComponentVersionNegotiationException("Circular dependency detected for component " @@ -317,7 +326,6 @@ private void detectCircularDependency(String targetComponent, Map requirements) diff --git a/src/main/java/com/aws/greengrass/componentmanager/KernelConfigResolver.java b/src/main/java/com/aws/greengrass/componentmanager/KernelConfigResolver.java index 87bc03233b..db00bd112e 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/KernelConfigResolver.java +++ b/src/main/java/com/aws/greengrass/componentmanager/KernelConfigResolver.java @@ -89,28 +89,26 @@ public class KernelConfigResolver { Pattern.compile("\\{([.\\w-]+):([.\\w-]+):([^:}]*)}"); // https://tools.ietf.org/html/rfc6901#section-5 private static final String JSON_POINTER_WHOLE_DOC = ""; - private static final ObjectMapper MAPPER = new ObjectMapper() - .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + private static final ObjectMapper MAPPER = new ObjectMapper().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); // Map from Namespace -> Key -> Function which returns the replacement value - private final Map>> - systemParameters = new HashMap<>(); + private final Map>> systemParameters = + new HashMap<>(); private final ComponentStore componentStore; private final Kernel kernel; private final DeviceConfiguration deviceConfiguration; - /** * Constructor. * * @param componentStore package store used to look up packages - * @param kernel kernel - * @param nucleusPaths nucleus paths + * @param kernel kernel + * @param nucleusPaths nucleus paths * @param deviceConfiguration device configuration */ @Inject public KernelConfigResolver(ComponentStore componentStore, Kernel kernel, NucleusPaths nucleusPaths, - DeviceConfiguration deviceConfiguration) { + DeviceConfiguration deviceConfiguration) { this.componentStore = componentStore; this.kernel = kernel; this.deviceConfiguration = deviceConfiguration; @@ -118,8 +116,8 @@ public KernelConfigResolver(ComponentStore componentStore, Kernel kernel, Nucleu // More system parameters can be added over time by extending this map with new namespaces/keys Map> artifactNamespace = new HashMap<>(); artifactNamespace.put(PATH_KEY, (id) -> nucleusPaths.artifactPath(id).toAbsolutePath().toString()); - artifactNamespace - .put(DECOMPRESSED_PATH_KEY, (id) -> nucleusPaths.unarchiveArtifactPath(id).toAbsolutePath().toString()); + artifactNamespace.put(DECOMPRESSED_PATH_KEY, + (id) -> nucleusPaths.unarchiveArtifactPath(id).toAbsolutePath().toString()); systemParameters.put(ARTIFACTS_NAMESPACE, artifactNamespace); Map> workNamespace = new HashMap<>(); @@ -141,22 +139,24 @@ public KernelConfigResolver(ComponentStore componentStore, Kernel kernel, Nucleu * key-value pair. * * @param componentsToDeploy package identifiers for resolved packages of complete dependency graph across groups - * @param document deployment document - * @param rootPackages root level packages + * @param document deployment document + * @param rootPackages root level packages * @param configMergeTimestamp timestamp to use for configuration merge * @return a kernel config map * @throws PackageLoadingException if any service package was unable to be loaded - * @throws IOException for directory issues + * @throws IOException for directory issues */ public Map resolve(List componentsToDeploy, DeploymentDocument document, List rootPackages, long configMergeTimestamp) throws PackageLoadingException, IOException { Map servicesConfig = new HashMap<>(); - LOGGER.atDebug().kv("Components to deploy", componentsToDeploy).kv("Root packages", rootPackages) + LOGGER.atDebug() + .kv("Components to deploy", componentsToDeploy) + .kv("Root packages", rootPackages) .log("Resolving services configuration"); // resolve configuration for (ComponentIdentifier componentToDeploy : componentsToDeploy) { - servicesConfig.put(componentToDeploy.getName(), getServiceConfig(componentToDeploy, document, - configMergeTimestamp)); + servicesConfig.put(componentToDeploy.getName(), + getServiceConfig(componentToDeploy, document, configMergeTimestamp)); } // Interpolate configurations @@ -164,28 +164,27 @@ public Map resolve(List componentsToDeploy, ComponentRecipe componentRecipe = componentStore.getPackageRecipe(resolvedComponentsToDeploy); if (shouldInterpolateConfiguration(servicesConfig)) { - Object existingConfiguration = ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())) - .get(CONFIGURATION_CONFIG_KEY); + Object existingConfiguration = + ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())).get(CONFIGURATION_CONFIG_KEY); Object interpolatedConfiguration = interpolateSystemParametersOnly(existingConfiguration, resolvedComponentsToDeploy, componentRecipe.getDependencies().keySet(), servicesConfig); - ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())) - .put(CONFIGURATION_CONFIG_KEY, interpolatedConfiguration); + ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())).put(CONFIGURATION_CONFIG_KEY, + interpolatedConfiguration); } Object existingLifecycle = ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())) .get(SERVICE_LIFECYCLE_NAMESPACE_TOPIC); Object interpolatedLifecycle = interpolate(existingLifecycle, resolvedComponentsToDeploy, - componentRecipe.getDependencies().keySet(), servicesConfig); + componentRecipe.getDependencies().keySet(), servicesConfig); - ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())) - .put(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, interpolatedLifecycle); + ((Map) servicesConfig.get(resolvedComponentsToDeploy.getName())).put(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, + interpolatedLifecycle); } - String nucleusComponentName = - getNucleusComponentName(servicesConfig); + String nucleusComponentName = getNucleusComponentName(servicesConfig); servicesConfig.putIfAbsent(nucleusComponentName, getNucleusComponentConfig(nucleusComponentName)); servicesConfig.put(kernel.getMain().getName(), getMainConfig(rootPackages, nucleusComponentName)); @@ -221,26 +220,23 @@ private boolean shouldInterpolateConfiguration(Map servicesConfi /** * Build the kernel config for a service/component by processing deployment document. * - * @param componentIdentifier target component id - * @param document deployment doc for the current deployment - * @param configMergeTimestamp timestamp to use during configuration merge + * @param componentIdentifier target component id + * @param document deployment doc for the current deployment + * @param configMergeTimestamp timestamp to use during configuration merge * @return a built map representing the kernel config under "services" key for a particular component * @throws PackageLoadingException if any service package was unable to be loaded */ private Map getServiceConfig(ComponentIdentifier componentIdentifier, DeploymentDocument document, - long configMergeTimestamp) - throws PackageLoadingException { + long configMergeTimestamp) throws PackageLoadingException { ComponentRecipe componentRecipe = componentStore.getPackageRecipe(componentIdentifier); - Map resolvedServiceConfig = new HashMap<>(); resolvedServiceConfig.put(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, componentRecipe.getLifecycle()); - - resolvedServiceConfig.put(SERVICE_TYPE_TOPIC_KEY, componentRecipe.getComponentType() == null ? null - : componentRecipe.getComponentType().name()); + resolvedServiceConfig.put(SERVICE_TYPE_TOPIC_KEY, + componentRecipe.getComponentType() == null ? null : componentRecipe.getComponentType().name()); // Generate dependencies resolvedServiceConfig.put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, @@ -248,15 +244,17 @@ private Map getServiceConfig(ComponentIdentifier componentIdenti // State information for deployments handleComponentVersionConfigs(componentIdentifier, componentRecipe.getVersion().getValue(), - resolvedServiceConfig); + resolvedServiceConfig); Optional optionalDeploymentPackageConfig = - document.getDeploymentPackageConfigurationList().stream() + document.getDeploymentPackageConfigurationList() + .stream() .filter(e -> e.getPackageName().equals(componentRecipe.getComponentName())) // only allow update config for root // no need to check version because root's version will be pinned - .filter(DeploymentPackageConfiguration::isRootComponent).findAny(); + .filter(DeploymentPackageConfiguration::isRootComponent) + .findAny(); Optional optionalConfigUpdate = Optional.empty(); if (optionalDeploymentPackageConfig.isPresent()) { @@ -269,18 +267,18 @@ private Map getServiceConfig(ComponentIdentifier componentIdenti updateRunWith(null, resolvedServiceConfig, componentIdentifier.getName()); } - Map resolvedConfiguration = resolveConfigurationToApply(optionalConfigUpdate.orElse(null), - componentRecipe, configMergeTimestamp); + Map resolvedConfiguration = + resolveConfigurationToApply(optionalConfigUpdate.orElse(null), componentRecipe, configMergeTimestamp); // merge resolved param and resolved configuration for backward compatibility - resolvedServiceConfig - .put(CONFIGURATION_CONFIG_KEY, resolvedConfiguration); + resolvedServiceConfig.put(CONFIGURATION_CONFIG_KEY, resolvedConfiguration); return resolvedServiceConfig; } /** * Generate service dependency list from the given dependency definition from recipe. + * * @param dependencyPropertiesMap map of service dependency name to dependency properties * @return service dependency list */ @@ -334,14 +332,13 @@ private void updateRunWith(RunWith runWith, Map resolvedServiceC } } - /** * Resolve configurations to apply for a component. It resolves based on current running config, default config, and * config update operation. * * @param configurationUpdateOperation nullable component configuration update operation. - * @param componentRecipe component recipe containing default configuration. - * @param configMergeTimestamp timestamp to use during configuration merge + * @param componentRecipe component recipe containing default configuration. + * @param configMergeTimestamp timestamp to use during configuration merge * @return resolved configuration for this component. non null. */ @SuppressWarnings("PMD.ConfusingTernary") @@ -377,9 +374,10 @@ private Map resolveConfigurationToApply( } // Merge in the defaults with timestamp 1 so that they don't overwrite any pre-existing values + // init null to be empty default config JsonNode defaultConfig = Optional.ofNullable(componentRecipe.getComponentConfiguration()) .map(ComponentConfiguration::getDefaultConfiguration) - .orElse(MAPPER.createObjectNode()); // init null to be empty default config + .orElse(MAPPER.createObjectNode()); // Merge in the defaults from the recipe using timestamp 1 to denote a default currentRunningConfig.mergeMap(1, MAPPER.convertValue(defaultConfig, Map.class)); @@ -410,11 +408,12 @@ private void removeKeysFromConfigWhichAreReset(Configuration original, List pointerToPath(JsonPointer pointer) { /** * Interpolate the lifecycle commands or config with resolved system configuration values. * - * @param configValue original value; could be Map or String - * @param componentIdentifier target component id - * @param dependencies name set of component's dependencies + * @param configValue original value; could be Map or String + * @param componentIdentifier target component id + * @param dependencies name set of component's dependencies * @param resolvedKernelServiceConfig resolved kernel configuration under "Services" key * @return the interpolated lifecycle object * @throws IOException for directory issues */ public Object interpolateSystemParametersOnly(Object configValue, ComponentIdentifier componentIdentifier, - Set dependencies, - Map resolvedKernelServiceConfig) throws IOException { + Set dependencies, Map resolvedKernelServiceConfig) throws IOException { return interpolate(configValue, componentIdentifier, dependencies, resolvedKernelServiceConfig, false); } @@ -469,9 +467,9 @@ public Object interpolateSystemParametersOnly(Object configValue, ComponentIdent * Interpolate the lifecycle commands or config with resolved component configuration values and system * configuration values. * - * @param configValue original value; could be Map or String - * @param componentIdentifier target component id - * @param dependencies name set of component's dependencies + * @param configValue original value; could be Map or String + * @param componentIdentifier target component id + * @param dependencies name set of component's dependencies * @param resolvedKernelServiceConfig resolved kernel configuration under "Services" key * @return the interpolated lifecycle object * @throws IOException for directory issues @@ -482,15 +480,14 @@ public Object interpolate(Object configValue, ComponentIdentifier componentIdent } /** - * Interpolate the lifecycle commands or config with - * * (optional) resolved component configuration values. - * * system configuration values. + * Interpolate the lifecycle commands or config with * (optional) resolved component configuration values. * system + * configuration values. * - * @param configValue original value; could be Map or String - * @param componentIdentifier target component id - * @param dependencies name set of component's dependencies + * @param configValue original value; could be Map or String + * @param componentIdentifier target component id + * @param dependencies name set of component's dependencies * @param resolvedKernelServiceConfig resolved kernel configuration under "Services" key - * @param shouldInterpolateConfiguration flag to enable interpolation with component configuration + * @param shouldInterpolateConfiguration flag to enable interpolation with component configuration * @return the interpolated lifecycle object * @throws IOException for directory issues */ @@ -508,14 +505,14 @@ public Object interpolate(Object configValue, ComponentIdentifier componentIdent Map resolvedChildConfig = new HashMap<>(); for (Entry childLifecycle : childConfigMap.entrySet()) { resolvedChildConfig.put(childLifecycle.getKey(), - interpolate(childLifecycle.getValue(), componentIdentifier, dependencies, - resolvedKernelServiceConfig, shouldInterpolateConfiguration)); + interpolate(childLifecycle.getValue(), componentIdentifier, dependencies, + resolvedKernelServiceConfig, shouldInterpolateConfiguration)); } result = resolvedChildConfig; } if (configValue instanceof List) { List resolvedConfigValue = new ArrayList<>(); - for (Object element: (List) configValue) { + for (Object element : (List) configValue) { resolvedConfigValue.add(interpolate(element, componentIdentifier, dependencies, resolvedKernelServiceConfig, shouldInterpolateConfiguration)); } @@ -539,9 +536,8 @@ private String replace(String stringValue, ComponentIdentifier componentIdentifi String key = matcher.group(2); if (shouldInterpolateConfiguration && CONFIGURATION_NAMESPACE.equals(namespace)) { - Optional configReplacement = - lookupConfigurationValueForComponent(componentIdentifier.getName(), key, - resolvedKernelServiceConfig); + Optional configReplacement = lookupConfigurationValueForComponent(componentIdentifier.getName(), + key, resolvedKernelServiceConfig); if (configReplacement.isPresent()) { stringValue = stringValue.replace(matcher.group(), configReplacement.get()); } @@ -554,7 +550,9 @@ private String replace(String stringValue, ComponentIdentifier componentIdentifi } else { // unrecognized namespace - LOGGER.atError().kv("interpolation placeholder", matcher.group()).kv("namespace", namespace) + LOGGER.atError() + .kv("interpolation placeholder", matcher.group()) + .kv("namespace", namespace) .log("Failed to interpolate because of unrecognized namespace for interpolation."); } } @@ -569,17 +567,21 @@ private String replace(String stringValue, ComponentIdentifier componentIdentifi // only interpolate if target component is a direct dependency if (!dependencies.contains(targetComponent)) { - LOGGER.atError().kv("interpolation text", matcher.group()).kv("target component", targetComponent) + LOGGER.atError() + .kv("interpolation text", matcher.group()) + .kv("target component", targetComponent) .kv("main component", componentIdentifier.getName()) .log("Failed to interpolate because the target component it's not a direct dependency."); continue; } if (!resolvedKernelServiceConfig.containsKey(targetComponent)) { - LOGGER.atError().kv("interpolation text", matcher.group()).kv("target component", targetComponent) + LOGGER.atError() + .kv("interpolation text", matcher.group()) + .kv("target component", targetComponent) .kv("main component", componentIdentifier.getName()) .log("Failed to interpolate because the target component is not in resolved Nucleus services." - + " This indicates the dependency resolution is broken."); + + " This indicates the dependency resolution is broken."); continue; } @@ -593,16 +595,17 @@ private String replace(String stringValue, ComponentIdentifier componentIdentifi String version = (String) ((Map) resolvedKernelServiceConfig.get(targetComponent)).get(VERSION_CONFIG_KEY); - String configReplacement = - lookupSystemConfig(new ComponentIdentifier(targetComponent, new Semver(version)), namespace, - key); + String configReplacement = lookupSystemConfig( + new ComponentIdentifier(targetComponent, new Semver(version)), namespace, key); if (configReplacement != null) { stringValue = stringValue.replace(matcher.group(), configReplacement); } } else { // unrecognized namespace - LOGGER.atError().kv("interpolation placeholder", matcher.group()).kv("namespace", namespace) + LOGGER.atError() + .kv("interpolation placeholder", matcher.group()) + .kv("namespace", namespace) .log("Failed to interpolate because of unrecognized namespace for interpolation."); } @@ -614,8 +617,8 @@ private String replace(String stringValue, ComponentIdentifier componentIdentifi /** * Find the configuration value for a component. * - * @param componentName component name - * @param path path to the value + * @param componentName component name + * @param path path to the value * @param resolvedKernelServiceConfig resolved kernel service config to search from * @return configuration value for the path; empty if not found. */ @@ -624,8 +627,8 @@ private Optional lookupConfigurationValueForComponent(String componentNa Map componentResolvedConfig; - if (resolvedKernelServiceConfig.containsKey(componentName) && ((Map) resolvedKernelServiceConfig - .get(componentName)).containsKey(CONFIGURATION_CONFIG_KEY)) { + if (resolvedKernelServiceConfig.containsKey(componentName) + && ((Map) resolvedKernelServiceConfig.get(componentName)).containsKey(CONFIGURATION_CONFIG_KEY)) { componentResolvedConfig = (Map) ((Map) resolvedKernelServiceConfig.get(componentName)).get(CONFIGURATION_CONFIG_KEY); } else { @@ -639,7 +642,8 @@ private Optional lookupConfigurationValueForComponent(String componentNa } if (targetNode.isMissingNode()) { - LOGGER.atError().addKeyValue("Path", path) + LOGGER.atError() + .addKeyValue("Path", path) .log("Failed to interpolate configuration due to missing value node at given path"); return Optional.empty(); } @@ -683,12 +687,14 @@ private Map getMainConfig(List rootPackages, String nucl } /* - * If the deployment's service config has a component of type Nucleus use that, if it doesn't, - * fall back to the first party Nucleus component + * If the deployment's service config has a component of type Nucleus use that, if it doesn't, fall back to the + * first party Nucleus component */ private String getNucleusComponentName(Map newServiceConfig) { - Optional nucleusComponentName = newServiceConfig.keySet().stream() - .filter(s -> ComponentType.NUCLEUS.name().equals(getComponentType(newServiceConfig.get(s)))).findAny(); + Optional nucleusComponentName = newServiceConfig.keySet() + .stream() + .filter(s -> ComponentType.NUCLEUS.name().equals(getComponentType(newServiceConfig.get(s)))) + .findAny(); return nucleusComponentName.orElse(deviceConfiguration.getNucleusComponentName()); } diff --git a/src/main/java/com/aws/greengrass/componentmanager/Unarchiver.java b/src/main/java/com/aws/greengrass/componentmanager/Unarchiver.java index 2cb3a8ee09..7bf15cab6a 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/Unarchiver.java +++ b/src/main/java/com/aws/greengrass/componentmanager/Unarchiver.java @@ -25,8 +25,8 @@ public class Unarchiver { /** * Unarchive a given file into a given path. * - * @param method type of archive to undo - * @param toUnarchive the file to be unarchived + * @param method type of archive to undo + * @param toUnarchive the file to be unarchived * @param unarchiveInto the path to unarchive the file into * @throws IOException if unarchiving fails */ @@ -52,10 +52,9 @@ static void unzip(File zipFile, File destDir) throws IOException { // Only unarchive when the destination file doesn't exist or the file sizes don't match if (!newFile.exists() || zipEntry.getSize() != newFile.length()) { try (FileChannel fc = FileChannel.open(newFile.toPath(), StandardOpenOption.CREATE, - StandardOpenOption.WRITE, - StandardOpenOption.TRUNCATE_EXISTING); - InputStream is = zf.getInputStream(zipEntry); - OutputStream fos = Channels.newOutputStream(fc)) { + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + InputStream is = zf.getInputStream(zipEntry); + OutputStream fos = Channels.newOutputStream(fc)) { IOUtils.copyLarge(is, fos, buffer); // calls sync() to force the file to disk to the best of our abilities fc.force(true); diff --git a/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloader.java b/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloader.java index efff1ff3f7..93cbd44f3b 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloader.java +++ b/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloader.java @@ -52,14 +52,16 @@ public abstract class ArtifactDownloader { protected final ComponentStore componentStore; @Setter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig checksumMismatchRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1L)) - .maxRetryInterval(Duration.ofMinutes(1L)).maxAttempt(10) - .retryableExceptions(Arrays.asList(ArtifactChecksumMismatchException.class)).build(); + private RetryUtils.RetryConfig checksumMismatchRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1L)) + .maxRetryInterval(Duration.ofMinutes(1L)) + .maxAttempt(10) + .retryableExceptions(Arrays.asList(ArtifactChecksumMismatchException.class)) + .build(); private Path saveToPath; - protected ArtifactDownloader(ComponentIdentifier identifier, ComponentArtifact artifact, - Path artifactDir, ComponentStore componentStore) { + protected ArtifactDownloader(ComponentIdentifier identifier, ComponentArtifact artifact, Path artifactDir, + ComponentStore componentStore) { this.identifier = identifier; this.artifact = artifact; this.artifactDir = artifactDir; @@ -88,12 +90,14 @@ private boolean recipeHasDigest(ComponentArtifact artifact) { * Download an artifact from remote. This call can take a long time if the network is intermittent. * * @return file handle of the downloaded file - * @throws IOException if I/O error occurred in network/disk - * @throws InterruptedException if interrupted in downloading - * @throws PackageDownloadException if error occurred in download process + * @throws IOException if I/O error occurred in network/disk + * @throws InterruptedException if interrupted in downloading + * @throws PackageDownloadException if error occurred in download process * @throws HashingAlgorithmUnavailableException if required hash algorithm is not supported */ - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) public File download() throws PackageDownloadException, IOException, InterruptedException, HashingAlgorithmUnavailableException { MessageDigest messageDigest; @@ -117,8 +121,9 @@ public File download() if (Files.size(saveToPath) > artifactSize) { // Existing file is corrupted, it's larger than defined in artifact. // Normally shouldn't happen, corrupted files are deleted every time. - logger.atError().log("existing file corrupted. Expected size: {}, Actual size: {}." - + " Removing and retrying download.", artifactSize, Files.size(saveToPath)); + logger.atError() + .log("existing file corrupted. Expected size: {}, Actual size: {}." + + " Removing and retrying download.", artifactSize, Files.size(saveToPath)); Files.deleteIfExists(saveToPath); } else { offset.set(Files.size(saveToPath)); @@ -128,7 +133,7 @@ public File download() try { // A checksum mismatch probably means the downloaded artifact is corrupted, Greengrass will retry the - //download for 10 times before giving up. + // download for 10 times before giving up. return RetryUtils.runWithRetry(checksumMismatchRetryConfig, () -> { while (offset.get() < artifactSize) { long downloadedBytes = download(offset.get(), artifactSize - 1, messageDigest); @@ -161,7 +166,7 @@ public File download() * will return actual number of bytes downloaded. Supposed to be invoked in `protected abstract long download(long * rangeStart, long rangeEnd)` * - * @param inputStream inputStream to download from. + * @param inputStream inputStream to download from. * @param messageDigest messageDigest to update. * @return number of bytes downloaded. Might return 0 only when encountering IOException * @throws PackageDownloadException Throw PackageDownloadException when fail to write to the disk @@ -170,7 +175,7 @@ protected long download(InputStream inputStream, MessageDigest messageDigest) th long totalReadBytes = 0; try (FileChannel artifactFileChannel = FileChannel.open(saveToPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND); - OutputStream artifactFile = Channels.newOutputStream(artifactFileChannel)) { + OutputStream artifactFile = Channels.newOutputStream(artifactFileChannel)) { byte[] buffer = new byte[DOWNLOAD_BUFFER_SIZE]; int readBytes = inputStream.read(buffer); while (readBytes > -1) { @@ -178,8 +183,8 @@ protected long download(InputStream inputStream, MessageDigest messageDigest) th try { artifactFile.write(buffer, 0, readBytes); } catch (IOException e) { - throw new PackageDownloadException(getErrorString("Error writing artifact"), e) - .withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR); + throw new PackageDownloadException(getErrorString("Error writing artifact"), e).withErrorContext(e, + DeploymentErrorCode.IO_WRITE_ERROR); } messageDigest.update(buffer, 0, readBytes); @@ -190,7 +195,9 @@ protected long download(InputStream inputStream, MessageDigest messageDigest) th artifactFileChannel.force(true); return totalReadBytes; } catch (IOException e) { - logger.atWarn().kv("bytes-read", totalReadBytes).setCause(e) + logger.atWarn() + .kv("bytes-read", totalReadBytes) + .setCause(e) .log("Failed to read from input stream and will retry"); return totalReadBytes; } @@ -199,8 +206,8 @@ protected long download(InputStream inputStream, MessageDigest messageDigest) th /** * Internal method invoked in downloadToFile(). * - * @param rangeStart Range start index. INCLUSIVE. - * @param rangeEnd Range end index. INCLUSIVE. + * @param rangeStart Range start index. INCLUSIVE. + * @param rangeEnd Range end index. INCLUSIVE. * @param messageDigest messageDigest to update. * @return number of bytes downloaded. * @throws PackageDownloadException PackageDownloadException @@ -225,9 +232,10 @@ public boolean downloadRequired() throws PackageDownloadException { String digest = Base64.getEncoder().encodeToString(messageDigest.digest()); boolean mismatches = !digest.equals(artifact.getChecksum()); if (mismatches) { - logger.atWarn().log("Artifact appears to exist on disk, " - + "but the digest on disk does not match the digest in the recipe. Will attempt to " - + "download it again."); + logger.atWarn() + .log("Artifact appears to exist on disk, " + + "but the digest on disk does not match the digest in the recipe. Will attempt to " + + "download it again."); } return mismatches; } catch (IOException | NoSuchAlgorithmException e) { @@ -256,6 +264,7 @@ public File getArtifactFile() { /** * Check whether the downloader has proper configs and is ready to download files. + * * @return Optional.empty if no errors and ready to download. Otherwise returns the error message string */ public abstract Optional checkDownloadable(); @@ -272,13 +281,13 @@ public File getArtifactFile() { protected abstract String getArtifactFilename(); protected String getErrorString(String reason) { - return String.format(ARTIFACT_DOWNLOAD_EXCEPTION_FMT, artifact.getArtifactUri(), - identifier.getName(), identifier.getVersion().toString()) + reason; + return String.format(ARTIFACT_DOWNLOAD_EXCEPTION_FMT, artifact.getArtifactUri(), identifier.getName(), + identifier.getVersion().toString()) + reason; } /** - * Check if an instance of implemented class supports checking component store size depending on - * if the artifact is located in greengrass artifact store or third party. + * Check if an instance of implemented class supports checking component store size depending on if the artifact is + * located in greengrass artifact store or third party. * * @return evaluation result */ @@ -287,8 +296,8 @@ public boolean checkComponentStoreSize() { } /** - * Check if an instance of implemented class supports setting file permissions depending on - * if the artifact is located in greengrass artifact store or third party. + * Check if an instance of implemented class supports setting file permissions depending on if the artifact is + * located in greengrass artifact store or third party. * * @return evaluation result */ @@ -297,8 +306,8 @@ public boolean canSetFilePermissions() { } /** - * Check if an instance of implemented class supports unarchiving the artifact depending on - * if the artifact is located in greengrass artifact store or third party. + * Check if an instance of implemented class supports unarchiving the artifact depending on if the artifact is + * located in greengrass artifact store or third party. * * @return evaluation result */ @@ -313,4 +322,3 @@ public boolean canUnarchiveArtifact() { */ public abstract void cleanup() throws IOException; } - diff --git a/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloaderFactory.java b/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloaderFactory.java index 432a21083d..ad460926b0 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloaderFactory.java +++ b/src/main/java/com/aws/greengrass/componentmanager/builtins/ArtifactDownloaderFactory.java @@ -33,8 +33,8 @@ public class ArtifactDownloaderFactory { private static final String GREENGRASS_SCHEME = "GREENGRASS"; private static final String S3_SCHEME = "S3"; public static final String DOCKER_SCHEME = "DOCKER"; - private static final List SUPPORTED_URI_SCHEMES = Arrays.asList(GREENGRASS_SCHEME, S3_SCHEME, - DOCKER_SCHEME); + private static final List SUPPORTED_URI_SCHEMES = + Arrays.asList(GREENGRASS_SCHEME, S3_SCHEME, DOCKER_SCHEME); static final String TOKEN_EXCHANGE_SERVICE_REQUIRED_ERROR_MSG = String.format("Deployments containing private ECR Docker artifacts must include the %s component", @@ -56,18 +56,16 @@ public class ArtifactDownloaderFactory { /** * ArtifactDownloaderFactory constructor. * - * @param s3SdkClientFactory s3SdkClientFactory + * @param s3SdkClientFactory s3SdkClientFactory * @param greengrassServiceClientFactory greengrassComponentServiceClientFactory - * @param componentStore componentStore - * @param context context - * @param deviceConfiguration deviceConfiguration + * @param componentStore componentStore + * @param context context + * @param deviceConfiguration deviceConfiguration */ @Inject public ArtifactDownloaderFactory(S3SdkClientFactory s3SdkClientFactory, - GreengrassServiceClientFactory greengrassServiceClientFactory, - ComponentStore componentStore, - Context context, - DeviceConfiguration deviceConfiguration) { + GreengrassServiceClientFactory greengrassServiceClientFactory, ComponentStore componentStore, + Context context, DeviceConfiguration deviceConfiguration) { this.s3ClientFactory = s3SdkClientFactory; this.clientFactory = greengrassServiceClientFactory; this.componentStore = componentStore; @@ -77,6 +75,7 @@ public ArtifactDownloaderFactory(S3SdkClientFactory s3SdkClientFactory, /** * Return the artifact downloader instance. + * * @param identifier componentIdentifier * @param artifact componentArtifact * @param artifactDir directory to download artifact to @@ -85,8 +84,7 @@ public ArtifactDownloaderFactory(S3SdkClientFactory s3SdkClientFactory, * @throws InvalidArtifactUriException throw if s3 url not valid */ public ArtifactDownloader getArtifactDownloader(ComponentIdentifier identifier, ComponentArtifact artifact, - Path artifactDir) - throws PackageLoadingException, InvalidArtifactUriException { + Path artifactDir) throws PackageLoadingException, InvalidArtifactUriException { URI artifactUri = artifact.getArtifactUri(); String scheme = artifactUri.getScheme() == null ? null : artifactUri.getScheme().toUpperCase(); if (GREENGRASS_SCHEME.equals(scheme)) { @@ -97,7 +95,7 @@ public ArtifactDownloader getArtifactDownloader(ComponentIdentifier identifier, return new S3Downloader(s3ClientFactory, identifier, artifact, artifactDir, componentStore); } // TODO : Needs to be moved out into a different mechanism where when loaded via a plugin, - // an artifact downloader can register itself and be discoverable here. + // an artifact downloader can register itself and be discoverable here. if (DOCKER_SCHEME.equals(scheme)) { return new DockerImageDownloader(identifier, artifact, artifactDir, context, componentStore); } @@ -109,16 +107,14 @@ public ArtifactDownloader getArtifactDownloader(ComponentIdentifier identifier, * Check if all plugins that are required for downloading artifacts of other components are included in the * deployment. * - * @param artifacts all artifacts belonging to a component + * @param artifacts all artifacts belonging to a component * @param currentComponentId {@link ComponentIdentifier} for the component for which the check is being made * @param componentIds deployment dependency closure * @throws MissingRequiredComponentsException when any required plugins are not included - * @throws PackageLoadingException when other errors occur + * @throws PackageLoadingException when other errors occur */ - public void checkDownloadPrerequisites(List artifacts, - ComponentIdentifier currentComponentId, - List componentIds) - throws PackageLoadingException, MissingRequiredComponentsException { + public void checkDownloadPrerequisites(List artifacts, ComponentIdentifier currentComponentId, + List componentIds) throws PackageLoadingException, MissingRequiredComponentsException { List componentNames = componentIds.stream().map(ComponentIdentifier::getName).collect(Collectors.toList()); for (ComponentArtifact artifact : artifacts) { @@ -138,9 +134,10 @@ public void checkDownloadPrerequisites(List artifacts, } } } catch (InvalidArtifactUriException e) { - throw new PackageLoadingException(String - .format("Failed to download due to bad artifact URI: %s for component %s", - artifact.getArtifactUri(), currentComponentId.getName()), e); + throw new PackageLoadingException( + String.format("Failed to download due to bad artifact URI: %s for component %s", + artifact.getArtifactUri(), currentComponentId.getName()), + e); } } } diff --git a/src/main/java/com/aws/greengrass/componentmanager/builtins/GreengrassRepositoryDownloader.java b/src/main/java/com/aws/greengrass/componentmanager/builtins/GreengrassRepositoryDownloader.java index 0a7c0e4abc..66932f3432 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/builtins/GreengrassRepositoryDownloader.java +++ b/src/main/java/com/aws/greengrass/componentmanager/builtins/GreengrassRepositoryDownloader.java @@ -46,12 +46,10 @@ import java.util.Objects; import java.util.Optional; - public class GreengrassRepositoryDownloader extends ArtifactDownloader { static final String CONTENT_LENGTH_HEADER = "content-length"; - private static final List HTTP_DOWNLOAD_ERROR_CODE = - Arrays.asList(DeploymentErrorCode.DOWNLOAD_GREENGRASS_ARTIFACT_ERROR, - DeploymentErrorCode.HTTP_REQUEST_ERROR); + private static final List HTTP_DOWNLOAD_ERROR_CODE = Arrays + .asList(DeploymentErrorCode.DOWNLOAD_GREENGRASS_ARTIFACT_ERROR, DeploymentErrorCode.HTTP_REQUEST_ERROR); private final ComponentStore componentStore; private final GreengrassServiceClientFactory clientFactory; private Long artifactSize = null; @@ -60,16 +58,17 @@ public class GreengrassRepositoryDownloader extends ArtifactDownloader { // Setter for unit test @Setter(AccessLevel.PACKAGE) @Getter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig clientExceptionRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1L)) - .maxRetryInterval(Duration.ofMinutes(1L)).maxAttempt(Integer.MAX_VALUE) - .retryableExceptions(Arrays.asList(SdkClientException.class, IOException.class, - DeviceConfigurationException.class, RetryableServerErrorException.class)).build(); + private RetryUtils.RetryConfig clientExceptionRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1L)) + .maxRetryInterval(Duration.ofMinutes(1L)) + .maxAttempt(Integer.MAX_VALUE) + .retryableExceptions(Arrays.asList(SdkClientException.class, IOException.class, + DeviceConfigurationException.class, RetryableServerErrorException.class)) + .build(); protected GreengrassRepositoryDownloader(GreengrassServiceClientFactory clientFactory, - ComponentIdentifier identifier, ComponentArtifact artifact, - Path artifactDir, ComponentStore componentStore, - DeviceConfiguration deviceConfiguration) { + ComponentIdentifier identifier, ComponentArtifact artifact, Path artifactDir, ComponentStore componentStore, + DeviceConfiguration deviceConfiguration) { super(identifier, artifact, artifactDir, componentStore); this.clientFactory = clientFactory; this.componentStore = componentStore; @@ -92,15 +91,16 @@ public void cleanup() throws IOException { } @Override - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) public Long getDownloadSize() throws PackageDownloadException, InterruptedException { if (artifactSize != null) { return artifactSize; } try { - artifactSize = RetryUtils - .runWithRetry(clientExceptionRetryConfig, () -> getDownloadSizeWithoutRetry(), "get-artifact-size", - logger); + artifactSize = RetryUtils.runWithRetry(clientExceptionRetryConfig, () -> getDownloadSizeWithoutRetry(), + "get-artifact-size", logger); return artifactSize; } catch (InterruptedException e) { throw e; @@ -109,9 +109,11 @@ public Long getDownloadSize() throws PackageDownloadException, InterruptedExcept } } - @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException"}) - private Long getDownloadSizeWithoutRetry() throws InterruptedException, PackageDownloadException, IOException, - RetryableServerErrorException { + @SuppressWarnings({ + "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" + }) + private Long getDownloadSizeWithoutRetry() + throws InterruptedException, PackageDownloadException, IOException, RetryableServerErrorException { String url = getArtifactDownloadURL(identifier, artifact.getArtifactUri().getSchemeSpecificPart()); try (SdkHttpClient client = getSdkHttpClient()) { @@ -130,8 +132,8 @@ private Long getDownloadSizeWithoutRetry() throws InterruptedException, PackageD } return length; } else if (RetryUtils.retryErrorCodes(responseCode)) { - throw new RetryableServerErrorException("Failed to get download size with retryable error. Error code" - + responseCode); + throw new RetryableServerErrorException( + "Failed to get download size with retryable error. Error code" + responseCode); } else { throw new PackageDownloadException( getErrorString("Failed to get download size. HTTP response: " + responseCode), @@ -141,7 +143,9 @@ private Long getDownloadSizeWithoutRetry() throws InterruptedException, PackageD } } - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) @Override protected long download(long rangeStart, long rangeEnd, MessageDigest messageDigest) throws PackageDownloadException, InterruptedException { @@ -150,11 +154,13 @@ protected long download(long rangeStart, long rangeEnd, MessageDigest messageDig try { return RetryUtils.runWithRetry(clientExceptionRetryConfig, () -> { try (SdkHttpClient client = getSdkHttpClient()) { - HttpExecuteRequest executeRequest = HttpExecuteRequest.builder().request( - SdkHttpFullRequest.builder().uri(URI.create(url)).method(SdkHttpMethod.GET) - .putHeader(HTTP_RANGE_HEADER_KEY, - String.format(HTTP_RANGE_HEADER_FORMAT, rangeStart, rangeEnd)) - .build()) + HttpExecuteRequest executeRequest = HttpExecuteRequest.builder() + .request(SdkHttpFullRequest.builder() + .uri(URI.create(url)) + .method(SdkHttpMethod.GET) + .putHeader(HTTP_RANGE_HEADER_KEY, + String.format(HTTP_RANGE_HEADER_FORMAT, rangeStart, rangeEnd)) + .build()) .build(); HttpExecuteResponse executeResponse = client.prepareRequest(executeRequest).call(); @@ -220,7 +226,9 @@ public Optional checkDownloadable() { return Optional.ofNullable(clientFactory.getConfigValidationError()); } - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) private String getArtifactDownloadURL(ComponentIdentifier componentIdentifier, String artifactName) throws InterruptedException, PackageDownloadException { String arn; @@ -235,9 +243,11 @@ private String getArtifactDownloadURL(ComponentIdentifier componentIdentifier, S return RetryUtils.runWithRetry(clientExceptionRetryConfig, () -> { try { GetComponentVersionArtifactRequest getComponentArtifactRequest = - GetComponentVersionArtifactRequest.builder().artifactName(artifactName) + GetComponentVersionArtifactRequest.builder() + .artifactName(artifactName) .s3EndpointType(Coerce.toString(deviceConfiguration.gets3EndpointType())) - .arn(arn).build(); + .arn(arn) + .build(); GetComponentVersionArtifactResponse getComponentArtifactResult = clientFactory.fetchGreengrassV2DataClient() .getComponentVersionArtifact(getComponentArtifactRequest); @@ -254,16 +264,16 @@ private String getArtifactDownloadURL(ComponentIdentifier componentIdentifier, S throw e; } catch (GreengrassV2DataException e) { if (e.statusCode() == HttpStatusCode.FORBIDDEN) { - throw new PackageDownloadException(getErrorString("Access denied when calling " - + "GetComponentVersionArtifact. Ensure certificate policy grants " - + "greengrass:GetComponentVersionArtifact"), + throw new PackageDownloadException(getErrorString( + "Access denied when calling " + "GetComponentVersionArtifact. Ensure certificate policy grants " + + "greengrass:GetComponentVersionArtifact"), e).withErrorContext(e, DeploymentErrorCode.GET_COMPONENT_VERSION_ARTIFACT_ACCESS_DENIED); } - throw new PackageDownloadException(getErrorString("Failed to call GetComponentVersionArtifact and get " - + "component artifact's pre-signed url"), e); + throw new PackageDownloadException(getErrorString( + "Failed to call GetComponentVersionArtifact and get " + "component artifact's pre-signed url"), e); } catch (Exception e) { - throw new PackageDownloadException(getErrorString("Failed to call GetComponentVersionArtifact and get " - + "component artifact's pre-signed url"), e); + throw new PackageDownloadException(getErrorString( + "Failed to call GetComponentVersionArtifact and get " + "component artifact's pre-signed url"), e); } } diff --git a/src/main/java/com/aws/greengrass/componentmanager/builtins/S3Downloader.java b/src/main/java/com/aws/greengrass/componentmanager/builtins/S3Downloader.java index ea30c58797..550eeccd30 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/builtins/S3Downloader.java +++ b/src/main/java/com/aws/greengrass/componentmanager/builtins/S3Downloader.java @@ -59,11 +59,13 @@ public class S3Downloader extends ArtifactDownloader { @Getter(AccessLevel.PACKAGE) // Setter for unit test @Setter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig s3ClientExceptionRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1L)) - .maxRetryInterval(Duration.ofMinutes(1L)).maxAttempt(Integer.MAX_VALUE) - .retryableExceptions(Arrays.asList(SdkClientException.class, - IOException.class, RetryableServerErrorException.class)).build(); + private RetryUtils.RetryConfig s3ClientExceptionRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1L)) + .maxRetryInterval(Duration.ofMinutes(1L)) + .maxAttempt(Integer.MAX_VALUE) + .retryableExceptions( + Arrays.asList(SdkClientException.class, IOException.class, RetryableServerErrorException.class)) + .build(); /** * Constructor. @@ -71,7 +73,7 @@ public class S3Downloader extends ArtifactDownloader { * @param clientFactory S3 client factory */ protected S3Downloader(S3SdkClientFactory clientFactory, ComponentIdentifier identifier, ComponentArtifact artifact, - Path artifactDir, ComponentStore componentStore) throws InvalidArtifactUriException { + Path artifactDir, ComponentStore componentStore) throws InvalidArtifactUriException { super(identifier, artifact, artifactDir, componentStore); this.s3ClientFactory = clientFactory; this.s3ObjectPath = getS3PathForURI(artifact.getArtifactUri()); @@ -89,18 +91,25 @@ public void cleanup() throws IOException { } - @SuppressWarnings( - {"PMD.CloseResource", "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.CloseResource", "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) @Override protected long download(long rangeStart, long rangeEnd, MessageDigest messageDigest) throws InterruptedException, PackageDownloadException { String bucket = s3ObjectPath.bucket; String key = s3ObjectPath.key; - GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucket).key(key) - .range(String.format(HTTP_RANGE_HEADER_FORMAT, rangeStart, rangeEnd)).build(); - logger.atDebug().kv("bucket", getObjectRequest.bucket()).kv("s3-key", getObjectRequest.key()) - .kv("range", getObjectRequest.range()).log("Getting s3 object request"); + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucket) + .key(key) + .range(String.format(HTTP_RANGE_HEADER_FORMAT, rangeStart, rangeEnd)) + .build(); + logger.atDebug() + .kv("bucket", getObjectRequest.bucket()) + .kv("s3-key", getObjectRequest.key()) + .kv("range", getObjectRequest.range()) + .log("Getting s3 object request"); try { return RetryUtils.runWithRetry(s3ClientExceptionRetryConfig, () -> { @@ -125,14 +134,14 @@ protected long download(long rangeStart, long rangeEnd, MessageDigest messageDig } catch (S3Exception e) { if (e.statusCode() == HttpStatusCode.FORBIDDEN) { throw new PackageDownloadException(getErrorString("S3 GetObject returns 403 Access Denied. " - + "Ensure the IAM role associated with the core device has a policy granting s3:GetObject"), - e).withErrorContext(e, DeploymentErrorCode.S3_GET_OBJECT_ACCESS_DENIED); + + "Ensure the IAM role associated with the core device has a policy granting s3:GetObject"), e) + .withErrorContext(e, DeploymentErrorCode.S3_GET_OBJECT_ACCESS_DENIED); } if (e.statusCode() == HttpStatusCode.NOT_FOUND) { throw new PackageDownloadException(getErrorString("S3 GetObject returns 404 Resource Not Found." + "Ensure the IAM role associated with the core device has a policy granting s3:GetObject " - + "and the artifact object uri is correct"), - e).withErrorContext(e, DeploymentErrorCode.S3_GET_OBJECT_RESOURCE_NOT_FOUND); + + "and the artifact object uri is correct"), e) + .withErrorContext(e, DeploymentErrorCode.S3_GET_OBJECT_RESOURCE_NOT_FOUND); } throw new PackageDownloadException(getErrorString("Failed to download object from S3"), e); } catch (Exception e) { @@ -157,7 +166,9 @@ public Optional checkDownloadable() { return Optional.ofNullable(s3ClientFactory.getConfigValidationError()); } - @SuppressWarnings({"PMD.CloseResource", "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.CloseResource", "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) @Override public Long getDownloadSize() throws InterruptedException, PackageDownloadException { logger.atDebug().setEventType("get-download-size-from-s3").log(); @@ -181,14 +192,14 @@ public Long getDownloadSize() throws InterruptedException, PackageDownloadExcept } catch (S3Exception e) { if (e.statusCode() == HttpStatusCode.FORBIDDEN) { throw new PackageDownloadException(getErrorString("S3 HeadObject returns 403 Access Denied. Ensure " - + "the IAM role associated with the core device has a policy granting s3:GetObject"), - e).withErrorContext(e, DeploymentErrorCode.S3_HEAD_OBJECT_ACCESS_DENIED); + + "the IAM role associated with the core device has a policy granting s3:GetObject"), e) + .withErrorContext(e, DeploymentErrorCode.S3_HEAD_OBJECT_ACCESS_DENIED); } if (e.statusCode() == HttpStatusCode.NOT_FOUND) { throw new PackageDownloadException(getErrorString("S3 HeadObject returns 404 Resource Not Found." + "Ensure the IAM role associated with the core device has a policy granting s3:GetObject " - + "and the artifact object uri is correct"), - e).withErrorContext(e, DeploymentErrorCode.S3_HEAD_OBJECT_RESOURCE_NOT_FOUND); + + "and the artifact object uri is correct"), e) + .withErrorContext(e, DeploymentErrorCode.S3_HEAD_OBJECT_RESOURCE_NOT_FOUND); } throw new PackageDownloadException(getErrorString("Failed to head artifact object from S3"), e); } catch (Exception e) { @@ -196,20 +207,23 @@ public Long getDownloadSize() throws InterruptedException, PackageDownloadExcept } } - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) private S3Client getRegionClientForBucket(String bucket) throws InterruptedException, PackageDownloadException { GetBucketLocationRequest getBucketLocationRequest = GetBucketLocationRequest.builder().bucket(bucket).build(); String region = null; try { region = RetryUtils.runWithRetry(s3ClientExceptionRetryConfig, () -> { try { - return s3ClientFactory.getS3Client().getBucketLocation(getBucketLocationRequest) + return s3ClientFactory.getS3Client() + .getBucketLocation(getBucketLocationRequest) .locationConstraintAsString(); } catch (S3Exception e) { throwRetryableOrNonRetryable(e, "GetBucketLocation"); throw e; // Call above is guaranteed to throw. This line will not execute } - },"get-bucket-location", logger); + }, "get-bucket-location", logger); } catch (InterruptedException e) { throw e; } catch (S3Exception e) { @@ -226,8 +240,8 @@ private S3Client getRegionClientForBucket(String bucket) throws InterruptedExcep .withErrorContext(e, DeploymentErrorCode.S3_GET_BUCKET_LOCATION_ACCESS_DENIED); } if (e.statusCode() == HttpStatusCode.NOT_FOUND) { - throw new PackageDownloadException(getErrorString("S3 GetBucketLocation returns 404 Resource Not" - + " Found"), e) + throw new PackageDownloadException( + getErrorString("S3 GetBucketLocation returns 404 Resource Not" + " Found"), e) .withErrorContext(e, DeploymentErrorCode.S3_GET_BUCKET_LOCATION_RESOURCE_NOT_FOUND); } throw new PackageDownloadException(getErrorString("Failed to determine S3 bucket location"), e); @@ -239,8 +253,7 @@ private S3Client getRegionClientForBucket(String bucket) throws InterruptedExcep return s3ClientFactory.getClientForRegion(Utils.isEmpty(region) ? Region.US_EAST_1 : Region.of(region)); } - private S3ObjectPath getS3PathForURI(URI artifactURI) - throws InvalidArtifactUriException { + private S3ObjectPath getS3PathForURI(URI artifactURI) throws InvalidArtifactUriException { Matcher s3PathMatcher = S3_PATH_REGEX.matcher(artifactURI.toString()); if (!s3PathMatcher.matches()) { // Bad URI diff --git a/src/main/java/com/aws/greengrass/componentmanager/converter/RecipeLoader.java b/src/main/java/com/aws/greengrass/componentmanager/converter/RecipeLoader.java index a2502699e4..7ef67e98e6 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/converter/RecipeLoader.java +++ b/src/main/java/com/aws/greengrass/componentmanager/converter/RecipeLoader.java @@ -58,8 +58,7 @@ public RecipeLoader(PlatformResolver platformResolver) { * @throws PackageLoadingException when there are issues parsing the string */ public static com.amazon.aws.iot.greengrass.component.common.ComponentRecipe parseRecipe(String recipe, - RecipeFormat recipeFormat) - throws PackageLoadingException { + RecipeFormat recipeFormat) throws PackageLoadingException { ObjectMapper mapper = getObjectMapperForRecipeFormat(recipeFormat); try { @@ -75,13 +74,12 @@ public static com.amazon.aws.iot.greengrass.component.common.ComponentRecipe par private static ObjectMapper getObjectMapperForRecipeFormat(RecipeFormat recipeFormat) { switch (recipeFormat) { - case JSON: - return SerializerFactory.getRecipeSerializerJson(); - case YAML: - return SerializerFactory.getRecipeSerializer(); - default: - throw new IllegalArgumentException( - String.format("No object mapper for recipe format %s", recipeFormat)); + case JSON: + return SerializerFactory.getRecipeSerializerJson(); + case YAML: + return SerializerFactory.getRecipeSerializer(); + default: + throw new IllegalArgumentException(String.format("No object mapper for recipe format %s", recipeFormat)); } } @@ -97,9 +95,9 @@ public Optional loadFromFile(String recipeFileContent) throws P com.amazon.aws.iot.greengrass.component.common.ComponentRecipe componentRecipe = parseRecipe(recipeFileContent, RecipeFormat.YAML); if (componentRecipe.getManifests() == null || componentRecipe.getManifests().isEmpty()) { - throw new PackageLoadingException( - String.format("Recipe file %s-%s.yaml is missing manifests", componentRecipe.getComponentName(), - componentRecipe.getComponentVersion()), DeploymentErrorCode.RECIPE_MISSING_MANIFEST); + throw new PackageLoadingException(String.format("Recipe file %s-%s.yaml is missing manifests", + componentRecipe.getComponentName(), componentRecipe.getComponentVersion()), + DeploymentErrorCode.RECIPE_MISSING_MANIFEST); } Optional optionalPlatformSpecificManifest = @@ -117,13 +115,18 @@ public Optional loadFromFile(String recipeFileContent) throws P dependencyPropertiesMap.putAll(componentRecipe.getComponentDependencies()); } - ComponentRecipe packageRecipe = ComponentRecipe.builder().componentName(componentRecipe.getComponentName()) - .version(componentRecipe.getComponentVersion()).publisher(componentRecipe.getComponentPublisher()) + ComponentRecipe packageRecipe = ComponentRecipe.builder() + .componentName(componentRecipe.getComponentName()) + .version(componentRecipe.getComponentVersion()) + .publisher(componentRecipe.getComponentPublisher()) .recipeTemplateVersion(componentRecipe.getRecipeFormatVersion()) - .componentType(componentRecipe.getComponentType()).dependencies(dependencyPropertiesMap).lifecycle( + .componentType(componentRecipe.getComponentType()) + .dependencies(dependencyPropertiesMap) + .lifecycle( convertLifecycleFromFile(componentRecipe.getLifecycle(), platformSpecificManifest, selectors)) .artifacts(convertArtifactsFromFile(platformSpecificManifest.getArtifacts())) - .componentConfiguration(componentRecipe.getComponentConfiguration()).build(); + .componentConfiguration(componentRecipe.getComponentConfiguration()) + .build(); return Optional.of(packageRecipe); } @@ -133,16 +136,21 @@ private static List convertArtifactsFromFile( if (artifacts == null || artifacts.isEmpty()) { return Collections.emptyList(); } - return artifacts.stream().filter(Objects::nonNull).map(RecipeLoader::convertArtifactFromFile) + return artifacts.stream() + .filter(Objects::nonNull) + .map(RecipeLoader::convertArtifactFromFile) .collect(Collectors.toList()); } private static ComponentArtifact convertArtifactFromFile( @Nonnull com.amazon.aws.iot.greengrass.component.common.ComponentArtifact componentArtifact) { - return ComponentArtifact.builder().artifactUri(componentArtifact.getUri()) - .algorithm(componentArtifact.getAlgorithm()).checksum(componentArtifact.getDigest()) + return ComponentArtifact.builder() + .artifactUri(componentArtifact.getUri()) + .algorithm(componentArtifact.getAlgorithm()) + .checksum(componentArtifact.getDigest()) .unarchive(componentArtifact.getUnarchive()) - .permission(convertPermissionFromFile(componentArtifact.getPermission())).build(); + .permission(convertPermissionFromFile(componentArtifact.getPermission())) + .build(); } /** @@ -153,8 +161,10 @@ private static ComponentArtifact convertArtifactFromFile( */ private static Set collectAllSelectors(@Nonnull List manifests) { Set allSelectors = new HashSet<>(); - manifests.stream().map(PlatformSpecificManifest::getSelections) - .filter(Objects::nonNull).forEach(allSelectors::addAll); + manifests.stream() + .map(PlatformSpecificManifest::getSelections) + .filter(Objects::nonNull) + .forEach(allSelectors::addAll); allSelectors.add(PlatformResolver.ALL_KEYWORD); // implicit, it is ok if it was specified explicitly return allSelectors; } @@ -163,13 +173,12 @@ private static Set collectAllSelectors(@Nonnull List convertLifecycleFromFile(@Nonnull Map lifecycleMap, - @Nonnull PlatformSpecificManifest manifest, - @Nonnull Set allSelectors) { + @Nonnull PlatformSpecificManifest manifest, @Nonnull Set allSelectors) { // If there is manifest level lifecycle Map manifestLifecycle = manifest.getLifecycle(); if (manifestLifecycle != null && !manifestLifecycle.isEmpty()) { @@ -180,12 +189,12 @@ private static Map convertLifecycleFromFile(@Nonnull Map: (optional) - // Section: - // : (optional) - // body - Object filtered = PlatformResolver.filterPlatform(lifecycleMap, allSelectors, - manifest.getSelections()).orElse(Collections.emptyMap()); + // : (optional) + // Section: + // : (optional) + // body + Object filtered = PlatformResolver.filterPlatform(lifecycleMap, allSelectors, manifest.getSelections()) + .orElse(Collections.emptyMap()); if (filtered instanceof Map && !((Map) filtered).isEmpty()) { return (Map) filtered; } else if (!lifecycleMap.isEmpty()) { diff --git a/src/main/java/com/aws/greengrass/componentmanager/exceptions/IncompatiblePlatformClaimByComponentException.java b/src/main/java/com/aws/greengrass/componentmanager/exceptions/IncompatiblePlatformClaimByComponentException.java index 38675a97b0..a315b2312e 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/exceptions/IncompatiblePlatformClaimByComponentException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/exceptions/IncompatiblePlatformClaimByComponentException.java @@ -15,20 +15,19 @@ public class IncompatiblePlatformClaimByComponentException extends PackagingExce static final long serialVersionUID = -3387516993124229948L; - public IncompatiblePlatformClaimByComponentException(String initialMessage, String componentName, - Map platform) { + Map platform) { super(makeMessage(initialMessage, componentName, platform)); super.addErrorCode(NO_AVAILABLE_COMPONENT_VERSION); super.addErrorCode(COMPONENT_VERSION_REQUIREMENTS_NOT_MET); } private static String makeMessage(String initialMessage, String componentName, - Map platformRequirements) { + Map platformRequirements) { StringBuilder sb = new StringBuilder(initialMessage.trim()); sb.append(" Check whether the component platform specifications mentioned in its recipe match the " - + "core device platform constraints. If the component is not supported on the core device " - + "platform with classic runtime, revise deployments to resolve the conflict. Component '") + + "core device platform constraints. If the component is not supported on the core device " + + "platform with classic runtime, revise deployments to resolve the conflict. Component '") .append(componentName) .append("' is incompatible with the core device(classic) platform requirements - "); for (Map.Entry requirement : platformRequirements.entrySet()) { diff --git a/src/main/java/com/aws/greengrass/componentmanager/exceptions/MissingRequiredComponentsException.java b/src/main/java/com/aws/greengrass/componentmanager/exceptions/MissingRequiredComponentsException.java index aa871b1da5..9befcc0fa3 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/exceptions/MissingRequiredComponentsException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/exceptions/MissingRequiredComponentsException.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package com.aws.greengrass.componentmanager.exceptions; import com.aws.greengrass.deployment.errorcode.DeploymentErrorCode; diff --git a/src/main/java/com/aws/greengrass/componentmanager/exceptions/NoAvailableComponentVersionException.java b/src/main/java/com/aws/greengrass/componentmanager/exceptions/NoAvailableComponentVersionException.java index 27ad12d58e..825543cea9 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/exceptions/NoAvailableComponentVersionException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/exceptions/NoAvailableComponentVersionException.java @@ -18,26 +18,27 @@ public class NoAvailableComponentVersionException extends PackagingException { static final long serialVersionUID = -3387516993124229948L; public NoAvailableComponentVersionException(String initialMessage, String componentName, - Map requirements) { + Map requirements) { super(makeMessage(initialMessage, componentName, requirements)); super.addErrorCode(NO_AVAILABLE_COMPONENT_VERSION); super.addErrorCode(COMPONENT_VERSION_REQUIREMENTS_NOT_MET); } public NoAvailableComponentVersionException(String initialMessage, String componentName, - Map requirements, Throwable cause) { + Map requirements, Throwable cause) { super(makeMessage(initialMessage, componentName, requirements), cause); super.addErrorCode(NO_AVAILABLE_COMPONENT_VERSION); super.addErrorCode(COMPONENT_VERSION_REQUIREMENTS_NOT_MET); } private static String makeMessage(String initialMessage, String componentName, - Map requirements) { + Map requirements) { StringBuilder sb = new StringBuilder(initialMessage.trim()); sb.append(" Check whether the version constraints conflict and that the component exists in your AWS " - + "account with a version that matches the version constraints. " - + "If the version constraints conflict, revise deployments to resolve the conflict. Component ") - .append(componentName).append(" version constraints:"); + + "account with a version that matches the version constraints. " + + "If the version constraints conflict, revise deployments to resolve the conflict. Component ") + .append(componentName) + .append(" version constraints:"); for (Map.Entry req : requirements.entrySet()) { sb.append(' ').append(req.getKey()).append(" requires ").append(req.getValue().toString()).append(','); diff --git a/src/main/java/com/aws/greengrass/componentmanager/exceptions/PackagingException.java b/src/main/java/com/aws/greengrass/componentmanager/exceptions/PackagingException.java index f4c06a3523..ff844b677f 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/exceptions/PackagingException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/exceptions/PackagingException.java @@ -5,7 +5,6 @@ package com.aws.greengrass.componentmanager.exceptions; - import com.aws.greengrass.deployment.errorcode.DeploymentErrorCode; import com.aws.greengrass.deployment.exceptions.DeploymentException; diff --git a/src/main/java/com/aws/greengrass/componentmanager/models/ComponentArtifact.java b/src/main/java/com/aws/greengrass/componentmanager/models/ComponentArtifact.java index 0114dc988b..c9a962df14 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/models/ComponentArtifact.java +++ b/src/main/java/com/aws/greengrass/componentmanager/models/ComponentArtifact.java @@ -18,7 +18,8 @@ @AllArgsConstructor public class ComponentArtifact { - @NonNull URI artifactUri; + @NonNull + URI artifactUri; String checksum; diff --git a/src/main/java/com/aws/greengrass/componentmanager/models/ComponentMetadata.java b/src/main/java/com/aws/greengrass/componentmanager/models/ComponentMetadata.java index 213441ef00..ccc594705d 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/models/ComponentMetadata.java +++ b/src/main/java/com/aws/greengrass/componentmanager/models/ComponentMetadata.java @@ -15,7 +15,7 @@ public class ComponentMetadata implements Comparable { ComponentIdentifier componentIdentifier; - Map dependencies; // from dependency package name to version requirement + Map dependencies; // from dependency package name to version requirement @Override public int compareTo(ComponentMetadata o) { diff --git a/src/main/java/com/aws/greengrass/componentmanager/models/Permission.java b/src/main/java/com/aws/greengrass/componentmanager/models/Permission.java index 36acfb10fb..cb5cd6c979 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/models/Permission.java +++ b/src/main/java/com/aws/greengrass/componentmanager/models/Permission.java @@ -10,12 +10,12 @@ import lombok.NonNull; import lombok.Value; - /** * Permission settings for component artifacts. Read and Execute permissions can be set. By default, the read permission * is set to allow only the owner of the artifact on the disk and the execute permission is set to NONE. * - *

Execute permissions can override read - execute permissions needs the file to be readable + *

+ * Execute permissions can override read - execute permissions needs the file to be readable */ @Value @Builder @@ -33,12 +33,12 @@ public class Permission { * @return a permission. */ public FileSystemPermission toFileSystemPermission() { - // user group is considered to be owner + // user group is considered to be owner return FileSystemPermission.builder() .ownerRead(true) // we always want user to read .ownerExecute(execute == PermissionType.ALL || execute == PermissionType.OWNER) - .groupRead(read == PermissionType.ALL || read == PermissionType.OWNER - || execute == PermissionType.OWNER || execute == PermissionType.ALL) // execute needs read + .groupRead(read == PermissionType.ALL || read == PermissionType.OWNER || execute == PermissionType.OWNER + || execute == PermissionType.ALL) // execute needs read .groupExecute(execute == PermissionType.OWNER || execute == PermissionType.ALL) .otherRead(read == PermissionType.ALL || execute == PermissionType.ALL) .otherExecute(execute == PermissionType.ALL) diff --git a/src/main/java/com/aws/greengrass/componentmanager/models/PermissionType.java b/src/main/java/com/aws/greengrass/componentmanager/models/PermissionType.java index b02ead691e..6b7828416b 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/models/PermissionType.java +++ b/src/main/java/com/aws/greengrass/componentmanager/models/PermissionType.java @@ -6,8 +6,8 @@ package com.aws.greengrass.componentmanager.models; /** - * Permission attribute to set. In Linux this corresponds to setting the User or Other bits of the standard POSIX - * file permissions. In Windows this would correspond with modifying the ACL for owner and "Everyone" groups. + * Permission attribute to set. In Linux this corresponds to setting the User or Other bits of the standard POSIX file + * permissions. In Windows this would correspond with modifying the ACL for owner and "Everyone" groups. */ public enum PermissionType { /** diff --git a/src/main/java/com/aws/greengrass/componentmanager/models/RecipeMetadata.java b/src/main/java/com/aws/greengrass/componentmanager/models/RecipeMetadata.java index b1f57943e4..a2e62bb231 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/models/RecipeMetadata.java +++ b/src/main/java/com/aws/greengrass/componentmanager/models/RecipeMetadata.java @@ -15,7 +15,8 @@ */ @Data @RequiredArgsConstructor -@NoArgsConstructor // need for JSON deserialization +@NoArgsConstructor // need for JSON deserialization public class RecipeMetadata { - @NonNull String componentVersionArn; + @NonNull + String componentVersionArn; } diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DefaultDockerClient.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DefaultDockerClient.java index 8e109c50ce..6b1f678fde 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DefaultDockerClient.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DefaultDockerClient.java @@ -61,9 +61,9 @@ public boolean dockerInstalled() { * Login to given docker registry. * * @param registry Registry to log into, with credentials encapsulated - * @throws DockerLoginException error in authenticating with the registry + * @throws DockerLoginException error in authenticating with the registry * @throws UserNotAuthorizedForDockerException when current user is not authorized to use docker - * @throws DockerServiceUnavailableException an error that can be potentially fixed through retries + * @throws DockerServiceUnavailableException an error that can be potentially fixed through retries */ public void login(Registry registry) throws DockerLoginException, UserNotAuthorizedForDockerException, DockerServiceUnavailableException { @@ -105,12 +105,12 @@ public void login(Registry registry) * Pull given docker image. * * @param image Image to download - * @throws DockerServiceUnavailableException an error that can be potentially fixed through retries + * @throws DockerServiceUnavailableException an error that can be potentially fixed through retries * @throws InvalidImageOrAccessDeniedException an error indicating incorrect image specification or auth issues with - * the registry + * the registry * @throws UserNotAuthorizedForDockerException when current user is not authorized to use docker - * @throws ConnectionException network error - * @throws DockerPullException unexpected error + * @throws ConnectionException network error + * @throws DockerPullException unexpected error */ public void pullImage(Image image) throws DockerServiceUnavailableException, InvalidImageOrAccessDeniedException, UserNotAuthorizedForDockerException, DockerPullException, ConnectionException { @@ -158,12 +158,12 @@ public void pullImage(Image image) throws DockerServiceUnavailableException, Inv * Check if an image exists locally. * * @param image image to check locally - * @throws DockerServiceUnavailableException an error that can be potentially fixed through retries + * @throws DockerServiceUnavailableException an error that can be potentially fixed through retries * @throws UserNotAuthorizedForDockerException when current user is not authorized to use docker * @throws DockerImageQueryException unexpected error */ - public boolean imageExistsLocally(Image image) throws DockerServiceUnavailableException, - UserNotAuthorizedForDockerException, DockerImageQueryException { + public boolean imageExistsLocally(Image image) + throws DockerServiceUnavailableException, UserNotAuthorizedForDockerException, DockerImageQueryException { CliResponse response = runDockerCmd(String.format("docker images -q %s", image.getImageFullName())); @@ -176,8 +176,8 @@ public boolean imageExistsLocally(Image image) throws DockerServiceUnavailableEx return StringUtils.isNotBlank(response.getOut()); } else { throw new DockerImageQueryException( - String.format("Unexpected error while trying to perform docker images -q %s", response.err), - response.failureCause); + String.format("Unexpected error while trying to perform docker images -q %s", response.err), + response.failureCause); } } @@ -209,8 +209,8 @@ private CliResponse runDockerCmd(String cmd, Map envs) { private Optional checkUserAuthorizationError(CliResponse response) { UserNotAuthorizedForDockerException error = null; - if (response.exit.isPresent() && response.exit.get() != 0 && response.err - .contains("Got permission denied while trying to connect to the Docker daemon socket")) { + if (response.exit.isPresent() && response.exit.get() != 0 + && response.err.contains("Got permission denied while trying to connect to the Docker daemon socket")) { error = new UserNotAuthorizedForDockerException("User not authorized to use docker, if you're " + "not running greengrass as root, please add the user you're running with to docker group " + "and redo the deployment"); diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageArtifactParser.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageArtifactParser.java index f6de387b0b..bb8f1df105 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageArtifactParser.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageArtifactParser.java @@ -136,7 +136,8 @@ public static Image getImage(ComponentArtifact artifact) throws InvalidArtifactU if (Utils.isEmpty(imageTag) && Utils.isEmpty(imageDigest)) { // No digest/tag specified, docker engine will pull the latest image - logger.atWarn().kv("artifact-uri", artifact.getArtifactUri()) + logger.atWarn() + .kv("artifact-uri", artifact.getArtifactUri()) .log("An image version is not present. Specify an image version via an image tag or digest to" + " ensure that the component is immutable and that the deployment will consistently " + "deliver the same artifacts"); @@ -168,7 +169,6 @@ private static Registry getRegistryFromArtifact(String endpoint) { return new Registry(endpoint, source, type); } - private static boolean containsAll(String str, List subStrs) { for (String subStr : subStrs) { if (!str.contains(subStr)) { diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageDownloader.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageDownloader.java index 9991128e20..1d1783a06e 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageDownloader.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/DockerImageDownloader.java @@ -42,39 +42,48 @@ import static com.aws.greengrass.componentmanager.plugins.docker.DockerImageArtifactParser.DOCKER_TAG_LATEST; -@SuppressWarnings({"PMD.SignatureDeclareThrowsException", "PMD.AvoidCatchingGenericException", - "PMD.AvoidInstanceofChecksInCatchClause", "PMD.AvoidRethrowingException"}) +@SuppressWarnings({ + "PMD.SignatureDeclareThrowsException", + "PMD.AvoidCatchingGenericException", + "PMD.AvoidInstanceofChecksInCatchClause", + "PMD.AvoidRethrowingException" +}) public class DockerImageDownloader extends ArtifactDownloader { - static final String DOCKER_NOT_INSTALLED_ERROR_MESSAGE = "Docker engine is not installed. Install Docker and " - + "retry the deployment."; + static final String DOCKER_NOT_INSTALLED_ERROR_MESSAGE = + "Docker engine is not installed. Install Docker and " + "retry the deployment."; private final EcrAccessor ecrAccessor; private final DefaultDockerClient dockerClient; private final MqttClient mqttClient; @Setter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig infiniteAttemptsRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1L)) - .maxRetryInterval(Duration.ofMinutes(64L)).maxAttempt(Integer.MAX_VALUE).retryableExceptions( - Arrays.asList(ConnectionException.class, SdkClientException.class, ServerException.class)).build(); + private RetryUtils.RetryConfig infiniteAttemptsRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1L)) + .maxRetryInterval(Duration.ofMinutes(64L)) + .maxAttempt(Integer.MAX_VALUE) + .retryableExceptions( + Arrays.asList(ConnectionException.class, SdkClientException.class, ServerException.class)) + .build(); @Setter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig finiteAttemptsRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofSeconds(10L)) - .maxRetryInterval(Duration.ofMinutes(32L)).maxAttempt(30).retryableExceptions( - Arrays.asList(DockerServiceUnavailableException.class, DockerLoginException.class, - SdkClientException.class, ServerException.class)).build(); + private RetryUtils.RetryConfig finiteAttemptsRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofSeconds(10L)) + .maxRetryInterval(Duration.ofMinutes(32L)) + .maxAttempt(30) + .retryableExceptions(Arrays.asList(DockerServiceUnavailableException.class, DockerLoginException.class, + SdkClientException.class, ServerException.class)) + .build(); /** * Constructor. * - * @param identifier component identifier - * @param artifact artifact to download + * @param identifier component identifier + * @param artifact artifact to download * @param artifactDir artifact store path - * @param context context + * @param context context * @param componentStore componentStore */ public DockerImageDownloader(ComponentIdentifier identifier, ComponentArtifact artifact, Path artifactDir, - Context context, ComponentStore componentStore) { + Context context, ComponentStore componentStore) { super(identifier, artifact, artifactDir, componentStore); ecrAccessor = context.get(EcrAccessor.class); dockerClient = context.get(DefaultDockerClient.class); @@ -82,8 +91,8 @@ public DockerImageDownloader(ComponentIdentifier identifier, ComponentArtifact a } DockerImageDownloader(ComponentIdentifier identifier, ComponentArtifact artifact, Path artifactDir, - DefaultDockerClient dockerClient, EcrAccessor ecrAccessor, MqttClient mqttClient, - ComponentStore componentStore) { + DefaultDockerClient dockerClient, EcrAccessor ecrAccessor, MqttClient mqttClient, + ComponentStore componentStore) { super(identifier, artifact, artifactDir, componentStore); this.dockerClient = dockerClient; this.ecrAccessor = ecrAccessor; @@ -121,8 +130,9 @@ public boolean downloadRequired() throws PackageDownloadException { } if (DOCKER_TAG_LATEST.equals(image.getTag())) { - logger.atDebug().log("Image tag: [{}] found, will require download and not check for the image locally.", - DOCKER_TAG_LATEST); + logger.atDebug() + .log("Image tag: [{}] found, will require download and not check for the image locally.", + DOCKER_TAG_LATEST); return true; } else { return !dockerClient.imageExistsLocally(image); @@ -204,13 +214,14 @@ private File performDownloadSteps(Image image) throws PackageDownloadException, // Login to registry // TODO: [P44950158]: Avoid logging into registries which might already have been logged in previously - // with the same and valid credentials by maintaining a cache across artifacts and deployments + // with the same and valid credentials by maintaining a cache across artifacts and deployments run(() -> { if (credentialsUsable(image)) { dockerClient.login(image.getRegistry()); } else { // Credentials have expired, re-fetch and login again - logger.atInfo().kv("registry-endpoint", image.getRegistry().getEndpoint()) + logger.atInfo() + .kv("registry-endpoint", image.getRegistry().getEndpoint()) .log("Registry credentials have expired," + "fetching fresh credentials and logging in again"); credentialRefreshNeeded.set(true); @@ -233,7 +244,8 @@ private File performDownloadSteps(Image image) throws PackageDownloadException, }, "get-ecr-image", logger); } else { // Credentials have expired, re-fetch and login again - logger.atInfo().kv("registry-endpoint", image.getRegistry().getEndpoint()) + logger.atInfo() + .kv("registry-endpoint", image.getRegistry().getEndpoint()) .log("Registry credentials have expired, fetching fresh credentials and logging in again"); credentialRefreshNeeded.set(true); } @@ -245,7 +257,7 @@ private File performDownloadSteps(Image image) throws PackageDownloadException, } private String getRegionFromArtifactUri(String artifactUriStr) { - //get the actual region from the artifact uri + // get the actual region from the artifact uri String regionStr = ""; @@ -281,7 +293,8 @@ private void run(CrashableSupplier task, String description, S // Indefinite retry for errors that are due to connectivity issues and can be // resolved when connectivity comes back .runWithRetry(infiniteAttemptsRetryConfig, () -> runWithConnectionErrorCheck(task), description, - logger), description, logger); + logger), + description, logger); } catch (InterruptedException e) { throw e; } catch (Exception e) { @@ -306,6 +319,7 @@ private T runWithConnectionErrorCheck(CrashableSupplier task) /** * Cleanup component, delete docker image when component being removed. + * * @throws IOException exception */ @Override @@ -342,13 +356,17 @@ public boolean ifImageUsedByOther(ComponentStore componentStore) throws PackageL continue; } ComponentRecipe recipe = componentStore.getPackageRecipe(identifier); - if (recipe.getArtifacts().stream().anyMatch( - i -> i.getArtifactUri().equals(artifact.getArtifactUri()))) { + if (recipe.getArtifacts() + .stream() + .anyMatch(i -> i.getArtifactUri().equals(artifact.getArtifactUri()))) { return true; } } catch (SemverException e) { - logger.atWarn().kv("identifier", identifier.getName()).kv("compVersion", compVersion) - .setCause(e).log("Error happened when semver being created"); + logger.atWarn() + .kv("identifier", identifier.getName()) + .kv("compVersion", compVersion) + .setCause(e) + .log("Error happened when semver being created"); } } } diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/EcrAccessor.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/EcrAccessor.java index 066eaeb804..14a41a33b6 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/EcrAccessor.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/EcrAccessor.java @@ -34,7 +34,7 @@ public class EcrAccessor { /** * Constructor. * - * @param deviceConfiguration Device config + * @param deviceConfiguration Device config * @param lazyCredentialProvider AWS credentials provider */ @Inject @@ -44,7 +44,6 @@ public EcrAccessor(DeviceConfiguration deviceConfiguration, LazyCredentialProvid this.lazyCredentialProvider = lazyCredentialProvider; } - /** * Get Ecr client with region. * @@ -60,14 +59,15 @@ public EcrClient getClient(String region) { return EcrClient.builder() .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) .region(Region.of(region)) - .credentialsProvider(lazyCredentialProvider).build(); + .credentialsProvider(lazyCredentialProvider) + .build(); } /** * Get credentials(auth token) for a private docker registry in ECR. * * @param registryId Registry id - * @param region actual region + * @param region actual region * @return Registry.Credentials - Registry's authorization information * @throws RegistryAuthException When authentication fails */ @@ -76,7 +76,8 @@ public Registry.Credentials getCredentials(String registryId, String region) thr try (EcrClient client = getClient(region)) { AuthorizationData authorizationData = client.getAuthorizationToken( GetAuthorizationTokenRequest.builder().registryIds(Collections.singletonList(registryId)).build()) - .authorizationData().get(0); + .authorizationData() + .get(0); // Decoded auth token is of the format : String[] authTokenParts = new String(Base64.getDecoder().decode(authorizationData.authorizationToken()), StandardCharsets.UTF_8).split(":"); diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Image.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Image.java index 310ff45ec2..88f7c82c7b 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Image.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Image.java @@ -35,7 +35,6 @@ public class Image { @EqualsAndHashCode.Include private URI artifactUri; - /** * Build an instance from a component artifact. * diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Registry.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Registry.java index 0df01dd556..74feb21109 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Registry.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/Registry.java @@ -33,9 +33,9 @@ public class Registry { /** * Constructor. * - * @param endpoint Registry endpoint + * @param endpoint Registry endpoint * @param registrySource Source of the registry, i.e. hosted in ECR or other registry servers - * @param registryType Type of the registry, i.e. private or public + * @param registryType Type of the registry, i.e. private or public */ public Registry(String endpoint, RegistrySource registrySource, RegistryType registryType) { this.endpoint = endpoint; @@ -103,8 +103,8 @@ public Credentials(String username, String password) { /** * Constructor. * - * @param username username - * @param password password + * @param username username + * @param password password * @param expiresAt time when credential expires */ public Credentials(String username, String password, Instant expiresAt) { diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/ConnectionException.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/ConnectionException.java index b17374e479..b7c083af29 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/ConnectionException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/ConnectionException.java @@ -5,7 +5,6 @@ package com.aws.greengrass.componentmanager.plugins.docker.exceptions; - import static com.aws.greengrass.deployment.errorcode.DeploymentErrorCode.NETWORK_ERROR; public class ConnectionException extends DockerImageDownloadException { diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerLoginException.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerLoginException.java index a7043187e4..4e25ccc5df 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerLoginException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerLoginException.java @@ -3,10 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ - package com.aws.greengrass.componentmanager.plugins.docker.exceptions; - import static com.aws.greengrass.deployment.errorcode.DeploymentErrorCode.DOCKER_LOGIN_ERROR; public class DockerLoginException extends DockerImageDownloadException { diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerServiceUnavailableException.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerServiceUnavailableException.java index 0abede7771..fd989946b0 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerServiceUnavailableException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/DockerServiceUnavailableException.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package com.aws.greengrass.componentmanager.plugins.docker.exceptions; import static com.aws.greengrass.deployment.errorcode.DeploymentErrorCode.DOCKER_SERVICE_UNAVAILABLE; diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/InvalidImageOrAccessDeniedException.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/InvalidImageOrAccessDeniedException.java index 1f9f0f28ae..037220b961 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/InvalidImageOrAccessDeniedException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/InvalidImageOrAccessDeniedException.java @@ -3,10 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ - package com.aws.greengrass.componentmanager.plugins.docker.exceptions; - import static com.aws.greengrass.deployment.errorcode.DeploymentErrorCode.DOCKER_IMAGE_NOT_VALID; public class InvalidImageOrAccessDeniedException extends DockerImageDownloadException { diff --git a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/RegistryAuthException.java b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/RegistryAuthException.java index 2e30dc56f7..0c15083ddc 100644 --- a/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/RegistryAuthException.java +++ b/src/main/java/com/aws/greengrass/componentmanager/plugins/docker/exceptions/RegistryAuthException.java @@ -3,10 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ - package com.aws.greengrass.componentmanager.plugins.docker.exceptions; - import static com.aws.greengrass.deployment.errorcode.DeploymentErrorCode.GET_ECR_CREDENTIAL_ERROR; public class RegistryAuthException extends DockerImageDownloadException { diff --git a/src/main/java/com/aws/greengrass/config/Configuration.java b/src/main/java/com/aws/greengrass/config/Configuration.java index e7154a9e8d..f2fcf5a71d 100644 --- a/src/main/java/com/aws/greengrass/config/Configuration.java +++ b/src/main/java/com/aws/greengrass/config/Configuration.java @@ -46,7 +46,7 @@ public class Configuration { @Inject @SuppressWarnings("LeakingThisInConstructor") - public Configuration(Context c) { // This is one of the few classes that can't use injection + public Configuration(Context c) { // This is one of the few classes that can't use injection root = new Topics(context = c, null, null); c.put(Configuration.class, this); } @@ -56,8 +56,7 @@ public static String[] splitPath(String path) { } /** - * Find, and create if missing, a topic (a name/value pair) in the config - * file. Never returns null. + * Find, and create if missing, a topic (a name/value pair) in the config file. Never returns null. * * @param path String[] of node names to traverse to find or create the Topic */ @@ -66,8 +65,8 @@ public Topic lookup(String... path) { } /** - * Find, and create if missing, a topic (a name/value pair) in the config - * file. Never returns null. + * Find, and create if missing, a topic (a name/value pair) in the config file. Never returns null. + * * @param timestamp modtime of newly created nodes * @param path String[] of node names to traverse to find or create the Topic * @return @@ -77,8 +76,7 @@ public Topic lookup(long timestamp, String... path) { } /** - * Find, and create if missing, a list of topics (name/value pairs) in the - * config file. Never returns null. + * Find, and create if missing, a list of topics (name/value pairs) in the config file. Never returns null. * * @param path String[] of node names to traverse to find or create the Topics */ @@ -87,8 +85,7 @@ public Topics lookupTopics(String... path) { } /** - * Find, and create if missing, a list of topics (name/value pairs) in the - * config file. Never returns null. + * Find, and create if missing, a list of topics (name/value pairs) in the config file. Never returns null. * * @param timestamp modtime of newly created nodes * @param path String[] of node names to traverse to find or create the Topics @@ -98,8 +95,7 @@ public Topics lookupTopics(long timestamp, String... path) { } /** - * Find, but do not create if missing, a topic (a name/value pair) in the - * config file. Returns null if missing. + * Find, but do not create if missing, a topic (a name/value pair) in the config file. Returns null if missing. * * @param path String[] of node names to traverse to find the Topic */ @@ -109,8 +105,7 @@ public Topic find(String... path) { } /** - * Find, but do not create if missing, a topic (a name/value pair) in the - * config file. Returns null if missing. + * Find, but do not create if missing, a topic (a name/value pair) in the config file. Returns null if missing. * * @param path String[] of node names to traverse to find the Topics */ @@ -120,8 +115,7 @@ public Topics findTopics(String... path) { } /** - * Find, but do not create if missing, a node in the - * config file. Returns null if missing. + * Find, but do not create if missing, a node in the config file. Returns null if missing. * * @param path String[] of node names to traverse to find the Topics */ @@ -143,19 +137,17 @@ public int size() { } /** - * Merges a Map into this configuration. The most common use case is for - * reading textual config files via jackson. For example, to merge a - * .yaml file: - *
+ * Merges a Map into this configuration. The most common use case is for reading textual config files via jackson. + * For example, to merge a .yaml file:
+ * * config.mergeMap(timestamp, (Map)JSON.std.with(new * YAMLFactory()).anyFrom(inputStream)); *
- * If you omit the .with(...) clause, you get the default - * parser, which is JSON. You can replace new YAMLFactory() - * with any other supported parser. + * If you omit the .with(...) clause, you get the default parser, which is JSON. You can replace + * new YAMLFactory() with any other supported parser. * * @param timestamp last modified time for the configuration values - * @param map map to merge + * @param map map to merge */ public void mergeMap(long timestamp, Map map) { this.updateMap(map, new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, timestamp)); @@ -164,7 +156,7 @@ public void mergeMap(long timestamp, Map map) { /** * Merges a Map into this configuration. * - * @param map map to merge + * @param map map to merge * @param updateBehavior the updateBehavior of each node to be merged in */ public void updateMap(Map map, UpdateBehaviorTree updateBehavior) { @@ -181,6 +173,7 @@ public void updateMap(Map map, UpdateBehaviorTree updateBehavior /** * If configuration is under update when this function is invoked, block until the update is complete. + * * @throws InterruptedException InterruptedException */ public void waitConfigUpdateComplete() throws InterruptedException { @@ -219,7 +212,7 @@ public Configuration read(String s) throws IOException { /** * Read and merge configuration from a URL. * - * @param url configuration source URL + * @param url configuration source URL * @param useSourceTimestamp true if the modified time should be set based on the value from the server (if any) * @return this with the new configuration merged in * @throws IOException if the reading fails @@ -241,7 +234,9 @@ public Configuration read(URL url, boolean useSourceTimestamp) throws IOExceptio * @throws IOException if the reading fails. */ public Configuration read(Path s) throws IOException { - logger.atInfo().addKeyValue("path", s).setEventType("config-loading") + logger.atInfo() + .addKeyValue("path", s) + .setEventType("config-loading") .log("Read configuration from a file path"); try (BufferedReader br = Files.newBufferedReader(s)) { read(br, extension(s.toString()), Files.getLastModifiedTime(s).toMillis()); @@ -258,47 +253,52 @@ private void read(InputStream in, String extension, long timestamp) throws IOExc /** * Read in a configuration from a Reader and merge it with the current configuration. * - * @param in reader to read new configuration from + * @param in reader to read new configuration from * @param extension extension of the file we're reading in (changes how we deserialize the input data) * @param timestamp timestamp to use as the last modified time - * @throws IOException if reading fails + * @throws IOException if reading fails */ private void read(Reader in, String extension, long timestamp) throws IOException { switch (extension) { - case "json": - mergeMap(timestamp, jsonMapper.readValue(in, Map.class)); - break; - case "yml": - // fallthrough - case "yaml": - mergeMap(timestamp, yamlMapper.readValue(in, Map.class)); - break; - case "tlog": - case "tlog~": - ConfigurationReader.mergeTLogInto(this, in, false, null); - break; - default: - throw new IllegalArgumentException( - "File format '" + extension + "' is not supported. Use one of: yaml, json or tlog"); + case "json": + mergeMap(timestamp, jsonMapper.readValue(in, Map.class)); + break; + case "yml": + // fallthrough + case "yaml": + mergeMap(timestamp, yamlMapper.readValue(in, Map.class)); + break; + case "tlog": + case "tlog~": + ConfigurationReader.mergeTLogInto(this, in, false, null); + break; + default: + throw new IllegalArgumentException( + "File format '" + extension + "' is not supported. Use one of: yaml, json or tlog"); } } /** * Read in a new configuration from a URL and merge it into the current config. * - * @param u URL to read in the configuration from + * @param u URL to read in the configuration from * @param sourceTimestamp true if the URL source timestamp should be used as the last modified time * @return any throwable that occurs from the merge or read */ public Throwable readMerge(URL u, boolean sourceTimestamp) { - /* We run the operation on the publish queue to ensure that no listeners are - * fired while the large config change is happening. They get reconciled - * all together */ + /* + * We run the operation on the publish queue to ensure that no listeners are fired while the large config change + * is happening. They get reconciled all together + */ return context.runOnPublishQueueAndWait(() -> { - logger.atDebug().setEventType("config-merge-start").addKeyValue("url", u) + logger.atDebug() + .setEventType("config-merge-start") + .addKeyValue("url", u) .log("Start merging configuration"); read(u, sourceTimestamp); - logger.atDebug().setEventType("config-merge-finish").addKeyValue("url", u) + logger.atDebug() + .setEventType("config-merge-finish") + .addKeyValue("url", u) .log("Finish merging configuration"); }); } diff --git a/src/main/java/com/aws/greengrass/config/ConfigurationReader.java b/src/main/java/com/aws/greengrass/config/ConfigurationReader.java index a100a83678..7da125a4fe 100644 --- a/src/main/java/com/aws/greengrass/config/ConfigurationReader.java +++ b/src/main/java/com/aws/greengrass/config/ConfigurationReader.java @@ -30,32 +30,31 @@ private ConfigurationReader() { /** * Merge the given transaction log into the given configuration. * - * @param config configuration to merge into - * @param reader reader of the transaction log to read from + * @param config configuration to merge into + * @param reader reader of the transaction log to read from * @param forceTimestamp should ignore if the proposed timestamp is older than current * @param mergeCondition Predicate that returns true if the provided Topic should be merged and false if not * @throws IOException if reading fails */ public static void mergeTLogInto(Configuration config, Reader reader, boolean forceTimestamp, - Predicate mergeCondition) throws IOException { + Predicate mergeCondition) throws IOException { mergeTLogInto(config, reader, forceTimestamp, mergeCondition, ConfigurationMode.WITH_VALUES); } /** * Merge the given transaction log into the given configuration. * - * @param config configuration to merge into - * @param reader reader of the transaction log to read from - * @param forceTimestamp should ignore if the proposed timestamp is older than current - * @param mergeCondition Predicate that returns true if the provided Topic should be merged and false if not + * @param config configuration to merge into + * @param reader reader of the transaction log to read from + * @param forceTimestamp should ignore if the proposed timestamp is older than current + * @param mergeCondition Predicate that returns true if the provided Topic should be merged and false if not * @param configurationMode Configuration mode * @throws IOException if reading fails */ private static void mergeTLogInto(Configuration config, Reader reader, boolean forceTimestamp, - Predicate mergeCondition, ConfigurationMode configurationMode) - throws IOException { - try (BufferedReader in = reader instanceof BufferedReader ? (BufferedReader) reader - : new BufferedReader(reader)) { + Predicate mergeCondition, ConfigurationMode configurationMode) throws IOException { + try (BufferedReader in = + reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader)) { String l; for (l = in.readLine(); l != null; l = in.readLine()) { @@ -102,14 +101,14 @@ private static void mergeTLogInto(Configuration config, Reader reader, boolean f /** * Merge the given transaction log into the given configuration. * - * @param config configuration to merge into - * @param tlogPath path of the tlog file to read to-be-merged config from + * @param config configuration to merge into + * @param tlogPath path of the tlog file to read to-be-merged config from * @param forceTimestamp should ignore if the proposed timestamp is older than current * @param mergeCondition Predicate that returns true if the provided Topic should be merged and false if not * @throws IOException if reading fails */ public static void mergeTLogInto(Configuration config, Path tlogPath, boolean forceTimestamp, - Predicate mergeCondition) throws IOException { + Predicate mergeCondition) throws IOException { try (BufferedReader bufferedReader = Files.newBufferedReader(tlogPath)) { mergeTLogInto(config, bufferedReader, forceTimestamp, mergeCondition); } @@ -126,15 +125,15 @@ private static void mergeTLogInto(Configuration c, Path p, ConfigurationMode con * tree and without losing listeners. Config listeners fire asynchronously as nodes update so if you need the * listeners to wait before all nodes are updated, run this on the context thread to synchronize. * - * @param config configuration to merge into - * @param tlogPath path of the tlog file to read to-be-replaced config from + * @param config configuration to merge into + * @param tlogPath path of the tlog file to read to-be-replaced config from * @param forceTimestamp should ignore if the proposed timestamp is older than current * @param mergeCondition Predicate that returns true if the provided Topic should be merged and false if not - * @param tree Merge behavior hierarchy for the update + * @param tree Merge behavior hierarchy for the update * @throws IOException if update fails */ public static void updateFromTLog(Configuration config, Path tlogPath, boolean forceTimestamp, - Predicate mergeCondition, UpdateBehaviorTree tree) throws IOException { + Predicate mergeCondition, UpdateBehaviorTree tree) throws IOException { // Merge tlog into configuration so that nodes to retain get replaced ConfigurationReader.mergeTLogInto(config, tlogPath, forceTimestamp, mergeCondition); @@ -151,7 +150,8 @@ private static void discardNodesNotInTLog(Configuration config, Path tlogPath, U config.deepForEach((n, b) -> { if (UpdateBehaviorTree.UpdateBehavior.REPLACE.equals(b) && tlogMirror.findNode(n.path()) == null) { - logger.atTrace().kv("node-to-remove", n.getFullName()) + logger.atTrace() + .kv("node-to-remove", n.getFullName()) .log("Removing config node not in source tlog"); n.remove(); } @@ -163,13 +163,15 @@ private static void discardNodesNotInTLog(Configuration config, Path tlogPath, U * Validate the tlog contents at the given path. * * @param tlogPath path to the file to validate. - * @return true if all entries in the file are valid; - * false if file doesn't exist, is empty, or contains invalid entry + * @return true if all entries in the file are valid; false if file doesn't exist, is empty, or contains invalid + * entry */ public static boolean validateTlog(Path tlogPath) { try { if (!Files.exists(tlogPath)) { - logger.atDebug().setEventType("validate-tlog").kv("path", tlogPath) + logger.atDebug() + .setEventType("validate-tlog") + .kv("path", tlogPath) .log("Transaction log file does not exist at given path"); return false; } @@ -193,7 +195,9 @@ public static boolean validateTlog(Path tlogPath) { String l = in.readLine(); // if file is empty, return false if (l == null) { - logger.atError().setEventType("validate-tlog").kv("path", tlogPath) + logger.atError() + .setEventType("validate-tlog") + .kv("path", tlogPath) .log("Empty transaction log file"); return false; } @@ -205,7 +209,10 @@ public static boolean validateTlog(Path tlogPath) { } } } catch (IOException e) { - logger.atError().setCause(e).setEventType("validate-tlog").kv("path", tlogPath) + logger.atError() + .setCause(e) + .setEventType("validate-tlog") + .kv("path", tlogPath) .log("Unable to validate the transaction log content"); return false; } @@ -216,7 +223,7 @@ public static boolean validateTlog(Path tlogPath) { * Create a Configuration based on a transaction log's path. * * @param context root context for the configuration - * @param p path to the transaction log + * @param p path to the transaction log * @return Configuration from the transaction log * @throws IOException if reading the transaction log fails */ @@ -229,8 +236,8 @@ public static Configuration createFromTLog(Context context, Path p) throws IOExc /** * Create a Configuration based on a transaction log's path. * - * @param context root context for the configuration - * @param p path to the transaction log + * @param context root context for the configuration + * @param p path to the transaction log * @param configurationMode Configuration mode * @return Configuration from the transaction log * @throws IOException if reading the transaction log fails diff --git a/src/main/java/com/aws/greengrass/config/ConfigurationWriter.java b/src/main/java/com/aws/greengrass/config/ConfigurationWriter.java index 50aa7df497..c0bf8e2d01 100644 --- a/src/main/java/com/aws/greengrass/config/ConfigurationWriter.java +++ b/src/main/java/com/aws/greengrass/config/ConfigurationWriter.java @@ -39,14 +39,14 @@ public class ConfigurationWriter implements Closeable, ChildChanged { private final Configuration conf; private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean truncateQueued = new AtomicBoolean(); - private final AtomicLong count = new AtomicLong(0); // entries written so far + private final AtomicLong count = new AtomicLong(0); // entries written so far @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "No need for flush immediately to be sync") private boolean flushImmediately; @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "No need to sync config variable") private boolean autoTruncate = false; @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "No need to sync config variable") - private long maxCount = DEFAULT_MAX_TLOG_ENTRIES; // max before truncation - private long retryCount = 0; // retry truncate at this count after error occurred + private long maxCount = DEFAULT_MAX_TLOG_ENTRIES; // max before truncation + private long retryCount = 0; // retry truncate at this count after error occurred private Context context; private static final Logger logger = LogManager.getLogger(ConfigurationWriter.class); @@ -174,15 +174,18 @@ public void childChanged(WhatHappened what, Node n) { try { Coerce.appendParseableString(tlogline, out); } catch (IOException ex) { - logger.atError().setEventType("config-dump-error").addKeyValue("configNode", n.getFullName()) - .setCause(ex).log(); + logger.atError() + .setEventType("config-dump-error") + .addKeyValue("configNode", n.getFullName()) + .setCause(ex) + .log(); } if (flushImmediately) { flush(out); } long currCount = count.incrementAndGet(); - if (autoTruncate && currCount > maxCount && currCount > retryCount && truncateQueued.compareAndSet(false, - true)) { + if (autoTruncate && currCount > maxCount && currCount > retryCount + && truncateQueued.compareAndSet(false, true)) { // childChanged runs on publish thread already. can only queue a task without blocking context.runOnPublishQueue(this::truncateTlog); logger.atDebug(TRUNCATE_TLOG_EVENT).log("queued"); @@ -203,8 +206,8 @@ public void writeAll() { * @throws IOException if I/O error creating output file or writer */ private static Writer newTlogWriter(Path outputPath) throws IOException { - return Files.newBufferedWriter(outputPath, StandardOpenOption.APPEND, - StandardOpenOption.SYNC, StandardOpenOption.CREATE); + return Files.newBufferedWriter(outputPath, StandardOpenOption.APPEND, StandardOpenOption.SYNC, + StandardOpenOption.CREATE); } public static Path getOldTlogPath(Path tlogPath) { diff --git a/src/main/java/com/aws/greengrass/config/Node.java b/src/main/java/com/aws/greengrass/config/Node.java index 286ffb9806..0c42bb98b2 100644 --- a/src/main/java/com/aws/greengrass/config/Node.java +++ b/src/main/java/com/aws/greengrass/config/Node.java @@ -130,6 +130,7 @@ public void remove() { /** * Remove with timestamp check. + * * @param timestamp timestamp */ public void remove(long timestamp) { @@ -161,8 +162,7 @@ protected Object validate(Object newValue, Object oldValue) { public abstract void deepForEachTopic(Consumer f); - public abstract void deepForEach(BiConsumer f, - UpdateBehaviorTree tree); + public abstract void deepForEach(BiConsumer f, UpdateBehaviorTree tree); /** * Check if this node is a child of a node with the given name. @@ -186,11 +186,13 @@ public String[] path() { } if (name == null) { - path = new String[]{}; + path = new String[] {}; return path; } - String[] p = {name}; + String[] p = { + name + }; if (parent != null) { String[] na = new String[p.length + parent.path().length]; @@ -205,8 +207,7 @@ public String[] path() { /** * Get if parents will be notified for changes. * - * @return false iff changes to this node should be ignored by it's parent - * (ie. it's completely handled locally) + * @return false iff changes to this node should be ignored by it's parent (ie. it's completely handled locally) */ public boolean parentNeedsToKnow() { return parent != null && parentNeedsToKnow; diff --git a/src/main/java/com/aws/greengrass/config/PlatformResolver.java b/src/main/java/com/aws/greengrass/config/PlatformResolver.java index a97b45de30..a6560c3c3e 100644 --- a/src/main/java/com/aws/greengrass/config/PlatformResolver.java +++ b/src/main/java/com/aws/greengrass/config/PlatformResolver.java @@ -30,7 +30,9 @@ import java.util.stream.Collectors; import javax.inject.Inject; -@SuppressWarnings({"PMD.AvoidDuplicateLiterals"}) +@SuppressWarnings({ + "PMD.AvoidDuplicateLiterals" +}) public class PlatformResolver { public static final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("wind"); public static final String ALL_KEYWORD = "all"; @@ -57,8 +59,7 @@ public class PlatformResolver { private final Lock lock = LockFactory.newReentrantLock(this); - private static final AtomicReference DETECTED_PLATFORM = - new AtomicReference<>(); + private static final AtomicReference DETECTED_PLATFORM = new AtomicReference<>(); private static Platform initializePlatform() { return Platform.builder() @@ -74,8 +75,7 @@ public PlatformResolver(DeviceConfiguration deviceConfiguration) { } /** - * Get current platform. - * Detect current platform and apply device configuration override. + * Get current platform. Detect current platform and apply device configuration override. * * @return Platform key-value map */ @@ -89,7 +89,7 @@ public Map getCurrentPlatform() { return detected; } Map platform = new HashMap<>(detected); - for (Map.Entry entry: platformOverride.toPOJO().entrySet()) { + for (Map.Entry entry : platformOverride.toPOJO().entrySet()) { if (entry.getValue() instanceof String) { // platform doesn't support Map/List value platform.put(entry.getKey(), (String) entry.getValue()); @@ -156,7 +156,9 @@ private static String getArchDetailInfo() { // on arm. if (ARCH_ARM.equals(arch) || ARCH_AARCH64.equals(arch)) { String archDetail = com.aws.greengrass.util.platforms.Platform.getInstance() - .createNewProcessRunner().sh("uname -m").toLowerCase(); + .createNewProcessRunner() + .sh("uname -m") + .toLowerCase(); // TODO: "uname -m" is not sufficient to capture arch details on all platforms. // Currently only return if detected arm, as required by lambda launcher. if ("armv6l".equals(archDetail) || "armv7l".equals(archDetail) || "armv8l".equals(archDetail)) { @@ -180,17 +182,16 @@ public Optional findBestMatch(List filterPlatform(Map source, Set keywords, - List selectors) { + List selectors) { // // Trying to preserve nulls will make this logic much more difficult than it is. Since this is @@ -199,10 +200,9 @@ public static Optional filterPlatform(Map source, Set selected = selectors == null ? Optional.empty() : - selectors.stream().map(source::get) - .filter(Objects::nonNull) - .findFirst(); + Optional selected = selectors == null + ? Optional.empty() + : selectors.stream().map(source::get).filter(Objects::nonNull).findFirst(); if (!selected.isPresent()) { // consider ALL keyword (this may not be in list of selectors). // Note, do not confuse this with set of keywords which does include "ALL". @@ -220,24 +220,24 @@ public static Optional filterPlatform(Map source, Set filterPlatformMapEntry(e, keywords, selectors)) .filter(Objects::nonNull) - .collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue))); + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); } /** * Diagnostic reporting for bad selector section. + * * @param source Source map * @param keywords Set of valid selector keywords (including ALL) * @param selected Selected section if any, else empty. */ private static void checkForBadSelectorMap(Map source, Set keywords, - Optional selected) { - List notSelector = source.keySet().stream() - .filter(k -> !keywords.contains(k)).collect(Collectors.toList()); + Optional selected) { + List notSelector = + source.keySet().stream().filter(k -> !keywords.contains(k)).collect(Collectors.toList()); if (!notSelector.isEmpty()) { List yesSelector = source.keySet().stream().filter(k -> !keywords.contains(k)).collect(Collectors.toList()); @@ -252,13 +252,14 @@ private static void checkForBadSelectorMap(Map source, Set filterPlatformMapEntry(Map.Entry entry, Set keywords, - List selectors) { + private static Map.Entry filterPlatformMapEntry(Map.Entry entry, + Set keywords, List selectors) { Optional v = filterPlatformIfMap(entry.getValue(), keywords, selectors); if (v.isPresent()) { return new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), v.get()); @@ -270,13 +271,12 @@ private static Map.Entry filterPlatformMapEntry(Map.Entry filterPlatformIfMap(Object source, Set keywords, - List selectors) { + private static Optional filterPlatformIfMap(Object source, Set keywords, List selectors) { if (source instanceof Map) { return filterPlatform((Map) source, keywords, selectors); } else { diff --git a/src/main/java/com/aws/greengrass/config/Subscriber.java b/src/main/java/com/aws/greengrass/config/Subscriber.java index 9a505b7395..b9e9095b47 100644 --- a/src/main/java/com/aws/greengrass/config/Subscriber.java +++ b/src/main/java/com/aws/greengrass/config/Subscriber.java @@ -6,12 +6,10 @@ package com.aws.greengrass.config; /** - * A subscriber is told what Topic changed, but must look in the Topic (t.getOnce()) - * to get the new value. There is no "old value" provided, although the publish framework - * endeavors to suppress notifying when the new value is the same as the old value. - * Subscribers do not necessarily get notified on every change. If a sequence of changes - * happen in rapid succession, they may be collapsed into one notification. This usually - * happens when a compound change occurs. + * A subscriber is told what Topic changed, but must look in the Topic (t.getOnce()) to get the new value. There is no + * "old value" provided, although the publish framework endeavors to suppress notifying when the new value is the same + * as the old value. Subscribers do not necessarily get notified on every change. If a sequence of changes happen in + * rapid succession, they may be collapsed into one notification. This usually happens when a compound change occurs. */ @FunctionalInterface public interface Subscriber extends Watcher { diff --git a/src/main/java/com/aws/greengrass/config/Topic.java b/src/main/java/com/aws/greengrass/config/Topic.java index 45b6066323..dd9e06cb29 100644 --- a/src/main/java/com/aws/greengrass/config/Topic.java +++ b/src/main/java/com/aws/greengrass/config/Topic.java @@ -42,9 +42,9 @@ public static Topic of(Context c, String n, Object v) { * Subscribe to a topic and invoke the subscriber right away on the same thread for a new subscriber. *

* This is the preferred way to get a value from a configuration. Instead of {@code setValue(configValue.getOnce())} - * use {@code configValue.get((nv,ov)->setValue(nv)) } - * This way, every change to the config file will get forwarded to the object. - *

+ * use {@code configValue.get((nv,ov)->setValue(nv)) } This way, every change to the config file will get forwarded + * to the object. + *

* * @param s subscriber * @return this topic @@ -61,9 +61,9 @@ public Topic subscribe(Subscriber s) { * Subscribe to a topic and invoke the subscriber right away on the same thread for a new subscriber. *

* This is the preferred way to get a value from a configuration. Instead of {@code setValue(configValue.getOnce())} - * use {@code configValue.get((nv,ov)->setValue(nv)) } - * This way, every change to the config file will get forwarded to the object. - *

+ * use {@code configValue.get((nv,ov)->setValue(nv)) } This way, every change to the config file will get forwarded + * to the object. + *

* * @param c subscribe change to this node or subNode * @return this topic @@ -90,8 +90,8 @@ public Topic addValidator(Validator validator) { } /** - * This should rarely be used. Instead, use subscribe(Subscriber). - * Not synchronized with setState(). The returned value is the value of the last completed setState(). + * This should rarely be used. Instead, use subscribe(Subscriber). Not synchronized with setState(). The returned + * value is the value of the last completed setState(). */ public Object getOnce() { return value; @@ -130,6 +130,7 @@ private Topic overrideValue(Object nv) { /** * Update the value in place without changing the timestamp. + * * @param nv new value * @return this */ @@ -178,7 +179,7 @@ Topic withNewerValue(long proposedModtime, final Object proposed) { * Set the value of this topic to a new value. * * @param proposedModtime the last modified time of the value. If this is in the past, we do not update the value. - * @param proposed new value. + * @param proposed new value. * @return this. */ public Topic withNewerValue(long proposedModtime, String proposed) { @@ -189,7 +190,7 @@ public Topic withNewerValue(long proposedModtime, String proposed) { * Set the value of this topic to a new value. * * @param proposedModtime the last modified time of the value. If this is in the past, we do not update the value. - * @param proposed new value. + * @param proposed new value. * @return this. */ public Topic withNewerValue(long proposedModtime, Number proposed) { @@ -199,9 +200,9 @@ public Topic withNewerValue(long proposedModtime, Number proposed) { /** * Set the value of this topic to a new value. * - * @param proposedModtime the last modified time of the value. If this is in the past, we do not update the - * value unless this is forced - * @param proposed new value. + * @param proposedModtime the last modified time of the value. If this is in the past, we do not update the value + * unless this is forced + * @param proposed new value. * @param allowTimestampToDecrease allow the timestamp to go back in time * @return this */ @@ -213,15 +214,15 @@ Topic withNewerValue(long proposedModtime, final Object proposed, boolean allowT * Set the value of this topic to a new value. * * @param proposedModtime the last modified time of the value. If this is in the past, we do not update the value - * unless this is forced - * @param proposed new value. + * unless this is forced + * @param proposed new value. * @param allowTimestampToDecrease allow the timestamp to go back in time * @param allowTimestampToIncreaseWhenValueHasntChanged allow the timestamp to go forward in time even if the - * proposed value is the same as the current value + * proposed value is the same as the current value * @return this. */ Topic withNewerValue(long proposedModtime, final Object proposed, boolean allowTimestampToDecrease, - boolean allowTimestampToIncreaseWhenValueHasntChanged) { + boolean allowTimestampToIncreaseWhenValueHasntChanged) { try (LockScope ls = LockScope.lock(lock)) { final Object currentValue = value; final long currentModTime = modtime; @@ -232,8 +233,8 @@ Topic withNewerValue(long proposedModtime, final Object proposed, boolean allowT // decrease the timestamp // AND the timestamp would not increase // THEN, return immediately and do nothing. - if ((Objects.equals(proposed, currentValue) || !allowTimestampToDecrease && (proposedModtime - < currentModTime)) && !timestampWouldIncrease) { + if ((Objects.equals(proposed, currentValue) + || !allowTimestampToDecrease && (proposedModtime < currentModTime)) && !timestampWouldIncrease) { return this; } final Object validated = validate(proposed, currentValue); @@ -250,8 +251,11 @@ Topic withNewerValue(long proposedModtime, final Object proposed, boolean allowT && !(validated instanceof Enum)) { // Log with cause if it is an invalid type. This will trip our test protection without // actually causing an exception and failing the write - logger.atError().cause(new UnsupportedInputTypeException(proposed.getClass())).kv("path", path()) - .kv("value", validated).log(); + logger.atError() + .cause(new UnsupportedInputTypeException(proposed.getClass())) + .kv("path", path()) + .kv("value", validated) + .log(); } value = validated; diff --git a/src/main/java/com/aws/greengrass/config/Topics.java b/src/main/java/com/aws/greengrass/config/Topics.java index d95f4145ff..21f228e6b9 100644 --- a/src/main/java/com/aws/greengrass/config/Topics.java +++ b/src/main/java/com/aws/greengrass/config/Topics.java @@ -47,7 +47,7 @@ public static Topics of(Context c, String n, Topics p) { * Create an errorNode with a given message. * * @param context context - * @param name name of the topics node + * @param name name of the topics node * @param message error message * @return node */ @@ -92,8 +92,7 @@ public Node getChild(String name) { } /** - * Create a leaf Topic under this Topics with the given name. - * Returns the leaf topic if it already existed. + * Create a leaf Topic under this Topics with the given name. Returns the leaf topic if it already existed. * * @param name name of the leaf node * @return the node @@ -103,35 +102,33 @@ public Topic createLeafChild(String name) { } /** - * Create a leaf Topic under this Topics with the given name. - * Returns the leaf topic if it already existed. + * Create a leaf Topic under this Topics with the given name. Returns the leaf topic if it already existed. * * @param name name of the leaf node * @param timestamp modtime of the leaf node * @return */ public Topic createLeafChild(String name, long timestamp) { - return createLeafChild(new CaseInsensitiveString(name), timestamp); + return createLeafChild(new CaseInsensitiveString(name), timestamp); } private Topic createLeafChild(CaseInsensitiveString name, long timestamp) { - Node n = children.computeIfAbsent(name, - (nm) -> { - Topic t = new Topic(context, nm.toString(), this, timestamp); - context.runOnPublishQueue(() -> childChanged(WhatHappened.childChanged, t)); - return t; - }); + Node n = children.computeIfAbsent(name, (nm) -> { + Topic t = new Topic(context, nm.toString(), this, timestamp); + context.runOnPublishQueue(() -> childChanged(WhatHappened.childChanged, t)); + return t; + }); if (n instanceof Topic) { return (Topic) n; } else { - throw new IllegalArgumentException(name + " in " - + getFullName() + " is already a container, cannot become a leaf"); + throw new IllegalArgumentException( + name + " in " + getFullName() + " is already a container, cannot become a leaf"); } } /** - * Create an interior Topics node with the provided name. - * Returns the new node or the existing node if it already existed. + * Create an interior Topics node with the provided name. Returns the new node or the existing node if it already + * existed. * * @param name name for the new node * @return the node @@ -141,8 +138,9 @@ public Topics createInteriorChild(String name) { } /** - * Create an interior Topics node with the provided name and modtime - * Returns the new node or the existing node if it already existed. + * Create an interior Topics node with the provided name and modtime Returns the new node or the existing node if it + * already existed. + * * @param name name for the new node * @param timestamp modtime of the new node * @return @@ -152,17 +150,16 @@ public Topics createInteriorChild(String name, long timestamp) { } private Topics createInteriorChild(CaseInsensitiveString name, long timestamp) { - Node n = children.computeIfAbsent(name, - (nm) -> { - Topics t = new Topics(context, nm.toString(), this, timestamp); - context.runOnPublishQueue(() -> childChanged(WhatHappened.interiorAdded, t)); - return t; - }); + Node n = children.computeIfAbsent(name, (nm) -> { + Topics t = new Topics(context, nm.toString(), this, timestamp); + context.runOnPublishQueue(() -> childChanged(WhatHappened.interiorAdded, t)); + return t; + }); if (n instanceof Topics) { return (Topics) n; } else { - throw new IllegalArgumentException(name + " in " - + getFullName() + " is already a leaf, cannot become a container"); + throw new IllegalArgumentException( + name + " in " + getFullName() + " is already a leaf, cannot become a container"); } } @@ -177,8 +174,7 @@ public Topic findLeafChild(String name) { } /** - * Find, and create if missing, a topic (a name/value pair) in the config - * file. Never returns null. + * Find, and create if missing, a topic (a name/value pair) in the config file. Never returns null. * * @param path String[] of node names to traverse to find or create the Topic */ @@ -191,11 +187,9 @@ public Topic lookup(String... path) { return n.createLeafChild(path[limit]); } - - /** - * Find, and create if missing, a topic (a name/value pair) in the config - * file. Never returns null. + * Find, and create if missing, a topic (a name/value pair) in the config file. Never returns null. + * * @param timestamp modtime of newly created nodes * @param path String[] of node names to traverse to find or create the Topic * @return @@ -210,8 +204,7 @@ public Topic lookup(long timestamp, String... path) { } /** - * Find, and create if missing, a list of topics (name/value pairs) in the - * config file. Never returns null. + * Find, and create if missing, a list of topics (name/value pairs) in the config file. Never returns null. * * @param path String[] of node names to traverse to find or create the Topics */ @@ -220,8 +213,7 @@ public Topics lookupTopics(String... path) { } /** - * Find, and create if missing, a list of topics (name/value pairs) in the - * config file. Never returns null. + * Find, and create if missing, a list of topics (name/value pairs) in the config file. Never returns null. * * @param timestamp modtime of newly created nodes * @param path String[] of node names to traverse to find or create the Topics @@ -235,10 +227,8 @@ public Topics lookupTopics(long timestamp, String... path) { return n; } - /** - * Find, but do not create if missing, a topic (a name/value pair) in the - * config file. Returns null if missing. + * Find, but do not create if missing, a topic (a name/value pair) in the config file. Returns null if missing. * * @param path String[] of node names to traverse to find the Topic */ @@ -252,12 +242,11 @@ public Topic find(String... path) { } /** - * Find, but do not create if missing, a topic (a name/value pair) in the - * config file. If the topic exists, it returns the value. If the topic does not - * exist, then it will return the default value provided. + * Find, but do not create if missing, a topic (a name/value pair) in the config file. If the topic exists, it + * returns the value. If the topic does not exist, then it will return the default value provided. * * @param defaultV default value if the Topic was not found - * @param path String[] of node names to traverse to find the Topic + * @param path String[] of node names to traverse to find the Topic */ public Object findOrDefault(Object defaultV, String... path) { Topic potentialTopic = find(path); @@ -302,7 +291,7 @@ public Node findNode(String... path) { /** * Add the given map to this Topics tree. * - * @param map map to merge in + * @param map map to merge in * @param mergeBehavior mergeBehavior */ @SuppressFBWarnings("NP_NULL_ON_SOME_PATH") @@ -329,8 +318,7 @@ public void updateFromMap(Map map, @NonNull UpdateBehaviorTree m }); } - private void updateChild(CaseInsensitiveString key, Object value, - @NonNull UpdateBehaviorTree mergeBehavior) { + private void updateChild(CaseInsensitiveString key, Object value, @NonNull UpdateBehaviorTree mergeBehavior) { UpdateBehaviorTree childMergeBehavior = mergeBehavior.getChildBehavior(key.toString()); Node existingChild = children.get(key); @@ -347,11 +335,11 @@ private void updateChild(CaseInsensitiveString key, Object value, } newNode.updateFromMap((Map) value, childMergeBehavior); } - // if new node is a leaf node + // if new node is a leaf node } else { if (existingChild == null || existingChild instanceof Topic) { - createLeafChild(key.toString()) - .withNewerValue(childMergeBehavior.getTimestampToUse(), value, false, true); + createLeafChild(key.toString()).withNewerValue(childMergeBehavior.getTimestampToUse(), value, false, + true); } else { remove(existingChild); Topic newNode = createLeafChild(key.toString()); @@ -442,7 +430,9 @@ public void deepForEach(BiConsumer f, U */ public void remove(Node n) { if (!children.remove(new CaseInsensitiveString(n.getName()), n)) { - logger.atError("config-node-child-remove-error").kv("thisNode", toString()).kv("childNode", n.getName()) + logger.atError("config-node-child-remove-error") + .kv("thisNode", toString()) + .kv("childNode", n.getName()) .log(); return; } @@ -454,13 +444,12 @@ public void remove(Node n) { /** * Clears all the children nodes and replaces with the provided new map. Waits for replace to finish + * * @param newValue Map of new values for this topics */ public void replaceAndWait(Map newValue) { - context.runOnPublishQueueAndWait(() -> - updateFromMap(newValue, - new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE, System.currentTimeMillis())) - ); + context.runOnPublishQueueAndWait(() -> updateFromMap(newValue, + new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE, System.currentTimeMillis()))); context.waitForPublishQueueToClear(); } @@ -542,7 +531,9 @@ public void forEachChildlessTopics(Consumer f) { if (children.isEmpty()) { f.accept(this); } else { - children.values().stream().filter(n -> n instanceof Topics) + children.values() + .stream() + .filter(n -> n instanceof Topics) .forEach(t -> ((Topics) t).forEachChildlessTopics(f)); } } diff --git a/src/main/java/com/aws/greengrass/config/UpdateBehaviorTree.java b/src/main/java/com/aws/greengrass/config/UpdateBehaviorTree.java index 53b922458e..87a42be261 100644 --- a/src/main/java/com/aws/greengrass/config/UpdateBehaviorTree.java +++ b/src/main/java/com/aws/greengrass/config/UpdateBehaviorTree.java @@ -13,65 +13,22 @@ import java.util.HashMap; import java.util.Map; -/** A hierarchy data structure indicating merge behavior of entire config tree. - * An example looks like below: - * [MERGE] - * key1: [MERGE] - * subkey1: [REPLACE] - * subkey2: [MERGE] - * *: [REPLACE] - * subkey1: [MERGE] - * subkey2: [REPLACE] +/** + * A hierarchy data structure indicating merge behavior of entire config tree. An example looks like below: [MERGE] + * key1: [MERGE] subkey1: [REPLACE] subkey2: [MERGE] *: [REPLACE] subkey1: [MERGE] subkey2: [REPLACE] *

- * Original config: - * -- - * key1: - * otherKey: otherVal - * subKey1: - * leafKey1:val1 - * subKey2: - * leafKey2:val2 - * foo: - * otherKey: otherVal - * subKey1: - * leafKey1:val1 - * subKey2: - * leafKey2:val2 - * bar: - * key1:val1 + * Original config: -- key1: otherKey: otherVal subKey1: leafKey1:val1 subKey2: leafKey2:val2 foo: otherKey: otherVal + * subKey1: leafKey1:val1 subKey2: leafKey2:val2 bar: key1:val1 *

*

- * Config to merge in: - * -- - * key1: - * subKey1: - * subKey2: - * updatedLeafKey2: updatedVal2 - * foo: - * subKey1: - * subKey2: - * updatedLeafKey2: updatedVal2 - * baz: - * key1:val1 + * Config to merge in: -- key1: subKey1: subKey2: updatedLeafKey2: updatedVal2 foo: subKey1: subKey2: updatedLeafKey2: + * updatedVal2 baz: key1:val1 *

*

- * Resulting config: - * -- - * key1: - * otherKey: otherVal (merged from original config) - * subKey1: (leafKey1 removed) - * subKey2: - * leafKey2:val2 (merged from original config) - * updatedLeafKey2: updatedVal2 - * foo: (otherKey removed) - * subKey1: - * leafKey1:val1 (merged from original config) - * subKey2: (leafKey2 removed) - * updatedLeafKey2: updatedVal2 - * bar: (merged from original config) - * key1:val1 - * baz: (merged from new config) - * key1:val1 + * Resulting config: -- key1: otherKey: otherVal (merged from original config) subKey1: (leafKey1 removed) subKey2: + * leafKey2:val2 (merged from original config) updatedLeafKey2: updatedVal2 foo: (otherKey removed) subKey1: + * leafKey1:val1 (merged from original config) subKey2: (leafKey2 removed) updatedLeafKey2: updatedVal2 bar: (merged + * from original config) key1:val1 baz: (merged from new config) key1:val1 *

*/ @AllArgsConstructor @@ -92,7 +49,7 @@ public enum UpdateBehavior { * Create a mutable behavior tree with some behavior and a timestamp. * * @param behavior behavior to use when merging this and child nodes - * @param timestamp timestamp to use for this and child nodes + * @param timestamp timestamp to use for this and child nodes */ public UpdateBehaviorTree(UpdateBehavior behavior, long timestamp) { this(behavior, timestamp, new HashMap<>()); @@ -101,12 +58,12 @@ public UpdateBehaviorTree(UpdateBehavior behavior, long timestamp) { /** * Create a behavior tree with some behavior, timestamp, and map of child behaviors. * - * @param behavior behavior to use when merging this and child nodes - * @param timestamp timestamp to use for this and child nodes - * @param childOverride initial map to use to override children + * @param behavior behavior to use when merging this and child nodes + * @param timestamp timestamp to use for this and child nodes + * @param childOverride initial map to use to override children */ protected UpdateBehaviorTree(UpdateBehavior behavior, long timestamp, - Map childOverride) { + Map childOverride) { this.behavior = behavior; this.timestampToUse = timestamp; this.childOverride = childOverride; diff --git a/src/main/java/com/aws/greengrass/config/Validator.java b/src/main/java/com/aws/greengrass/config/Validator.java index 00f1d56094..e89dc57806 100644 --- a/src/main/java/com/aws/greengrass/config/Validator.java +++ b/src/main/java/com/aws/greengrass/config/Validator.java @@ -6,9 +6,9 @@ package com.aws.greengrass.config; /** - * Used to validate a value being assigned to a topic. A no-op validator returns newValue. - * Validators are called when a topic is locked, so they should be quick, not throw exceptions, - * and have no chance of being recursive. To reject a change, return oldValue + * Used to validate a value being assigned to a topic. A no-op validator returns newValue. Validators are + * called when a topic is locked, so they should be quick, not throw exceptions, and have no chance of being recursive. + * To reject a change, return oldValue */ public interface Validator extends Watcher { Object validate(Object newValue, Object oldValue); diff --git a/src/main/java/com/aws/greengrass/dependency/ComponentStatusCode.java b/src/main/java/com/aws/greengrass/dependency/ComponentStatusCode.java index 9881b0b236..654a38e7df 100644 --- a/src/main/java/com/aws/greengrass/dependency/ComponentStatusCode.java +++ b/src/main/java/com/aws/greengrass/dependency/ComponentStatusCode.java @@ -15,43 +15,37 @@ */ public enum ComponentStatusCode { - NONE(""), - INSTALL_ERROR("An error occurred during installation.", - "The install script exited with code %s."), - INSTALL_CONFIG_NOT_VALID( - "Installation couldn't be completed. The structure of the installation section of the recipe is " - + "not valid. Check the install section and try your request again."), - INSTALL_IO_ERROR("There was an I/O error during installation. Check the component log for more information."), - INSTALL_MISSING_DEFAULT_RUNWITH("Couldn't determine the user or group to use when installing the component. Check" - + " the runWith section of your recipe and try your request again."), - INSTALL_TIMEOUT( - "Install script didn't finish within the timeout period. Increase the timeout to give it more " - + "time to run or check your code."), - STARTUP_ERROR("An error occurred during startup.", - "The startup script exited with code %s."), - STARTUP_CONFIG_NOT_VALID( - "The component couldn't be started. The structure of the startup section of the recipe is not valid. " - + "Check the startup section and try your request again."), - STARTUP_IO_ERROR("There was an I/O error starting the component. Check the component log for more information."), - STARTUP_MISSING_DEFAULT_RUNWITH("Couldn't determine the user or group to use when starting the component. " - + "Check the runWith section of your recipe and try your request again."), - STARTUP_TIMEOUT( - "Startup script didn't finish within the timeout period. Increase the timeout to give it more " - + "time to run or check your code."), - RUN_ERROR("An error occurred while running the component.", - "The run script exited with code %s."), - RUN_MISSING_DEFAULT_RUNWITH("Couldn't determine the user or group to use when running the component. Check" - + " the runWith section of your recipe and try your request again."), - RUN_CONFIG_NOT_VALID( - "The component couldn't run. The structure of the run section of the recipe is not valid. Check" - + " the run section and try your request again."), - RUN_IO_ERROR("There was an I/O error running the component. Check the component log for more information."), - RUN_TIMEOUT("Run script didn't finish within the timeout period. Increase the timeout to give it more time to run " - + "or check your code."), - SHUTDOWN_ERROR("An error occurred while shutting down the component.", - "The shutdown script exited with code %s."), - SHUTDOWN_TIMEOUT("Shutdown script didn't finish within the timeout period. Increase the timeout to give it more " - + "time to run or check your code."); + NONE(""), INSTALL_ERROR("An error occurred during installation.", + "The install script exited with code %s."), INSTALL_CONFIG_NOT_VALID( + "Installation couldn't be completed. The structure of the installation section of the recipe is " + + "not valid. Check the install section and try your request again."), INSTALL_IO_ERROR( + "There was an I/O error during installation. Check the component log for more information."), INSTALL_MISSING_DEFAULT_RUNWITH( + "Couldn't determine the user or group to use when installing the component. Check" + + " the runWith section of your recipe and try your request again."), INSTALL_TIMEOUT( + "Install script didn't finish within the timeout period. Increase the timeout to give it more " + + "time to run or check your code."), STARTUP_ERROR( + "An error occurred during startup.", + "The startup script exited with code %s."), STARTUP_CONFIG_NOT_VALID( + "The component couldn't be started. The structure of the startup section of the recipe is not valid. " + + "Check the startup section and try your request again."), STARTUP_IO_ERROR( + "There was an I/O error starting the component. Check the component log for more information."), STARTUP_MISSING_DEFAULT_RUNWITH( + "Couldn't determine the user or group to use when starting the component. " + + "Check the runWith section of your recipe and try your request again."), STARTUP_TIMEOUT( + "Startup script didn't finish within the timeout period. Increase the timeout to give it more " + + "time to run or check your code."), RUN_ERROR( + "An error occurred while running the component.", + "The run script exited with code %s."), RUN_MISSING_DEFAULT_RUNWITH( + "Couldn't determine the user or group to use when running the component. Check" + + " the runWith section of your recipe and try your request again."), RUN_CONFIG_NOT_VALID( + "The component couldn't run. The structure of the run section of the recipe is not valid. Check" + + " the run section and try your request again."), RUN_IO_ERROR( + "There was an I/O error running the component. Check the component log for more information."), RUN_TIMEOUT( + "Run script didn't finish within the timeout period. Increase the timeout to give it more time to run " + + "or check your code."), SHUTDOWN_ERROR( + "An error occurred while shutting down the component.", + "The shutdown script exited with code %s."), SHUTDOWN_TIMEOUT( + "Shutdown script didn't finish within the timeout period. Increase the timeout to give it more " + + "time to run or check your code."); @Getter private String description; @@ -90,14 +84,14 @@ public String getDescriptionWithExitCode(int exit) { */ public static ComponentStatusCode getCodeMissingRunWithForState(String lifecycleTopicName) { switch (lifecycleTopicName) { - case Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC: - return INSTALL_MISSING_DEFAULT_RUNWITH; - case Lifecycle.LIFECYCLE_STARTUP_NAMESPACE_TOPIC: - return STARTUP_MISSING_DEFAULT_RUNWITH; - case GenericExternalService.LIFECYCLE_RUN_NAMESPACE_TOPIC: - return RUN_MISSING_DEFAULT_RUNWITH; - default: - return NONE; + case Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC: + return INSTALL_MISSING_DEFAULT_RUNWITH; + case Lifecycle.LIFECYCLE_STARTUP_NAMESPACE_TOPIC: + return STARTUP_MISSING_DEFAULT_RUNWITH; + case GenericExternalService.LIFECYCLE_RUN_NAMESPACE_TOPIC: + return RUN_MISSING_DEFAULT_RUNWITH; + default: + return NONE; } } @@ -109,14 +103,14 @@ public static ComponentStatusCode getCodeMissingRunWithForState(String lifecycle */ public static ComponentStatusCode getCodeInvalidConfigForState(String lifecycleTopicName) { switch (lifecycleTopicName) { - case Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC: - return INSTALL_CONFIG_NOT_VALID; - case Lifecycle.LIFECYCLE_STARTUP_NAMESPACE_TOPIC: - return STARTUP_CONFIG_NOT_VALID; - case GenericExternalService.LIFECYCLE_RUN_NAMESPACE_TOPIC: - return RUN_CONFIG_NOT_VALID; - default: - return NONE; + case Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC: + return INSTALL_CONFIG_NOT_VALID; + case Lifecycle.LIFECYCLE_STARTUP_NAMESPACE_TOPIC: + return STARTUP_CONFIG_NOT_VALID; + case GenericExternalService.LIFECYCLE_RUN_NAMESPACE_TOPIC: + return RUN_CONFIG_NOT_VALID; + default: + return NONE; } } @@ -128,14 +122,14 @@ public static ComponentStatusCode getCodeInvalidConfigForState(String lifecycleT */ public static ComponentStatusCode getCodeIOErrorForState(String lifecycleTopicName) { switch (lifecycleTopicName) { - case Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC: - return INSTALL_IO_ERROR; - case Lifecycle.LIFECYCLE_STARTUP_NAMESPACE_TOPIC: - return STARTUP_IO_ERROR; - case GenericExternalService.LIFECYCLE_RUN_NAMESPACE_TOPIC: - return RUN_IO_ERROR; - default: - return NONE; + case Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC: + return INSTALL_IO_ERROR; + case Lifecycle.LIFECYCLE_STARTUP_NAMESPACE_TOPIC: + return STARTUP_IO_ERROR; + case GenericExternalService.LIFECYCLE_RUN_NAMESPACE_TOPIC: + return RUN_IO_ERROR; + default: + return NONE; } } @@ -156,17 +150,17 @@ public static ComponentStatusCode getDefaultStatusCodeForTransition(State previo private static ComponentStatusCode getDefaultErrorCodeFrom(State previousState) { switch (previousState) { - case NEW: - case INSTALLED: - return INSTALL_ERROR; - case STARTING: - return STARTUP_ERROR; - case RUNNING: - return RUN_ERROR; - case STOPPING: - return SHUTDOWN_ERROR; - default: - return NONE; + case NEW: + case INSTALLED: + return INSTALL_ERROR; + case STARTING: + return STARTUP_ERROR; + case RUNNING: + return RUN_ERROR; + case STOPPING: + return SHUTDOWN_ERROR; + default: + return NONE; } } } diff --git a/src/main/java/com/aws/greengrass/dependency/Context.java b/src/main/java/com/aws/greengrass/dependency/Context.java index 8d6dab3c0f..8159da0fe0 100644 --- a/src/main/java/com/aws/greengrass/dependency/Context.java +++ b/src/main/java/com/aws/greengrass/dependency/Context.java @@ -60,7 +60,7 @@ public class Context implements Closeable { { setName("Serialized listener processor"); setPriority(Thread.MAX_PRIORITY - 1); - // setDaemon(true); + // setDaemon(true); } @SuppressWarnings("PMD.AvoidCatchingThrowable") @@ -78,7 +78,8 @@ public void run() { } } }; - private static final Crashable doNothing = () -> {}; + private static final Crashable doNothing = () -> { + }; // magical private boolean shuttingDown = false; // global state change notification @@ -127,7 +128,7 @@ public T newInstance(Class cl) { /** * Get the class with the provided tag, if it exists. * - * @param cl class to lookup + * @param cl class to lookup * @param tag tag of the instance of the class to get * @param the class type to lookup * @return null if it could not be found, returns the class otherwise @@ -148,9 +149,9 @@ public Value getvIfExists(Object tag) { /** * Put a class into the Context. * - * @param clazz type of class to be stored + * @param clazz type of class to be stored * @param object instance of class to store - * @param the class type to put + * @param the class type to put * @return this */ public Context put(Class clazz, T object) { @@ -170,7 +171,7 @@ public Context put(Class clazz, T object) { * * @param clazz type of class to be stored * @param value value instance of class to store - * @param the class type to put + * @param the class type to put * @return this */ public Context put(Class clazz, Value value) { @@ -181,7 +182,7 @@ public Context put(Class clazz, Value value) { /** * Put object into the context with a provided tag. * - * @param tag tag + * @param tag tag * @param object value * @return this */ @@ -214,8 +215,10 @@ public void shutdown() { logger.atDebug("context-shutdown").kv(classKeyword, Coerce.toString(object)).log(); } if (object instanceof ExecutorService) { - logger.atDebug("context-shutdown").kv(classKeyword, Coerce.toString(object)) - .kv("executorInterruptedRunnables", ((ExecutorService) object).shutdownNow()).log(); + logger.atDebug("context-shutdown") + .kv(classKeyword, Coerce.toString(object)) + .kv("executorInterruptedRunnables", ((ExecutorService) object).shutdownNow()) + .log(); } } catch (IOException t) { logger.atError("context-shutdown-error", t).kv(classKeyword, Coerce.toString(object)).log(); @@ -225,7 +228,8 @@ public void shutdown() { // Request stop without actually interrupting the publish thread requestPublishThreadStop.set(true); // Add something into the queue to be sure that takeFirst returns - runOnPublishQueue(() -> {}); + runOnPublishQueue(() -> { + }); } @Override @@ -255,12 +259,11 @@ public void removeGlobalStateChangeListener(GlobalStateChangeListener l) { * Serially send an event to the global state change listeners. * * @param changedService the service which had a state change - * @param oldState the old state of the service - * @param newState the new state of the service + * @param oldState the old state of the service + * @param newState the new state of the service */ @SuppressWarnings("PMD.AvoidCatchingThrowable") - public void globalNotifyStateChanged(GreengrassService changedService, final State oldState, - final State newState) { + public void globalNotifyStateChanged(GreengrassService changedService, final State oldState, final State newState) { listeners.forEach(s -> { try { s.globalServiceStateChanged(changedService, oldState, newState); @@ -324,7 +327,9 @@ private boolean onPublishThread() { * @param object Object to inject fields into */ @SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") - @SuppressWarnings({"PMD.AvoidCatchingThrowable"}) + @SuppressWarnings({ + "PMD.AvoidCatchingThrowable" + }) public void injectFields(Object object) { if (object == null) { return; @@ -406,7 +411,9 @@ public void injectFields(Object object) { } @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD}) + @Target({ + ElementType.FIELD + }) public @interface ServiceDependencyType { /** * What state to start the service. @@ -436,6 +443,7 @@ public final T get() { /** * Constructs an object without injection. + * * @return object */ public final T getObjectWithoutInjection() { @@ -445,7 +453,6 @@ public final T getObjectWithoutInjection() { return constructObject(); } - /** * Put a new object instance and inject fields with pre and post actions, if the new object is not equal to * current one. @@ -567,16 +574,16 @@ private Constructor pickConstructor(Class clazz) throws NoSuchMethodExcept /** * Computes and returns T if object instance is null. The mapping function used for the instance creation should - * not inject the created object into the context as it will anyway be injected as part of this method. - * TODO revisit to see if there is a better way because the mapping function usage is weird. + * not inject the created object into the context as it will anyway be injected as part of this method. TODO + * revisit to see if there is a better way because the mapping function usage is weird. * * @param mappingFunction maps from Value to T - * @param CheckedException + * @param CheckedException * @return the current (existing or computed) object instance * @throws E when mapping function throws checked exception */ - public final T computeObjectIfEmpty( - CrashableFunction mappingFunction) throws E { + public final T computeObjectIfEmpty(CrashableFunction mappingFunction) + throws E { try (LockScope ls = LockScope.lock(lock)) { if (object != null) { return object; diff --git a/src/main/java/com/aws/greengrass/dependency/Crashable.java b/src/main/java/com/aws/greengrass/dependency/Crashable.java index 3b2ec2fffa..b9696b0a0a 100644 --- a/src/main/java/com/aws/greengrass/dependency/Crashable.java +++ b/src/main/java/com/aws/greengrass/dependency/Crashable.java @@ -6,8 +6,8 @@ package com.aws.greengrass.dependency; /** - * Like Runnable, but exceptions pass through. It is normally used in situations where - * the caller is prepared to take corrective action if badness ensues. + * Like Runnable, but exceptions pass through. It is normally used in situations where the caller is prepared to take + * corrective action if badness ensues. */ public interface Crashable { void run() throws Throwable; diff --git a/src/main/java/com/aws/greengrass/dependency/EZPlugins.java b/src/main/java/com/aws/greengrass/dependency/EZPlugins.java index c9710af0e0..2d2f45556e 100644 --- a/src/main/java/com/aws/greengrass/dependency/EZPlugins.java +++ b/src/main/java/com/aws/greengrass/dependency/EZPlugins.java @@ -42,7 +42,6 @@ import java.util.stream.Stream; import javax.inject.Inject; - @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Spotbugs false positive") public class EZPlugins implements Closeable { private static final Logger logger = LogManager.getLogger(EZPlugins.class); @@ -113,8 +112,9 @@ private void loadPlugins(boolean trusted, ClassLoader cls) { } } } catch (IOException e) { - logger.atWarn().log("Problem looking for Greengrass plugin with the fast path." - + " Falling back to classpath scanner", e); + logger.atWarn() + .log("Problem looking for Greengrass plugin with the fast path." + + " Falling back to classpath scanner", e); } FastClasspathScanner sc = new FastClasspathScanner("com.aws.greengrass"); @@ -128,7 +128,9 @@ private void loadPlugins(boolean trusted, ClassLoader cls) { @SuppressWarnings("PMD.CloseResource") // Class loader must stay open, otherwise we won't be able to load all classes from the jar private void loadPlugins(boolean trusted, Path p) throws IOException { - URLClassLoader cl = new URLClassLoader(new URL[]{p.toUri().toURL()}); + URLClassLoader cl = new URLClassLoader(new URL[] { + p.toUri().toURL() + }); classLoaders.add(cl); loadPlugins(trusted, cl); } @@ -136,7 +138,7 @@ private void loadPlugins(boolean trusted, Path p) throws IOException { /** * Load a single plugin with the classpath scanner. * - * @param p path to jar file + * @param p path to jar file * @param annotationClass annotation to search for * @param annotation class type * @param matcher matcher to use @@ -145,9 +147,11 @@ private void loadPlugins(boolean trusted, Path p) throws IOException { // Class loader must stay open, otherwise we won't be able to load all classes from the jar @SuppressWarnings("PMD.CloseResource") public ClassLoader loadPluginAnnotatedWith(Path p, Class annotationClass, - Consumer> matcher) throws IOException { + Consumer> matcher) throws IOException { try (LockScope ls = LockScope.lock(lock)) { - URL[] urls = {p.toUri().toURL()}; + URL[] urls = { + p.toUri().toURL() + }; return AccessController.doPrivileged((PrivilegedAction) () -> { URLClassLoader cl = new URLClassLoader(urls, root); classLoaders.add(cl); @@ -291,8 +295,8 @@ public String[] list(boolean trusted) { /** * Find plugins implementing the given class. * - * @param c Class that the plugin should implement - * @param m Callback to do something if a matching plugin is found + * @param c Class that the plugin should implement + * @param m Callback to do something if a matching plugin is found * @param the class type to lookup * @return this * @throws IllegalStateException if plugins are not yet loaded @@ -313,8 +317,8 @@ public EZPlugins implementing(Class c, ImplementingClassMatchProcessor /** * Find plugin annotated with a given class. * - * @param c Annotation to search for - * @param m Callback if a match is found + * @param c Annotation to search for + * @param m Callback if a match is found * @param the class type to lookup * @return this * @throws IllegalStateException if plugins are not yet loaded diff --git a/src/main/java/com/aws/greengrass/dependency/ImplementsService.java b/src/main/java/com/aws/greengrass/dependency/ImplementsService.java index bcf396cc99..1555fd53bf 100644 --- a/src/main/java/com/aws/greengrass/dependency/ImplementsService.java +++ b/src/main/java/com/aws/greengrass/dependency/ImplementsService.java @@ -16,7 +16,8 @@ /** * The name of the service (must be unique). */ - @Nonnull String name(); // the name of the service + @Nonnull + String name(); // the name of the service /** * True if the service should start immediately when Kernel starts. @@ -26,5 +27,6 @@ /** * Version of the service. By default it is 0.0.0. Must be in the form of a.b.c. */ - @Nonnull String version() default "0.0.0"; + @Nonnull + String version() default "0.0.0"; } diff --git a/src/main/java/com/aws/greengrass/dependency/InjectionActions.java b/src/main/java/com/aws/greengrass/dependency/InjectionActions.java index d205df56ac..3658a7fba9 100644 --- a/src/main/java/com/aws/greengrass/dependency/InjectionActions.java +++ b/src/main/java/com/aws/greengrass/dependency/InjectionActions.java @@ -5,20 +5,17 @@ package com.aws.greengrass.dependency; - public interface InjectionActions { /** - * Called after the constructor, but before dependency injection. - * It is critical that you remember to call super.preInject() when you override this - * method. + * Called after the constructor, but before dependency injection. It is critical that you remember to call + * super.preInject() when you override this method. */ default void preInject() { } /** - * Called after dependency injection, but before dependencies are all - * RUNNING. It is critical that you remember to call super.postInject() when you - * override this method. + * Called after dependency injection, but before dependencies are all RUNNING. It is critical that you remember to + * call super.postInject() when you override this method. */ default void postInject() { } diff --git a/src/main/java/com/aws/greengrass/dependency/State.java b/src/main/java/com/aws/greengrass/dependency/State.java index 3e29ac652b..44b27edeb6 100644 --- a/src/main/java/com/aws/greengrass/dependency/State.java +++ b/src/main/java/com/aws/greengrass/dependency/State.java @@ -30,8 +30,8 @@ public enum State { STARTING(true, false, false, "Starting"), /** - * Up and running, operating normally. This is the only state that should - * ever take a significant amount of time to run. + * Up and running, operating normally. This is the only state that should ever take a significant amount of time to + * run. */ RUNNING(true, true, true, "Running"), @@ -41,8 +41,7 @@ public enum State { STOPPING(true, false, true, "Stopping"), /** - * Not running. It may be possible for the enclosing framework to restart - * it. + * Not running. It may be possible for the enclosing framework to restart it. */ ERRORED(false, false, false, "Errored"), @@ -51,8 +50,8 @@ public enum State { */ BROKEN(false, false, false, "Broken"), /** - * The service has done it's job and has no more to do. May be restarted - * (for example, a monitoring task that will be restarted by a timer) + * The service has done it's job and has no more to do. May be restarted (for example, a monitoring task that will + * be restarted by a timer) */ FINISHED(true, false, true, "Finished"); @@ -61,7 +60,6 @@ public enum State { private final boolean functioningProperly; private final String name; - State(boolean happy, boolean running, boolean functioningProperly, String name) { this.happy = happy; this.running = running; @@ -69,7 +67,6 @@ public enum State { this.name = name; } - /** * Nothing is going wrong, but it may not be fully "up". */ diff --git a/src/main/java/com/aws/greengrass/deployment/DefaultDeploymentTask.java b/src/main/java/com/aws/greengrass/deployment/DefaultDeploymentTask.java index 7d99acbb17..2ebb37728a 100644 --- a/src/main/java/com/aws/greengrass/deployment/DefaultDeploymentTask.java +++ b/src/main/java/com/aws/greengrass/deployment/DefaultDeploymentTask.java @@ -73,26 +73,24 @@ public class DefaultDeploymentTask implements DeploymentTask { /** * Constructor for DefaultDeploymentTask. * - * @param dependencyResolver DependencyResolver instance - * @param componentManager PackageManager instance - * @param kernelConfigResolver KernelConfigResolver instance - * @param deploymentConfigMerger DeploymentConfigMerger instance - * @param logger Logger instance - * @param deployment Deployment instance - * @param deploymentServiceConfig Deployment service configuration Topics - * @param executorService Executor service + * @param dependencyResolver DependencyResolver instance + * @param componentManager PackageManager instance + * @param kernelConfigResolver KernelConfigResolver instance + * @param deploymentConfigMerger DeploymentConfigMerger instance + * @param logger Logger instance + * @param deployment Deployment instance + * @param deploymentServiceConfig Deployment service configuration Topics + * @param executorService Executor service * @param deploymentDocumentDownloader download large deployment document. - * @param thingGroupHelper Thing Group Helper / Retriever - * @param deviceConfiguration Device Configuration Information + * @param thingGroupHelper Thing Group Helper / Retriever + * @param deviceConfiguration Device Configuration Information */ @SuppressWarnings("PMD.ExcessiveParameterList") public DefaultDeploymentTask(DependencyResolver dependencyResolver, ComponentManager componentManager, - KernelConfigResolver kernelConfigResolver, - DeploymentConfigMerger deploymentConfigMerger, Logger logger, Deployment deployment, - Topics deploymentServiceConfig, ExecutorService executorService, - DeploymentDocumentDownloader deploymentDocumentDownloader, - ThingGroupHelper thingGroupHelper, - DeviceConfiguration deviceConfiguration) { + KernelConfigResolver kernelConfigResolver, DeploymentConfigMerger deploymentConfigMerger, Logger logger, + Deployment deployment, Topics deploymentServiceConfig, ExecutorService executorService, + DeploymentDocumentDownloader deploymentDocumentDownloader, ThingGroupHelper thingGroupHelper, + DeviceConfiguration deviceConfiguration) { this.dependencyResolver = dependencyResolver; this.componentManager = componentManager; this.kernelConfigResolver = kernelConfigResolver; @@ -107,14 +105,17 @@ public DefaultDeploymentTask(DependencyResolver dependencyResolver, ComponentMan } @Override - @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.PrematureDeclaration"}) + @SuppressWarnings({ + "PMD.PreserveStackTrace", "PMD.PrematureDeclaration" + }) public DeploymentResult call() throws InterruptedException { Future> resolveDependenciesFuture = null; Future preparePackagesFuture = null; Future deploymentMergeFuture = null; DeploymentDocument deploymentDocument = deployment.getDeploymentDocumentObj(); try { - logger.atInfo().setEventType(DEPLOYMENT_TASK_EVENT_TYPE) + logger.atInfo() + .setEventType(DEPLOYMENT_TASK_EVENT_TYPE) .kv("Deployment service config", deploymentServiceConfig.toPOJO().toString()) .log("Starting deployment task"); @@ -128,20 +129,21 @@ public DeploymentResult call() throws InterruptedException { packages.forEach(p -> rootPackages.add(p.getName())); }); - resolveDependenciesFuture = executorService.submit(() -> - dependencyResolver.resolveDependencies(deploymentDocument, nonTargetGroupsToRootPackagesMap)); + resolveDependenciesFuture = executorService.submit( + () -> dependencyResolver.resolveDependencies(deploymentDocument, nonTargetGroupsToRootPackagesMap)); List desiredPackages = resolveDependenciesFuture.get(); // download configuration if large List requiredCapabilities = deploymentDocument.getRequiredCapabilities(); - if (requiredCapabilities != null && requiredCapabilities - .contains(DeploymentCapability.LARGE_CONFIGURATION.toString())) { + if (requiredCapabilities != null + && requiredCapabilities.contains(DeploymentCapability.LARGE_CONFIGURATION.toString())) { DeploymentDocument downloadedDeploymentDocument = deploymentDocumentDownloader.download(deploymentDocument.getDeploymentId()); - deployment.getDeploymentDocumentObj().setDeploymentPackageConfigurationList( - downloadedDeploymentDocument.getDeploymentPackageConfigurationList()); + deployment.getDeploymentDocumentObj() + .setDeploymentPackageConfigurationList( + downloadedDeploymentDocument.getDeploymentPackageConfigurationList()); } @@ -159,62 +161,58 @@ public DeploymentResult call() throws InterruptedException { // If the incoming deployment contains a requested deploymentConfigurationTimeSource, // use the incoming setting for processing the deployment itself. - // - First, get the name of the nucleus component, by searching through the component configs that were - // on the device before the deployment started, and finding one of type nucleus. + // - First, get the name of the nucleus component, by searching through the component configs that were + // on the device before the deployment started, and finding one of type nucleus. Optional incomingNucleusComponentConfiguration = - deploymentDocument.getDeploymentPackageConfigurationList() == null ? Optional.empty() : - deploymentDocument.getDeploymentPackageConfigurationList().stream() - .filter(c -> c.getPackageName().equals(deviceConfiguration.getNucleusComponentName())) - .findAny(); + deploymentDocument.getDeploymentPackageConfigurationList() == null + ? Optional.empty() + : deploymentDocument.getDeploymentPackageConfigurationList() + .stream() + .filter(c -> c.getPackageName() + .equals(deviceConfiguration.getNucleusComponentName())) + .findAny(); if (incomingNucleusComponentConfiguration.isPresent() - && incomingNucleusComponentConfiguration - .get() - .getConfigurationUpdateOperation() != null - && incomingNucleusComponentConfiguration - .get() - .getConfigurationUpdateOperation() - .getValueToMerge() != null - && incomingNucleusComponentConfiguration - .get() - .getConfigurationUpdateOperation() - .getValueToMerge() - .containsKey(DEVICE_PARAM_DEPLOYMENT_CONFIGURATION_TIME_SOURCE)) { - logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE).log( - "Incoming nucleus component configuration contains deployment configuration time source"); - String incomingDeploymentConfigurationTimeSource = Coerce.toString( - incomingNucleusComponentConfiguration - .get() + && incomingNucleusComponentConfiguration.get().getConfigurationUpdateOperation() != null + && incomingNucleusComponentConfiguration.get() + .getConfigurationUpdateOperation() + .getValueToMerge() != null + && incomingNucleusComponentConfiguration.get() + .getConfigurationUpdateOperation() + .getValueToMerge() + .containsKey(DEVICE_PARAM_DEPLOYMENT_CONFIGURATION_TIME_SOURCE)) { + logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE) + .log("Incoming nucleus component configuration contains deployment configuration time source"); + String incomingDeploymentConfigurationTimeSource = + Coerce.toString(incomingNucleusComponentConfiguration.get() .getConfigurationUpdateOperation() .getValueToMerge() - .get(DEVICE_PARAM_DEPLOYMENT_CONFIGURATION_TIME_SOURCE) - ); + .get(DEVICE_PARAM_DEPLOYMENT_CONFIGURATION_TIME_SOURCE)); if (DEPLOYMENT_CONFIGURATION_TIME_SOURCE_DEPLOYMENT_PROCESSING_TIME .equals(incomingDeploymentConfigurationTimeSource)) { - logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE).log( - "Incoming nucleus component configuration contains deployment configuration time " + logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE) + .log("Incoming nucleus component configuration contains deployment configuration time " + "source set to deployment processing time"); timestamp = System.currentTimeMillis(); } } else { // The incoming deployment does not specify deploymentConfigurationTimeSource - logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE).log( - "Incoming nucleus component configuration does not contain deployment configuration time " + logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE) + .log("Incoming nucleus component configuration does not contain deployment configuration time " + "source"); // Use it from the existing device configuration, if present - if (DEPLOYMENT_CONFIGURATION_TIME_SOURCE_DEPLOYMENT_PROCESSING_TIME.equals( - Coerce.toString(deviceConfiguration.getDeploymentConfigurationTimeSource()))) { - logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE).log( - "Existing nucleus component configuration specifies deployment configuration time " + if (DEPLOYMENT_CONFIGURATION_TIME_SOURCE_DEPLOYMENT_PROCESSING_TIME + .equals(Coerce.toString(deviceConfiguration.getDeploymentConfigurationTimeSource()))) { + logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE) + .log("Existing nucleus component configuration specifies deployment configuration time " + "source as deployment processing time"); timestamp = System.currentTimeMillis(); } } - logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE).log( - "Timestamp to be used for deployment configuration: " + timestamp); + logger.atDebug(DEPLOYMENT_TASK_EVENT_TYPE) + .log("Timestamp to be used for deployment configuration: " + timestamp); - Map newConfig = - kernelConfigResolver.resolve(desiredPackages, deploymentDocument, - new ArrayList<>(rootPackages), timestamp); + Map newConfig = kernelConfigResolver.resolve(desiredPackages, deploymentDocument, + new ArrayList<>(rootPackages), timestamp); if (Thread.currentThread().isInterrupted()) { throw new InterruptedException("Deployment task is interrupted"); } @@ -224,7 +222,8 @@ public DeploymentResult call() throws InterruptedException { // (if it's not in a safe window). DeploymentResult result = deploymentMergeFuture.get(); - logger.atInfo(DEPLOYMENT_TASK_EVENT_TYPE).setEventType(DEPLOYMENT_TASK_EVENT_TYPE) + logger.atInfo(DEPLOYMENT_TASK_EVENT_TYPE) + .setEventType(DEPLOYMENT_TASK_EVENT_TYPE) .log("Finished deployment task"); componentManager.cleanupStaleVersions(); @@ -248,10 +247,11 @@ public DeploymentResult call() throws InterruptedException { } } - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) private Map> getNonTargetGroupToRootPackagesMap( - DeploymentDocument deploymentDocument) - throws DeploymentTaskFailureException, InterruptedException { + DeploymentDocument deploymentDocument) throws DeploymentTaskFailureException, InterruptedException { // Don't block local deployments due to device being offline by using finite retries for getting the // hierarchy and fall back to hierarchy stored previously in worst case. For cloud deployment, use infinite @@ -268,9 +268,11 @@ private Map> getNonTargetGroupToRoot // Getting group hierarchy requires permission to call the ListThingGroupsForCoreDevice API which // may not be configured on existing IoT Thing policy in use for current device, log a warning in // that case and move on. - logger.atWarn().setCause(e).log("Failed to get thing group hierarchy. Deployment will proceed. " - + "To automatically clean up unused components, please add " - + "greengrass:ListThingGroupsForCoreDevice permission to your IoT Thing policy."); + logger.atWarn() + .setCause(e) + .log("Failed to get thing group hierarchy. Deployment will proceed. " + + "To automatically clean up unused components, please add " + + "greengrass:ListThingGroupsForCoreDevice permission to your IoT Thing policy."); groupsForDeviceOpt = getPersistedMembershipInfo(); } else { throw new DeploymentTaskFailureException("Error fetching thing group information", e); @@ -298,12 +300,12 @@ private Map> getNonTargetGroupToRoot // skip root packages if device does not belong to that group anymore if (!groupTopics.getName().equals(deploymentDocument.getGroupName()) && (groupTopics.getName().startsWith(DEVICE_DEPLOYMENT_GROUP_NAME_PREFIX) - || groupTopics.getName().equals(LOCAL_DEPLOYMENT_GROUP_NAME) - || groupsForDevice.contains(groupTopics.getName()))) { + || groupTopics.getName().equals(LOCAL_DEPLOYMENT_GROUP_NAME) + || groupsForDevice.contains(groupTopics.getName()))) { groupTopics.forEach(pkgNode -> { Topics pkgTopics = (Topics) pkgNode; - Requirement versionReq = Requirement.buildNPM(Coerce.toString(pkgTopics - .lookup(GROUP_TO_ROOT_COMPONENTS_VERSION_KEY))); + Requirement versionReq = Requirement + .buildNPM(Coerce.toString(pkgTopics.lookup(GROUP_TO_ROOT_COMPONENTS_VERSION_KEY))); nonTargetGroupsToRootPackagesMap.putIfAbsent(groupTopics.getName(), new HashSet<>()); nonTargetGroupsToRootPackagesMap.get(groupTopics.getName()) .add(new ComponentRequirementIdentifier(pkgTopics.getName(), versionReq)); @@ -312,8 +314,7 @@ private Map> getNonTargetGroupToRoot }); deploymentServiceConfig.lookupTopics(DeploymentService.GROUP_MEMBERSHIP_TOPICS).remove(); - Topics groupMembership = - deploymentServiceConfig.lookupTopics(DeploymentService.GROUP_MEMBERSHIP_TOPICS); + Topics groupMembership = deploymentServiceConfig.lookupTopics(DeploymentService.GROUP_MEMBERSHIP_TOPICS); groupsForDevice.forEach(groupMembership::createLeafChild); return nonTargetGroupsToRootPackagesMap; @@ -325,15 +326,16 @@ private Map> getNonTargetGroupToRoot private Optional> getPersistedMembershipInfo() { Topics groupsToRootPackages = deploymentServiceConfig.lookupTopics(DeploymentService.GROUP_TO_ROOT_COMPONENTS_TOPICS); - return Optional.of(groupsToRootPackages.children.values().stream().map(Node::getName) + return Optional.of(groupsToRootPackages.children.values() + .stream() + .map(Node::getName) .filter(g -> !LOCAL_DEPLOYMENT_GROUP_NAME.equals(g)) .filter(g -> !g.startsWith(DEVICE_DEPLOYMENT_GROUP_NAME_PREFIX)) .collect(Collectors.toSet())); } private void cancelDeploymentTask(Future> resolveDependenciesFuture, - Future preparePackagesFuture, - Future deploymentMergeFuture) { + Future preparePackagesFuture, Future deploymentMergeFuture) { if (resolveDependenciesFuture != null && !resolveDependenciesFuture.isDone()) { resolveDependenciesFuture.cancel(true); logger.atInfo(DEPLOYMENT_TASK_EVENT_TYPE).log("Cancelled dependency resolution due to received interrupt"); diff --git a/src/main/java/com/aws/greengrass/deployment/DeploymentConfigMerger.java b/src/main/java/com/aws/greengrass/deployment/DeploymentConfigMerger.java index 79374f9b4d..57bc438343 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeploymentConfigMerger.java +++ b/src/main/java/com/aws/greengrass/deployment/DeploymentConfigMerger.java @@ -5,7 +5,6 @@ package com.aws.greengrass.deployment; - import com.aws.greengrass.config.Topics; import com.aws.greengrass.dependency.Context.Value; import com.aws.greengrass.dependency.State; @@ -73,19 +72,21 @@ public class DeploymentConfigMerger { * Merge in new configuration values and new services. * * @param deployment deployment object - * @param newConfig the map of new configuration + * @param newConfig the map of new configuration * @param configMergeTimestamp the timestamp to use when merging new configuration * @return future which completes only once the config is merged and all the services in the config are running */ - public Future mergeInNewConfig(Deployment deployment, - Map newConfig, long configMergeTimestamp) { + public Future mergeInNewConfig(Deployment deployment, Map newConfig, + long configMergeTimestamp) { CompletableFuture totallyCompleteFuture = new CompletableFuture<>(); DeploymentActivator activator; try { activator = kernel.getContext().get(DeploymentActivatorFactory.class).getDeploymentActivator(newConfig); } catch (ServiceUpdateException | ComponentConfigurationValidationException e) { // Failed to pre-process new config, no rollback needed - logger.atError().setEventType(MERGE_ERROR_LOG_EVENT_KEY).setCause(e) + logger.atError() + .setEventType(MERGE_ERROR_LOG_EVENT_KEY) + .setCause(e) .log("Failed to process new configuration for activation"); totallyCompleteFuture .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e)); @@ -100,15 +101,17 @@ public Future mergeInNewConfig(Deployment deployment, DeploymentDocument deploymentDocument = deployment.getDeploymentDocumentObj(); if (DeploymentComponentUpdatePolicyAction.NOTIFY_COMPONENTS .equals(deploymentDocument.getComponentUpdatePolicy().getComponentUpdatePolicyAction())) { - kernel.getContext().get(UpdateSystemPolicyService.class) + kernel.getContext() + .get(UpdateSystemPolicyService.class) .addUpdateAction(deploymentDocument.getDeploymentId(), - new UpdateAction(deploymentDocument.getDeploymentId(), - ggcRestart, deploymentDocument.getComponentUpdatePolicy().getTimeout(), + new UpdateAction(deploymentDocument.getDeploymentId(), ggcRestart, + deploymentDocument.getComponentUpdatePolicy().getTimeout(), () -> updateActionForDeployment(newConfig, deployment, activator, configMergeTimestamp, totallyCompleteFuture))); } else { - logger.atInfo().log("Deployment is configured to skip update policy check," - + " not waiting for disruptable time to update"); + logger.atInfo() + .log("Deployment is configured to skip update policy check," + + " not waiting for disruptable time to update"); // use executor service to execute updateActionForDeployment // prevents default deployment cancellation from directly interrupting executorService.execute(() -> updateActionForDeployment(newConfig, deployment, activator, @@ -120,13 +123,14 @@ public Future mergeInNewConfig(Deployment deployment, } private void updateActionForDeployment(Map newConfig, Deployment deployment, - DeploymentActivator activator, long configMergeTimestamp, - CompletableFuture totallyCompleteFuture) { + DeploymentActivator activator, long configMergeTimestamp, + CompletableFuture totallyCompleteFuture) { String deploymentId = deployment.getGreengrassDeploymentId(); // if the update is cancelled, don't perform merge if (totallyCompleteFuture.isCancelled()) { - logger.atInfo(MERGE_CONFIG_EVENT_KEY).kv("deployment", deploymentId) + logger.atInfo(MERGE_CONFIG_EVENT_KEY) + .kv("deployment", deploymentId) .log("Future was cancelled so no need to go through with the update"); return; } @@ -135,8 +139,8 @@ private void updateActionForDeployment(Map newConfig, Deployment if (newConfig.containsKey(SERVICES_NAMESPACE_TOPIC)) { serviceConfig = (Map) newConfig.get(SERVICES_NAMESPACE_TOPIC); if (serviceConfig.containsKey(deviceConfiguration.getNucleusComponentName())) { - Map nucleusNamespace = (Map) serviceConfig - .get(deviceConfiguration.getNucleusComponentName()); + Map nucleusNamespace = + (Map) serviceConfig.get(deviceConfiguration.getNucleusComponentName()); nucleusConfig = (Map) nucleusNamespace.get(CONFIGURATION_CONFIG_KEY); } } else { @@ -154,13 +158,12 @@ private void updateActionForDeployment(Map newConfig, Deployment return; } - logger.atInfo(MERGE_CONFIG_EVENT_KEY).kv("deployment", deploymentId) - .log("Applying deployment changes"); + logger.atInfo(MERGE_CONFIG_EVENT_KEY).kv("deployment", deploymentId).log("Applying deployment changes"); activator.activate(newConfig, deployment, configMergeTimestamp, totallyCompleteFuture); } private boolean validateNucleusConfig(CompletableFuture totallyCompleteFuture, - Map nucleusConfig) { + Map nucleusConfig) { if (nucleusConfig != null) { String awsRegion = tryGetAwsRegionFromNewConfig(nucleusConfig); String iotCredEndpoint = tryGetIoTCredEndpointFromNewConfig(nucleusConfig); @@ -178,23 +181,21 @@ private boolean validateNucleusConfig(CompletableFuture totall } /** - * Completes the provided future when all the listed services are running. - * Exits early if the future is cancelled + * Completes the provided future when all the listed services are running. Exits early if the future is cancelled * - * @param servicesToTrack services to track - * @param mergeTime time the merge was started, used to check if a service is broken due to the merge - * @param kernel kernel + * @param servicesToTrack services to track + * @param mergeTime time the merge was started, used to check if a service is broken due to the merge + * @param kernel kernel * @param totallyCompleteFuture used to check if a deployment is cancelled - * @throws InterruptedException if the thread is interrupted while waiting here + * @throws InterruptedException if the thread is interrupted while waiting here * @throws ServiceUpdateException if a service could not be updated */ public static void waitForServicesToStart(Collection servicesToTrack, long mergeTime, - Kernel kernel, CompletableFuture totallyCompleteFuture) + Kernel kernel, CompletableFuture totallyCompleteFuture) throws InterruptedException, ServiceUpdateException { while (!areAllServiceInDesiredState(servicesToTrack, mergeTime, kernel)) { if (totallyCompleteFuture.isCancelled()) { - logger.atWarn(MERGE_CONFIG_EVENT_KEY) - .log("deployment cancelled while waiting for services to start"); + logger.atWarn(MERGE_CONFIG_EVENT_KEY).log("deployment cancelled while waiting for services to start"); return; } Thread.sleep(WAIT_SVC_START_POLL_INTERVAL_MILLISEC); // hardcoded @@ -202,14 +203,15 @@ public static void waitForServicesToStart(Collection services } private static boolean areAllServiceInDesiredState(Collection servicesToTrack, long mergeTime, - Kernel kernel) throws ServiceUpdateException { + Kernel kernel) throws ServiceUpdateException { boolean allServicesRunning = true; for (GreengrassService service : servicesToTrack) { State state = service.getState(); // If a service is previously BROKEN, its state might have not been updated yet when this check // executes. We must check the service broke after merge map occurs. if (service.getStateModTime() > mergeTime && State.BROKEN.equals(state)) { - logger.atWarn(MERGE_CONFIG_EVENT_KEY).kv(SERVICE_NAME_LOG_KEY, service.getName()) + logger.atWarn(MERGE_CONFIG_EVENT_KEY) + .kv(SERVICE_NAME_LOG_KEY, service.getName()) .log("merge-config-service BROKEN"); throw new ServiceUpdateException( String.format("Service %s in broken state after deployment", service.getName()), @@ -220,8 +222,8 @@ private static boolean areAllServiceInDesiredState(Collection allServicesRunning = false; continue; } - if (State.RUNNING.equals(state) || State.FINISHED.equals(state) || !service.shouldAutoStart() - && service.reachedDesiredState()) { + if (State.RUNNING.equals(state) || State.FINISHED.equals(state) + || !service.shouldAutoStart() && service.reachedDesiredState()) { continue; } allServicesRunning = false; @@ -253,7 +255,6 @@ private String tryGetIoTDataEndpointFromNewConfig(Map kernelConf return iotDataEndpoint; } - @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public static class AggregateServicesChangeManager { @@ -267,29 +268,33 @@ public static class AggregateServicesChangeManager { /** * Constructs an object based on the current Kernel state and the config to be merged. * - * @param kernel Greengrass kernel + * @param kernel Greengrass kernel * @param newServiceConfig new config to be merged for deployment */ public AggregateServicesChangeManager(Kernel kernel, Map newServiceConfig) { // No builtin services should be modified in any way by deployments outside of - // Nucleus component update - Set runningDeployableServices = - kernel.orderedDependencies().stream().filter(s -> !s.isBuiltin()) - .map(GreengrassService::getServiceName) - .collect(Collectors.toSet()); + // Nucleus component update + Set runningDeployableServices = kernel.orderedDependencies() + .stream() + .filter(s -> !s.isBuiltin()) + .map(GreengrassService::getServiceName) + .collect(Collectors.toSet()); this.kernel = kernel; - this.servicesToAdd = newServiceConfig.keySet().stream() + this.servicesToAdd = newServiceConfig.keySet() + .stream() .filter(serviceName -> !runningDeployableServices.contains(serviceName)) .collect(Collectors.toSet()); - this.servicesToUpdate = newServiceConfig.keySet().stream().filter(runningDeployableServices::contains) + this.servicesToUpdate = newServiceConfig.keySet() + .stream() + .filter(runningDeployableServices::contains) .collect(Collectors.toSet()); - this.servicesToRemove = - runningDeployableServices.stream().filter(serviceName -> !newServiceConfig.containsKey(serviceName)) - .collect(Collectors.toSet()); + this.servicesToRemove = runningDeployableServices.stream() + .filter(serviceName -> !newServiceConfig.containsKey(serviceName)) + .collect(Collectors.toSet()); this.alreadyBrokenServices = runningDeployableServices.stream().filter(name -> { try { return kernel.locate(name).currentOrReportedStateIs(State.BROKEN); @@ -314,8 +319,8 @@ public AggregateServicesChangeManager(Kernel kernel, Map newServ public AggregateServicesChangeManager createRollbackManager() { // For rollback, services the deployment originally intended to add should be removed // and services it intended to remove should be added back - return new AggregateServicesChangeManager(kernel, servicesToRemove, servicesToUpdate, - servicesToAdd, alreadyBrokenServices, alreadyUnloadableServices); + return new AggregateServicesChangeManager(kernel, servicesToRemove, servicesToUpdate, servicesToAdd, + alreadyBrokenServices, alreadyUnloadableServices); } /** @@ -359,8 +364,7 @@ public void replaceUnloadableService() throws ServiceLoadException { kernel.getContext().remove(greengrassService.getClass()); kernel.locateIgnoreError(serviceName).requestReinstall(); } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.atError().kv(SERVICE_NAME_LOG_KEY, serviceName) - .log("Failed to close unloadable service", e); + logger.atError().kv(SERVICE_NAME_LOG_KEY, serviceName).log("Failed to close unloadable service", e); } } } @@ -369,7 +373,7 @@ public void replaceUnloadableService() throws ServiceLoadException { * Clean up services that the merge intends to remove. * * @throws InterruptedException when the merge is interrupted - * @throws ServiceUpdateException when error is encountered while trying to close any service + * @throws ServiceUpdateException when error is encountered while trying to close any service */ public void removeObsoleteServices() throws InterruptedException, ServiceUpdateException { Set ggServicesToRemove = new HashSet<>(); @@ -384,7 +388,9 @@ public void removeObsoleteServices() throws InterruptedException, ServiceUpdateE ggServicesToRemove.add(eg); } catch (ServiceLoadException e) { - logger.atError(MERGE_ERROR_LOG_EVENT_KEY).setCause(e).addKeyValue(SERVICE_NAME_LOG_KEY, serviceName) + logger.atError(MERGE_ERROR_LOG_EVENT_KEY) + .setCause(e) + .addKeyValue(SERVICE_NAME_LOG_KEY, serviceName) .log("Could not locate Greengrass service to close service"); // Even though we couldn't find it, we might still need to drop it from the context, so return true return true; diff --git a/src/main/java/com/aws/greengrass/deployment/DeploymentDirectoryManager.java b/src/main/java/com/aws/greengrass/deployment/DeploymentDirectoryManager.java index 84226057fe..ad873c14c1 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeploymentDirectoryManager.java +++ b/src/main/java/com/aws/greengrass/deployment/DeploymentDirectoryManager.java @@ -5,7 +5,6 @@ package com.aws.greengrass.deployment; - import com.aws.greengrass.deployment.model.Deployment; import com.aws.greengrass.lifecyclemanager.Kernel; import com.aws.greengrass.logging.api.Logger; @@ -137,8 +136,10 @@ public void writeDeploymentMetadata(Deployment deployment) throws IOException { throw new IOException("Deployment details can not be saved to directory " + ongoingDir); } Path filePath = getDeploymentMetadataFilePath(); - logger.atInfo().kv(FILE_LOG_KEY, filePath).kv(DEPLOYMENT_ID_LOG_KEY, - deployment.getGreengrassDeploymentId()).log("Saving deployment metadata to file"); + logger.atInfo() + .kv(FILE_LOG_KEY, filePath) + .kv(DEPLOYMENT_ID_LOG_KEY, deployment.getGreengrassDeploymentId()) + .log("Saving deployment metadata to file"); writeDeploymentMetadata(filePath, deployment); } @@ -252,7 +253,8 @@ public Path createNewDeploymentDirectory(String deploymentId) throws IOException Path path = deploymentsDir.resolve(getSafeFileName(deploymentId)); if (Files.exists(path)) { - logger.atWarn().kv("directory", path) + logger.atWarn() + .kv("directory", path) .log("Deployment directory already exists. Clean up outdated artifacts and create new"); try { Utils.deleteFileRecursively(path.toFile()); @@ -262,7 +264,10 @@ public Path createNewDeploymentDirectory(String deploymentId) throws IOException } } - logger.atInfo().kv("directory", path).kv(DEPLOYMENT_ID_LOG_KEY, deploymentId).kv(LINK_LOG_KEY, ongoingDir) + logger.atInfo() + .kv("directory", path) + .kv(DEPLOYMENT_ID_LOG_KEY, deploymentId) + .kv(LINK_LOG_KEY, ongoingDir) .log("Create work directory for new deployment"); Utils.createPaths(path); Files.createSymbolicLink(ongoingDir, path); diff --git a/src/main/java/com/aws/greengrass/deployment/DeploymentDocumentDownloader.java b/src/main/java/com/aws/greengrass/deployment/DeploymentDocumentDownloader.java index 374188b01f..7cffc66914 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeploymentDocumentDownloader.java +++ b/src/main/java/com/aws/greengrass/deployment/DeploymentDocumentDownloader.java @@ -71,28 +71,30 @@ public class DeploymentDocumentDownloader { * Constructor. * * @param greengrassServiceClientFactory greengrassServiceClientFactory for lazily initialize the client. - * @param deviceConfiguration deviceConfiguration for getting the thing name topic. - * @param httpClientProvider httpClientProvider for making calls to presigned url. + * @param deviceConfiguration deviceConfiguration for getting the thing name topic. + * @param httpClientProvider httpClientProvider for making calls to presigned url. */ @Inject public DeploymentDocumentDownloader(GreengrassServiceClientFactory greengrassServiceClientFactory, - DeviceConfiguration deviceConfiguration, - HttpClientProvider httpClientProvider) { + DeviceConfiguration deviceConfiguration, HttpClientProvider httpClientProvider) { this.greengrassServiceClientFactory = greengrassServiceClientFactory; this.deviceConfiguration = deviceConfiguration; this.httpClientProvider = httpClientProvider; - RetryUtils.RetryConfig infiniteRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1)) - .maxRetryInterval(Duration.ofMinutes(1)).maxAttempt(Integer.MAX_VALUE) - .retryableExceptions(Arrays.asList(RetryableDeploymentDocumentDownloadException.class, - DeviceConfigurationException.class, RetryableServerErrorException.class)).build(); - - RetryUtils.RetryConfig finiteRetryConfig = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMinutes(1)) - .maxRetryInterval(Duration.ofMinutes(1)).maxAttempt(MAX_CLIENT_ERROR_RETRY_COUNT) - .retryableExceptions(Collections.singletonList(RetryableClientErrorException.class)).build(); + RetryUtils.RetryConfig infiniteRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1)) + .maxRetryInterval(Duration.ofMinutes(1)) + .maxAttempt(Integer.MAX_VALUE) + .retryableExceptions(Arrays.asList(RetryableDeploymentDocumentDownloadException.class, + DeviceConfigurationException.class, RetryableServerErrorException.class)) + .build(); + RetryUtils.RetryConfig finiteRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1)) + .maxRetryInterval(Duration.ofMinutes(1)) + .maxAttempt(MAX_CLIENT_ERROR_RETRY_COUNT) + .retryableExceptions(Collections.singletonList(RetryableClientErrorException.class)) + .build(); this.clientExceptionRetryConfig = RetryUtils.DifferentiatedRetryConfig.builder() .retryConfigList(Arrays.asList(infiniteRetryConfig, finiteRetryConfig)) @@ -107,7 +109,9 @@ public DeploymentDocumentDownloader(GreengrassServiceClientFactory greengrassSer * @throws DeploymentTaskFailureException if failed to download the full deployment document. * @throws InterruptedException if interrupted. */ - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) public DeploymentDocument download(String deploymentId) throws InterruptedException, DeploymentTaskFailureException { if (!deviceConfiguration.isDeviceConfiguredToTalkToCloud()) { @@ -119,8 +123,7 @@ public DeploymentDocument download(String deploymentId) String configurationString; try { configurationString = RetryUtils.runWithRetry(clientExceptionRetryConfig, - () -> downloadDeploymentDocument(deploymentId), "download-large-configuration", - logger); + () -> downloadDeploymentDocument(deploymentId), "download-large-configuration", logger); } catch (InterruptedException e) { throw e; } catch (Exception e) { @@ -130,10 +133,9 @@ public DeploymentDocument download(String deploymentId) return deserializeDeploymentDoc(configurationString); } - protected String downloadDeploymentDocument(String deploymentId) - throws DeploymentTaskFailureException, RetryableDeploymentDocumentDownloadException, - DeviceConfigurationException, HashingAlgorithmUnavailableException, RetryableServerErrorException, - RetryableClientErrorException { + protected String downloadDeploymentDocument(String deploymentId) throws DeploymentTaskFailureException, + RetryableDeploymentDocumentDownloadException, DeviceConfigurationException, + HashingAlgorithmUnavailableException, RetryableServerErrorException, RetryableClientErrorException { // 1. Get url, digest, and algorithm by calling gg data plane GetDeploymentConfigurationResponse response = getDeploymentConfiguration(deploymentId); @@ -156,8 +158,7 @@ private String downloadFromUrl(String deploymentId, String preSignedUrl) .build(); // url is not logged for security concerns - logger.atDebug().kv("DeploymentId", deploymentId) - .log("Making HTTP request to the presigned url"); + logger.atDebug().kv("DeploymentId", deploymentId).log("Making HTTP request to the presigned url"); try (SdkHttpClient client = httpClientProvider.getSdkHttpClient()) { @@ -188,29 +189,36 @@ private GetDeploymentConfigurationResponse getDeploymentConfiguration(String dep DeploymentTaskFailureException, RetryableServerErrorException, RetryableClientErrorException { String thingName = Coerce.toString(deviceConfiguration.getThingName()); GetDeploymentConfigurationRequest getDeploymentConfigurationRequest = - GetDeploymentConfigurationRequest.builder().deploymentId(deploymentId).coreDeviceThingName(thingName) - .s3EndpointType(Coerce.toString(deviceConfiguration.gets3EndpointType())).build(); + GetDeploymentConfigurationRequest.builder() + .deploymentId(deploymentId) + .coreDeviceThingName(thingName) + .s3EndpointType(Coerce.toString(deviceConfiguration.gets3EndpointType())) + .build(); GetDeploymentConfigurationResponse deploymentConfiguration; try { - logger.atInfo().kv("DeploymentId", deploymentId).kv("ThingName", thingName) + logger.atInfo() + .kv("DeploymentId", deploymentId) + .kv("ThingName", thingName) .log("Calling Greengrass cloud to get full deployment configuration"); deploymentConfiguration = greengrassServiceClientFactory.fetchGreengrassV2DataClient() - .getDeploymentConfiguration(getDeploymentConfigurationRequest); + .getDeploymentConfiguration(getDeploymentConfigurationRequest); } catch (GreengrassV2DataException e) { if (RetryUtils.retryErrorCodes(e.statusCode())) { - throw new RetryableServerErrorException("Failed with retryable error: " + e.statusCode() - + " while calling getDeploymentConfiguration", e); + throw new RetryableServerErrorException( + "Failed with retryable error: " + e.statusCode() + " while calling getDeploymentConfiguration", + e); } // also retry on 404s because sometimes querying DDB may fail initially due to its eventual consistency if (e.statusCode() == HttpStatusCode.NOT_FOUND) { - throw new RetryableClientErrorException("Failed with retryable error: " + e.statusCode() - + " while calling getDeploymentConfiguration", e); + throw new RetryableClientErrorException( + "Failed with retryable error: " + e.statusCode() + " while calling getDeploymentConfiguration", + e); } - if (e.statusCode() == HttpStatusCode.FORBIDDEN) { + if (e.statusCode() == HttpStatusCode.FORBIDDEN) { throw new DeploymentTaskFailureException( "Access denied when calling GetDeploymentConfiguration. Ensure " + "certificate policy grants greengrass:GetDeploymentConfiguration", @@ -223,9 +231,9 @@ private GetDeploymentConfigurationResponse getDeploymentConfiguration(String dep } catch (SdkClientException e) { throw new RetryableDeploymentDocumentDownloadException( "Failed to contact Greengrass cloud or unable to parse response", e); - } catch (TLSAuthException e) { - throw new RetryableClientErrorException( - "Failed to contact Greengrass cloud or unable to parse response", e); + } catch (TLSAuthException e) { + throw new RetryableClientErrorException("Failed to contact Greengrass cloud or unable to parse response", + e); } return deploymentConfiguration; } @@ -238,16 +246,18 @@ private void validateHttpExecuteResponse(HttpExecuteResponse executeResponse) executeResponse.httpResponse().statusCode(), executeResponse.httpResponse().statusText().orElse(StringUtils.EMPTY))); } - Optional deploymentDocumentSizeOptional = executeResponse.httpResponse() - .firstMatchingHeader(CONTENT_LENGTH_HEADER); + Optional deploymentDocumentSizeOptional = + executeResponse.httpResponse().firstMatchingHeader(CONTENT_LENGTH_HEADER); - //this should never happen as GGC cloud supports max 10 MB documents due to API GW payload limit, - //but adding a check as deployment document is read into process memory. + // this should never happen as GGC cloud supports max 10 MB documents due to API GW payload limit, + // but adding a check as deployment document is read into process memory. if (deploymentDocumentSizeOptional.isPresent() && Long.parseLong(deploymentDocumentSizeOptional.get()) > MAX_DEPLOYMENT_DOCUMENT_SIZE_BYTES) { - throw new DeploymentTaskFailureException(String.format("Requested deployment document exceeded size limit." - + " The requested document is %s bytes, but the size limit is %s bytes", - deploymentDocumentSizeOptional.get(), MAX_DEPLOYMENT_DOCUMENT_SIZE_BYTES), + throw new DeploymentTaskFailureException( + String.format( + "Requested deployment document exceeded size limit." + + " The requested document is %s bytes, but the size limit is %s bytes", + deploymentDocumentSizeOptional.get(), MAX_DEPLOYMENT_DOCUMENT_SIZE_BYTES), DeploymentErrorCode.DEPLOYMENT_DOCUMENT_SIZE_EXCEEDED); } @@ -260,15 +270,15 @@ private void validateHttpExecuteResponse(HttpExecuteResponse executeResponse) private DeploymentDocument deserializeDeploymentDoc(String configurationInString) throws DeploymentTaskFailureException { try { - Configuration configuration = SerializerFactory.getFailSafeJsonObjectMapper() + Configuration configuration = SerializerFactory.getFailSafeJsonObjectMapper() .readValue(configurationInString, Configuration.class); return DeploymentDocumentConverter.convertFromDeploymentConfiguration(configuration); } catch (IOException e) { - throw new DeploymentTaskFailureException("Failed to deserialize deployment document", e) - .withErrorContext(e, DeploymentErrorCode.DEPLOYMENT_DOCUMENT_PARSE_ERROR); + throw new DeploymentTaskFailureException("Failed to deserialize deployment document", e).withErrorContext(e, + DeploymentErrorCode.DEPLOYMENT_DOCUMENT_PARSE_ERROR); } catch (InvalidRequestException e) { - throw new DeploymentTaskFailureException("Invalid component metadata from deployment document", - e).withErrorContext(e, DeploymentErrorCode.COMPONENT_METADATA_NOT_VALID_IN_DEPLOYMENT); + throw new DeploymentTaskFailureException("Invalid component metadata from deployment document", e) + .withErrorContext(e, DeploymentErrorCode.COMPONENT_METADATA_NOT_VALID_IN_DEPLOYMENT); } } @@ -280,7 +290,8 @@ private void checkIntegrity(String algorithm, String digest, String configuratio if (!calculatedDigest.equals(digest)) { throw new RetryableDeploymentDocumentDownloadException(String.format( "Integrity check failed because the calculated digest is different from provided digest.%n" - + "Provided digest: '%s'. %nCalculated digest: '%s'", digest, calculatedDigest)); + + "Provided digest: '%s'. %nCalculated digest: '%s'", + digest, calculatedDigest)); } } catch (NoSuchAlgorithmException e) { // This should never happen as SHA-256 is mandatory for every default JVM provider diff --git a/src/main/java/com/aws/greengrass/deployment/DeploymentQueue.java b/src/main/java/com/aws/greengrass/deployment/DeploymentQueue.java index bec9f0fe58..a08e468014 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeploymentQueue.java +++ b/src/main/java/com/aws/greengrass/deployment/DeploymentQueue.java @@ -19,28 +19,35 @@ import java.util.concurrent.locks.Lock; /** - *

DeploymentQueue is a thread-safe deployment queue that automatically de-duplicates by deployment id, and - * also de-duplicates shadow deployments, i.e. there can be at-most-one shadow deployment enqueued.

+ *

+ * DeploymentQueue is a thread-safe deployment queue that automatically de-duplicates by deployment id, and also + * de-duplicates shadow deployments, i.e. there can be at-most-one shadow deployment enqueued. + *

* - *

DeploymentQueue is implemented internally as a LinkedHashMap of id -> deployment.

+ *

+ * DeploymentQueue is implemented internally as a LinkedHashMap of id -> deployment. + *

* - *

When an offered deployment has a unique deployment id, it is enqueued normally.

+ *

+ * When an offered deployment has a unique deployment id, it is enqueued normally. + *

* - *

When an offered deployment has the same deployment id as an already-enqueued element: - * - if the offered deployment meets replacement criteria, then the enqueued element is replaced by the offered - * element, preserving queue order. - * - otherwise, the offered deployment is ignored.

+ *

+ * When an offered deployment has the same deployment id as an already-enqueued element: - if the offered deployment + * meets replacement criteria, then the enqueued element is replaced by the offered element, preserving queue order. - + * otherwise, the offered deployment is ignored. + *

* - *

Replacement criteria are as follows: - * - if enqueued.getDeploymentStage() != DEFAULT, then do not replace - * - else replace if: - * - offered.getDeploymentStage() != DEFAULT, OR - * - offered.isCancelled() == true, OR - * - offered.getDeploymentType().equals(SHADOW)

+ *

+ * Replacement criteria are as follows: - if enqueued.getDeploymentStage() != DEFAULT, then do not replace - else + * replace if: - offered.getDeploymentStage() != DEFAULT, OR - offered.isCancelled() == true, OR - + * offered.getDeploymentType().equals(SHADOW) + *

* - *

When an offered deployment is a shadow deployment: - * - if there is already a shadow deployment in the queue, then replace it and preserve queue order. - * - otherwise, enqueue normally.

+ *

+ * When an offered deployment is a shadow deployment: - if there is already a shadow deployment in the queue, then + * replace it and preserve queue order. - otherwise, enqueue normally. + *

*/ public class DeploymentQueue { @@ -61,23 +68,26 @@ public class DeploymentQueue { private final Lock lock = LockFactory.newReentrantLock(this); /** - *

If the offered deployment id is unique, then insert the offered deployment at the tail of the queue.

+ *

+ * If the offered deployment id is unique, then insert the offered deployment at the tail of the queue. + *

* - *

When an offered deployment has the same deployment id as an already-enqueued element: - * - if the offered deployment meets replacement criteria, then the enqueued element is replaced by the offered - * element, preserving queue order. - * - otherwise, the offered deployment is ignored.

+ *

+ * When an offered deployment has the same deployment id as an already-enqueued element: - if the offered deployment + * meets replacement criteria, then the enqueued element is replaced by the offered element, preserving queue order. + * - otherwise, the offered deployment is ignored. + *

* - *

Replacement criteria are as follows: - * - if enqueued.getDeploymentStage() != DEFAULT, then do not replace - * - else replace if: - * - offered.getDeploymentStage() != DEFAULT, OR - * - offered.isCancelled() == true, OR - * - offered.getDeploymentType().equals(SHADOW)

+ *

+ * Replacement criteria are as follows: - if enqueued.getDeploymentStage() != DEFAULT, then do not replace - else + * replace if: - offered.getDeploymentStage() != DEFAULT, OR - offered.isCancelled() == true, OR - + * offered.getDeploymentType().equals(SHADOW) + *

* - *

When an offered deployment is a shadow deployment: - * - if there is already a shadow deployment in the queue, then replace it and preserve queue order. - * - otherwise, enqueue normally.

+ *

+ * When an offered deployment is a shadow deployment: - if there is already a shadow deployment in the queue, then + * replace it and preserve queue order. - otherwise, enqueue normally. + *

* * @param offeredDeployment the offered deployment instance. * @return true if the queue was modified, otherwise false. @@ -100,15 +110,18 @@ public boolean offer(Deployment offeredDeployment) { // internal queue id is already in use; check the replacement criteria final Deployment enqueuedDeployment = deploymentMap.get(offeredDeploymentInternalId); if (checkReplacementCriteria(enqueuedDeployment, offeredDeployment)) { - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY, offeredDeployment.getId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY, offeredDeployment.getId()) .kv(DISCARDED_DEPLOYMENT_ID_LOG_KEY, - deploymentMap.get(offeredDeploymentInternalId) == null ? null + deploymentMap.get(offeredDeploymentInternalId) == null + ? null : deploymentMap.get(offeredDeploymentInternalId).getId()) .log("New deployment replacing enqueued deployment"); deploymentMap.put(offeredDeploymentInternalId, offeredDeployment); return true; } - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY, offeredDeployment.getId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY, offeredDeployment.getId()) .log("New deployment ignored as duplicate"); return false; } diff --git a/src/main/java/com/aws/greengrass/deployment/DeploymentService.java b/src/main/java/com/aws/greengrass/deployment/DeploymentService.java index 1778e389ab..a0c9726edd 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeploymentService.java +++ b/src/main/java/com/aws/greengrass/deployment/DeploymentService.java @@ -5,7 +5,6 @@ package com.aws.greengrass.deployment; - import com.amazon.aws.iot.greengrass.component.common.ComponentRecipe; import com.amazon.aws.iot.greengrass.configuration.common.Configuration; import com.aws.greengrass.componentmanager.ComponentManager; @@ -157,14 +156,14 @@ public DeploymentService(Topics topics) { /** * Constructor for unit testing. * - * @param topics The configuration coming from kernel - * @param executorService Executor service coming from kernel - * @param dependencyResolver {@link DependencyResolver} - * @param componentManager {@link ComponentManager} - * @param kernelConfigResolver {@link KernelConfigResolver} + * @param topics The configuration coming from kernel + * @param executorService Executor service coming from kernel + * @param dependencyResolver {@link DependencyResolver} + * @param componentManager {@link ComponentManager} + * @param kernelConfigResolver {@link KernelConfigResolver} * @param deploymentConfigMerger {@link DeploymentConfigMerger} - * @param kernel {@link Kernel} - * @param deviceConfiguration {@link DeviceConfiguration} + * @param kernel {@link Kernel} + * @param deviceConfiguration {@link DeviceConfiguration} */ @SuppressWarnings("PMD.ExcessiveParameterList") DeploymentService(Topics topics, ExecutorService executorService, DependencyResolver dependencyResolver, @@ -200,7 +199,9 @@ public void postInject() { } @Override - @SuppressWarnings({"PMD.AvoidDeeplyNestedIfStmts", "PMD.NullAssignment"}) + @SuppressWarnings({ + "PMD.AvoidDeeplyNestedIfStmts", "PMD.NullAssignment" + }) protected void startup() throws InterruptedException { // Reset shutdown signal since we're trying to startup here this.receivedShutdown.set(false); @@ -213,12 +214,12 @@ protected void startup() throws InterruptedException { } while (!receivedShutdown.get()) { - if (currentDeploymentTaskMetadata != null && currentDeploymentTaskMetadata.getDeploymentResultFuture() - .isDone()) { + if (currentDeploymentTaskMetadata != null + && currentDeploymentTaskMetadata.getDeploymentResultFuture().isDone()) { finishCurrentDeployment(); } - //Cannot wait on queue because need to listen to queue as well as the currentProcessStatus future. - //One thread cannot wait on both. If we want to make this completely event driven then we need to put + // Cannot wait on queue because need to listen to queue as well as the currentProcessStatus future. + // One thread cannot wait on both. If we want to make this completely event driven then we need to put // the waiting on currentProcessStatus in its own thread. I currently choose to not do this. if (nextDeployment == null) { nextDeployment = deploymentQueue.poll(); @@ -231,7 +232,8 @@ protected void startup() throws InterruptedException { .equals(nextDeployment.getDeploymentType())) { // Cancel the current deployment if it's an IoT Jobs deployment // that is in progress and still cancellable. - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Canceling current deployment"); @@ -239,7 +241,8 @@ protected void startup() throws InterruptedException { cancelCurrentDeployment(); } else if (currentDeploymentTaskMetadata != null) { // Ignore the cancelling signal if the deployment is NOT cancellable any more. - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("The current deployment cannot be cancelled"); @@ -247,11 +250,12 @@ protected void startup() throws InterruptedException { nextDeployment = null; } else if (DeploymentType.SHADOW.equals(nextDeployment.getDeploymentType())) { // The deployment type is shadow - if (currentDeploymentTaskMetadata != null && DeploymentType.SHADOW.equals( - currentDeploymentTaskMetadata.getDeploymentType())) { + if (currentDeploymentTaskMetadata != null + && DeploymentType.SHADOW.equals(currentDeploymentTaskMetadata.getDeploymentType())) { // A new device deployment invalidates the previous deployment, cancel the ongoing device - //deployment and wait till the new device deployment can be picked up. - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + // deployment and wait till the new device deployment can be picked up. + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Canceling current device deployment"); @@ -263,11 +267,13 @@ protected void startup() throws InterruptedException { } } else if (DeploymentType.IOT_JOBS.equals(nextDeployment.getDeploymentType())) { // The deployment type is IoT Jobs - if (currentDeploymentTaskMetadata != null && currentDeploymentTaskMetadata.getDeploymentId() - .equals(nextDeployment.getId()) && currentDeploymentTaskMetadata.getDeploymentType() - .equals(nextDeployment.getDeploymentType())) { + if (currentDeploymentTaskMetadata != null + && currentDeploymentTaskMetadata.getDeploymentId().equals(nextDeployment.getId()) + && currentDeploymentTaskMetadata.getDeploymentType() + .equals(nextDeployment.getDeploymentType())) { // The new deployment is duplicate of current in progress deployment. Ignore the new one. - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, nextDeployment.getId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, nextDeployment.getId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Skip the duplicated IoT Jobs deployment"); @@ -285,7 +291,8 @@ protected void startup() throws InterruptedException { nextDeployment = null; } } else { - logger.atError().kv(DEPLOYMENT_ID_LOG_KEY_NAME, nextDeployment.getId()) + logger.atError() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, nextDeployment.getId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .kv("DeploymentType", nextDeployment.getDeploymentType()) @@ -313,7 +320,8 @@ protected void shutdown() { @SuppressWarnings("PMD.NullAssignment") private void finishCurrentDeployment() throws InterruptedException { - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Current deployment finished"); String deploymentId = currentDeploymentTaskMetadata.getDeploymentId(); @@ -331,7 +339,7 @@ private void finishCurrentDeployment() throws InterruptedException { Map statusDetails = new HashMap<>(); statusDetails.put(DEPLOYMENT_DETAILED_STATUS_KEY, deploymentStatus.name()); if (DeploymentStatus.SUCCESSFUL.equals(deploymentStatus)) { - //Add the root packages of successful deployment to the configuration + // Add the root packages of successful deployment to the configuration persistGroupToRootComponents(currentDeploymentTaskMetadata.getDeploymentDocument()); deploymentStatusKeeper.persistAndPublishDeploymentStatus(deploymentId, ggDeploymentId, @@ -349,7 +357,9 @@ private void finishCurrentDeployment() throws InterruptedException { if (result.getFailureCause() != null) { updateStatusDetailsFromException(statusDetails, result.getFailureCause(), currentDeploymentTaskMetadata.getDeploymentType()); - logger.atWarn().setCause(result.getFailureCause()).kv(DEPLOYMENT_ID_LOG_KEY_NAME, deploymentId) + logger.atWarn() + .setCause(result.getFailureCause()) + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, deploymentId) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, ggDeploymentId) .kv(DEPLOYMENT_DETAILED_STATUS_KEY, result.getDeploymentStatus()) .kv(DEPLOYMENT_ERROR_STACK_KEY, statusDetails.get(DEPLOYMENT_ERROR_STACK_KEY)) @@ -362,7 +372,9 @@ private void finishCurrentDeployment() throws InterruptedException { if (result.getFailureCause() != null) { updateStatusDetailsFromException(statusDetails, result.getFailureCause(), currentDeploymentTaskMetadata.getDeploymentType()); - logger.atError().setCause(result.getFailureCause()).kv(DEPLOYMENT_ID_LOG_KEY_NAME, deploymentId) + logger.atError() + .setCause(result.getFailureCause()) + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, deploymentId) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, ggDeploymentId) .kv(DEPLOYMENT_DETAILED_STATUS_KEY, result.getDeploymentStatus()) .kv(DEPLOYMENT_ERROR_STACK_KEY, statusDetails.get(DEPLOYMENT_ERROR_STACK_KEY)) @@ -391,25 +403,30 @@ private void finishCurrentDeployment() throws InterruptedException { } catch (ExecutionException e) { Throwable t = e.getCause(); if (t instanceof InterruptedException) { - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Deployment task is interrupted"); } else { // This code path can only occur when DeploymentTask throws unchecked exception. Map statusDetails = new HashMap<>(); updateStatusDetailsFromException(statusDetails, t, currentDeploymentTaskMetadata.getDeploymentType()); - logger.atError().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + logger.atError() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, ggDeploymentId) .kv(DEPLOYMENT_ERROR_STACK_KEY, statusDetails.get(DEPLOYMENT_ERROR_STACK_KEY)) - .kv(DEPLOYMENT_ERROR_TYPES_KEY, statusDetails.get(DEPLOYMENT_ERROR_TYPES_KEY)).setCause(t) + .kv(DEPLOYMENT_ERROR_TYPES_KEY, statusDetails.get(DEPLOYMENT_ERROR_TYPES_KEY)) + .setCause(t) .log("Deployment task throws unknown exception"); deploymentStatusKeeper.persistAndPublishDeploymentStatus(deploymentId, ggDeploymentId, configurationArn, type, JobStatus.FAILED.toString(), statusDetails, rootPackages); deploymentDirectoryManager.persistLastFailedDeployment(); } } catch (CancellationException e) { - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) - .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, ggDeploymentId).log("Deployment task is cancelled"); + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, ggDeploymentId) + .log("Deployment task is cancelled"); } // Setting this to null to indicate there is no current deployment being processed // Did not use optionals over null due to performance @@ -430,9 +447,9 @@ private void persistGroupToRootComponents(DeploymentDocument deploymentDocument) Map pkgDetails = new HashMap<>(); pkgDetails.put(GROUP_TO_ROOT_COMPONENTS_VERSION_KEY, pkgConfig.getResolvedVersion()); pkgDetails.put(GROUP_TO_ROOT_COMPONENTS_GROUP_NAME, deploymentDocument.getGroupName()); - String configurationArn = - Utils.isEmpty(deploymentDocument.getConfigurationArn()) ? deploymentDocument.getDeploymentId() - : deploymentDocument.getConfigurationArn(); + String configurationArn = Utils.isEmpty(deploymentDocument.getConfigurationArn()) + ? deploymentDocument.getDeploymentId() + : deploymentDocument.getConfigurationArn(); pkgDetails.put(GROUP_TO_ROOT_COMPONENTS_GROUP_CONFIG_ARN, configurationArn); deploymentGroupToRootPackages.put(pkgConfig.getPackageName(), pkgDetails); } @@ -442,8 +459,7 @@ private void persistGroupToRootComponents(DeploymentDocument deploymentDocument) Map lastDeploymentDetails = new HashMap<>(); lastDeploymentDetails.put(GROUP_TO_LAST_DEPLOYMENT_TIMESTAMP_KEY, deploymentDocument.getTimestamp()); lastDeploymentDetails.put(GROUP_TO_LAST_DEPLOYMENT_CONFIG_ARN_KEY, deploymentDocument.getConfigurationArn()); - groupLastDeploymentTopics.lookupTopics(deploymentDocument.getGroupName()) - .replaceAndWait(lastDeploymentDetails); + groupLastDeploymentTopics.lookupTopics(deploymentDocument.getGroupName()).replaceAndWait(lastDeploymentDetails); // persist group to root packages mapping deploymentGroupTopics.lookupTopics(deploymentDocument.getGroupName()) @@ -459,9 +475,9 @@ private void cleanupGroupData(Topics deploymentGroupTopics, Topics groupLastDepl deploymentGroupTopics.forEach(node -> { if (node instanceof Topics) { Topics groupTopics = (Topics) node; - if (groupMembershipTopics.find(groupTopics.getName()) == null && !groupTopics.getName() - .startsWith(DEVICE_DEPLOYMENT_GROUP_NAME_PREFIX) && !groupTopics.getName() - .equals(LOCAL_DEPLOYMENT_GROUP_NAME)) { + if (groupMembershipTopics.find(groupTopics.getName()) == null + && !groupTopics.getName().startsWith(DEVICE_DEPLOYMENT_GROUP_NAME_PREFIX) + && !groupTopics.getName().equals(LOCAL_DEPLOYMENT_GROUP_NAME)) { logger.debug("Removing mapping for thing group " + groupTopics.getName()); groupTopics.remove(); } @@ -471,9 +487,9 @@ private void cleanupGroupData(Topics deploymentGroupTopics, Topics groupLastDepl groupLastDeploymentTopics.forEach(node -> { if (node instanceof Topics) { Topics groupTopics = (Topics) node; - if (groupMembershipTopics.find(groupTopics.getName()) == null && !groupTopics.getName() - .startsWith(DEVICE_DEPLOYMENT_GROUP_NAME_PREFIX) && !groupTopics.getName() - .equals(LOCAL_DEPLOYMENT_GROUP_NAME)) { + if (groupMembershipTopics.find(groupTopics.getName()) == null + && !groupTopics.getName().startsWith(DEVICE_DEPLOYMENT_GROUP_NAME_PREFIX) + && !groupTopics.getName().equals(LOCAL_DEPLOYMENT_GROUP_NAME)) { logger.debug("Removing last deployment information for thing group " + groupTopics.getName()); groupTopics.remove(); } @@ -483,34 +499,32 @@ private void cleanupGroupData(Topics deploymentGroupTopics, Topics groupLastDepl } /* - * When a cancellation is received, there are following possibilities - - * - No task has yet been created for current deployment so result future is null, we do nothing for this - * - If the result future is already cancelled, nothing to do. - * - If the result future has already completed then we cannot cancel it, we do nothing for this - * - The deployment is not yet running the update, i.e. it may be in any one of dependency resolution stage/ - * package download stage/ config resolution stage/ waiting for safe time to update as part of the merge stage, - * in that case we cancel that update and the DeploymentResult future - * - The deployment is already executing the update, so we let it finish - * For cases when deployment cannot be cancelled customers can figure out what happened through logs + * When a cancellation is received, there are following possibilities - - No task has yet been created for current + * deployment so result future is null, we do nothing for this - If the result future is already cancelled, nothing + * to do. - If the result future has already completed then we cannot cancel it, we do nothing for this - The + * deployment is not yet running the update, i.e. it may be in any one of dependency resolution stage/ package + * download stage/ config resolution stage/ waiting for safe time to update as part of the merge stage, in that case + * we cancel that update and the DeploymentResult future - The deployment is already executing the update, so we let + * it finish For cases when deployment cannot be cancelled customers can figure out what happened through logs * because in the case of IoT jobs, a cancelled job does not accept status update */ @SuppressWarnings("PMD.NullAssignment") private void cancelCurrentDeployment() { - if (currentDeploymentTaskMetadata.getDeploymentResultFuture() != null && !currentDeploymentTaskMetadata - .getDeploymentResultFuture().isCancelled()) { + if (currentDeploymentTaskMetadata.getDeploymentResultFuture() != null + && !currentDeploymentTaskMetadata.getDeploymentResultFuture().isCancelled()) { if (currentDeploymentTaskMetadata.getDeploymentResultFuture().isDone()) { logger.atInfo().log("Deployment already finished processing or cannot be cancelled"); } else { DeploymentTask deploymentTask = currentDeploymentTaskMetadata.getDeploymentTask(); - if (deploymentTask instanceof DefaultDeploymentTask) { + if (deploymentTask instanceof DefaultDeploymentTask) { DefaultDeploymentTask defaultDeploymentTask = (DefaultDeploymentTask) deploymentTask; context.get(UpdateSystemPolicyService.class) - .discardPendingUpdateAction(defaultDeploymentTask.getDeployment() - .getGreengrassDeploymentId()); + .discardPendingUpdateAction( + defaultDeploymentTask.getDeployment().getGreengrassDeploymentId()); } - logger.atWarn().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) - .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, - currentDeploymentTaskMetadata.getGreengrassDeploymentId()) + logger.atWarn() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Cancelling deployment, changes may already have been applied. " + "Make a new deployment to revert the update"); @@ -524,7 +538,8 @@ private void cancelCurrentDeployment() { currentDeploymentTaskMetadata.getDeploymentType(), JobStatus.CANCELED.toString(), new HashMap<>(), currentDeploymentTaskMetadata.getRootPackages()); } - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getDeploymentId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, currentDeploymentTaskMetadata.getGreengrassDeploymentId()) .log("Deployment was cancelled"); } @@ -532,7 +547,8 @@ private void cancelCurrentDeployment() { } private void createNewDeployment(Deployment deployment) { - logger.atInfo().kv(DEPLOYMENT_ID_LOG_KEY_NAME, deployment.getId()) + logger.atInfo() + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, deployment.getId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, deployment.getGreengrassDeploymentId()) .kv("DeploymentType", deployment.getDeploymentType().toString()) .log("Received deployment in the queue"); @@ -555,22 +571,25 @@ private void createNewDeployment(Deployment deployment) { } /* - * Enforce deployments are received for a given deployment target (thing or thingGroup) in sequence such - * that old deployments for that target does not override a new deployment. + * Enforce deployments are received for a given deployment target (thing or thingGroup) in sequence such that + * old deployments for that target does not override a new deployment. */ if (checkIfDeploymentReceivedIsStale(deployment.getDeploymentDocumentObj(), deployment.getDeploymentType())) { - logger.atInfo().log("Nucleus has a newer deployment for '{}' target. Rejecting the deployment", - deployment.getDeploymentDocumentObj().getGroupName()); + logger.atInfo() + .log("Nucleus has a newer deployment for '{}' target. Rejecting the deployment", + deployment.getDeploymentDocumentObj().getGroupName()); Topics lastDeployment = config.lookupTopics(DeploymentService.GROUP_TO_LAST_DEPLOYMENT_TOPICS, deployment.getDeploymentDocumentObj().getGroupName()); String lastDeploymentConfigArn = Coerce.toString(lastDeployment.find(GROUP_TO_LAST_DEPLOYMENT_CONFIG_ARN_KEY)); - updateDeploymentResultAsRejected(deployment, deploymentTask, new DeploymentRejectedException(String.format( - "Nucleus has a newer deployment for '%s' target deployed by '%s'. Rejecting the " - + "deployment from '%s'", deployment.getDeploymentDocumentObj().getGroupName(), - lastDeploymentConfigArn, deployment.getDeploymentDocumentObj().getConfigurationArn()), + updateDeploymentResultAsRejected(deployment, deploymentTask, new DeploymentRejectedException( + String.format( + "Nucleus has a newer deployment for '%s' target deployed by '%s'. Rejecting the " + + "deployment from '%s'", + deployment.getDeploymentDocumentObj().getGroupName(), lastDeploymentConfigArn, + deployment.getDeploymentDocumentObj().getConfigurationArn()), DeploymentErrorCode.REJECTED_STALE_DEPLOYMENT)); return; } else { @@ -611,21 +630,21 @@ private void createNewDeployment(Deployment deployment) { try { copyRecipesAndArtifacts(deployment); } catch (InvalidRequestException e) { - logger.atError().log("Error copying recipes and artifacts. " - + "Unable to parse the local deployment request", e); + logger.atError() + .log("Error copying recipes and artifacts. " + + "Unable to parse the local deployment request", e); updateDeploymentResultAsFailed(deployment, deploymentTask, false, e); return; } catch (IOException e) { logger.atError().log("Error copying recipes and artifacts", e); updateDeploymentResultAsFailed(deployment, deploymentTask, false, - new DeploymentException("Error copying recipes and artifacts", e) - .withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR)); + new DeploymentException("Error copying recipes and artifacts", e).withErrorContext(e, + DeploymentErrorCode.IO_WRITE_ERROR)); return; } } } - Future process = executorService.submit(deploymentTask); logger.atInfo().kv("deployment", deployment.getId()).log("Started deployment execution"); @@ -634,40 +653,36 @@ private void createNewDeployment(Deployment deployment) { } /* - * Enforce deployments are received for a given deployment target (thing or thingGroup) in sequence such - * that old deployments for that target does not override a new deployment. + * Enforce deployments are received for a given deployment target (thing or thingGroup) in sequence such that old + * deployments for that target does not override a new deployment. * - * For thing deployments, we don't consider them here as they are always in sequence and always for only - * one target. + * For thing deployments, we don't consider them here as they are always in sequence and always for only one target. * - * For thingGroup deployments sent to different targets (thingGroup A & B), nucleus allows components from - * both groups to be deployment as long as they don't have a conflicting component versions. This - * behavior is not changed. + * For thingGroup deployments sent to different targets (thingGroup A & B), nucleus allows components from both + * groups to be deployment as long as they don't have a conflicting component versions. This behavior is not + * changed. * - * For thingGroup deployments sent to the same target (thingGroup A) are always in sequence, however if - * receive a bad/stale deployment due to cloud error we don't want that stale deployment to override a - * new deployment already performed on device. + * For thingGroup deployments sent to the same target (thingGroup A) are always in sequence, however if receive a + * bad/stale deployment due to cloud error we don't want that stale deployment to override a new deployment already + * performed on device. * - * For a subgroup deployments targeted for a parent fleet group (subgroup A1, A2 & A3 targeted for - * thingGroup A), as there could be multiple subgroup deployments each of these sent as different jobs to - * the device could be received in any order yielding an unpredictable behavior. To resolve this, nucleus - * enforces processing these subgroup deployment in-order of their creation irrespective of when these - * signals are received. For example: + * For a subgroup deployments targeted for a parent fleet group (subgroup A1, A2 & A3 targeted for thingGroup A), as + * there could be multiple subgroup deployments each of these sent as different jobs to the device could be received + * in any order yielding an unpredictable behavior. To resolve this, nucleus enforces processing these subgroup + * deployment in-order of their creation irrespective of when these signals are received. For example: * - * Order of deployment creation is: A1, A2, A3 - * So these, have to be processed in this order. + * Order of deployment creation is: A1, A2, A3 So these, have to be processed in this order. * - * Order of deployments received: A2, A1, A3 - * then A2 and A3 deployment will succeed, but A1 would be rejected as nucleus has already processed - * newer deployment A2. + * Order of deployments received: A2, A1, A3 then A2 and A3 deployment will succeed, but A1 would be rejected as + * nucleus has already processed newer deployment A2. * * @return true if deployment is considered stale, false otherwise */ private boolean checkIfDeploymentReceivedIsStale(DeploymentDocument deploymentDocument, - DeploymentType deploymentType) { + DeploymentType deploymentType) { // Check if group deployment - boolean isGroupDeployment = Deployment.DeploymentType.IOT_JOBS.equals(deploymentType) - && deploymentDocument.getGroupName() != null; + boolean isGroupDeployment = + Deployment.DeploymentType.IOT_JOBS.equals(deploymentType) && deploymentDocument.getGroupName() != null; // if not a group deployment, then not stale if (!isGroupDeployment) { @@ -675,8 +690,8 @@ private boolean checkIfDeploymentReceivedIsStale(DeploymentDocument deploymentDo } // Get timestamp for the root target group - Topics lastDeployment = config - .lookupTopics(DeploymentService.GROUP_TO_LAST_DEPLOYMENT_TOPICS, deploymentDocument.getGroupName()); + Topics lastDeployment = config.lookupTopics(DeploymentService.GROUP_TO_LAST_DEPLOYMENT_TOPICS, + deploymentDocument.getGroupName()); long timestamp = Coerce.toLong(lastDeployment.find(GROUP_TO_LAST_DEPLOYMENT_TIMESTAMP_KEY)); @@ -690,7 +705,7 @@ private boolean checkIfDeploymentReceivedIsStale(DeploymentDocument deploymentDo } private void updateDeploymentResultAsRejected(Deployment deployment, DeploymentTask deploymentTask, - Throwable rejectionCause) { + Throwable rejectionCause) { DeploymentResult result = new DeploymentResult(DeploymentResult.DeploymentStatus.REJECTED, rejectionCause); @@ -701,7 +716,7 @@ private void updateDeploymentResultAsRejected(Deployment deployment, DeploymentT } private void updateDeploymentResultAsFailed(Deployment deployment, DeploymentTask deploymentTask, - boolean completeExceptionally, Throwable e) { + boolean completeExceptionally, Throwable e) { DeploymentResult result = new DeploymentResult(DeploymentStatus.FAILED_NO_STATE_CHANGE, e); CompletableFuture process; if (completeExceptionally) { @@ -715,7 +730,7 @@ private void updateDeploymentResultAsFailed(Deployment deployment, DeploymentTas } private void updateStatusDetailsFromException(Map statusDetails, Throwable failureCause, - DeploymentType deploymentType) { + DeploymentType deploymentType) { Pair, List> errorReport = DeploymentErrorCodeUtils.generateErrorReportFromExceptionStack(failureCause, deploymentType); statusDetails.put(DEPLOYMENT_ERROR_STACK_KEY, errorReport.getLeft()); @@ -735,8 +750,8 @@ private void copyRecipesAndArtifacts(Deployment deployment) throws InvalidReques } if (!Utils.isEmpty(localOverrideRequest.getArtifactsDirectoryPath())) { - Path kernelArtifactsDirectoryPath = kernel.getNucleusPaths().componentStorePath() - .resolve(ComponentStore.ARTIFACT_DIRECTORY); + Path kernelArtifactsDirectoryPath = + kernel.getNucleusPaths().componentStorePath().resolve(ComponentStore.ARTIFACT_DIRECTORY); Path artifactsDirectoryPath = Paths.get(localOverrideRequest.getArtifactsDirectoryPath()); try { Utils.copyFolderRecursively(artifactsDirectoryPath, kernelArtifactsDirectoryPath, @@ -762,14 +777,13 @@ private void copyRecipesAndArtifacts(Deployment deployment) throws InvalidReques return true; }, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - throw new IOException( - String.format("Unable to copy artifacts from %s due to: %s", artifactsDirectoryPath, - e.getMessage()), e); + throw new IOException(String.format("Unable to copy artifacts from %s due to: %s", + artifactsDirectoryPath, e.getMessage()), e); } } } catch (JsonProcessingException e) { - throw new InvalidRequestException("Unable to parse the local deployment request - Invalid JSON", - e, DeploymentType.LOCAL).withErrorContext(e, DeploymentErrorCode.DEPLOYMENT_DOCUMENT_PARSE_ERROR); + throw new InvalidRequestException("Unable to parse the local deployment request - Invalid JSON", e, + DeploymentType.LOCAL).withErrorContext(e, DeploymentErrorCode.DEPLOYMENT_DOCUMENT_PARSE_ERROR); } } @@ -793,26 +807,26 @@ private void copyRecipesToComponentStore(Path from) throws IOException { * @throws IOException on I/O error */ @SuppressWarnings("PMD.ExceptionAsFlowControl") - public static ComponentRecipe copyRecipeFileToComponentStore(ComponentStore componentStore, - Path recipePath, Logger logger) throws IOException { + public static ComponentRecipe copyRecipeFileToComponentStore(ComponentStore componentStore, Path recipePath, + Logger logger) throws IOException { String ext = Utils.extension(recipePath.toString()); ComponentRecipe recipe = null; - //reading it in as a recipe, so that will fail if it is malformed with a good error. - //The second reason to do this is to parse the name and version so that we can properly name - //the file when writing it into the local recipe store. + // reading it in as a recipe, so that will fail if it is malformed with a good error. + // The second reason to do this is to parse the name and version so that we can properly name + // the file when writing it into the local recipe store. try { if (recipePath.toFile().length() > 0) { switch (ext.toLowerCase()) { - case "yaml": - case "yml": - recipe = getRecipeSerializer().readValue(recipePath.toFile(), ComponentRecipe.class); - break; - case "json": - recipe = getRecipeSerializerJson().readValue(recipePath.toFile(), ComponentRecipe.class); - break; - default: - break; + case "yaml": + case "yml": + recipe = getRecipeSerializer().readValue(recipePath.toFile(), ComponentRecipe.class); + break; + case "json": + recipe = getRecipeSerializerJson().readValue(recipePath.toFile(), ComponentRecipe.class); + break; + default: + break; } } } catch (IOException e) { @@ -832,8 +846,7 @@ public static ComponentRecipe copyRecipeFileToComponentStore(ComponentStore comp new ComponentIdentifier(recipe.getComponentName(), recipe.getComponentVersion()); try { - componentStore - .savePackageRecipe(componentIdentifier, getRecipeSerializer().writeValueAsString(recipe)); + componentStore.savePackageRecipe(componentIdentifier, getRecipeSerializer().writeValueAsString(recipe)); } catch (PackageLoadingException e) { // Throw on error so that the user will receive this message and we will stop the deployment. // This is to fail fast while providing actionable feedback. @@ -848,17 +861,20 @@ private KernelUpdateDeploymentTask createKernelUpdateDeployment(Deployment deplo } @SuppressWarnings("PMD.AvoidCatchingGenericException") - //Catching generic exception here to make sure any exception while parsing deployment document will not cause - //deployment service to move to errored state. + // Catching generic exception here to make sure any exception while parsing deployment document will not cause + // deployment service to move to errored state. private DefaultDeploymentTask createDefaultNewDeployment(Deployment deployment) { try { - logger.atInfo().kv("document", deployment.getDeploymentDocument()) + logger.atInfo() + .kv("document", deployment.getDeploymentDocument()) .log("Received deployment document in queue"); parseAndValidateJobDocument(deployment); } catch (Exception e) { Map statusDetails = new HashMap<>(); updateStatusDetailsFromException(statusDetails, e, deployment.getDeploymentType()); - logger.atError().cause(e).kv(DEPLOYMENT_ID_LOG_KEY_NAME, deployment.getId()) + logger.atError() + .cause(e) + .kv(DEPLOYMENT_ID_LOG_KEY_NAME, deployment.getId()) .kv(GG_DEPLOYMENT_ID_LOG_KEY_NAME, deployment.getGreengrassDeploymentId()) .kv("DeploymentType", deployment.getDeploymentType().toString()) .kv(DEPLOYMENT_ERROR_STACK_KEY, statusDetails.get(DEPLOYMENT_ERROR_STACK_KEY)) @@ -884,48 +900,49 @@ private DeploymentDocument parseAndValidateJobDocument(Deployment deployment) th DeploymentDocument document; try { switch (deployment.getDeploymentType()) { - case LOCAL: - LocalOverrideRequest localOverrideRequest = SerializerFactory.getFailSafeJsonObjectMapper() - .readValue(jobDocumentString, LocalOverrideRequest.class); - Map rootComponents = new HashMap<>(); - Set rootComponentsInRequestedGroup = new HashSet<>(); - config.lookupTopics(GROUP_TO_ROOT_COMPONENTS_TOPICS, - localOverrideRequest.getGroupName() == null ? LOCAL_DEPLOYMENT_GROUP_NAME - : THING_GROUP_RESOURCE_NAME_PREFIX + localOverrideRequest.getGroupName()) - .forEach(t -> rootComponentsInRequestedGroup.add(t.getName())); - if (!Utils.isEmpty(rootComponentsInRequestedGroup)) { - rootComponentsInRequestedGroup.forEach(c -> { - Topics serviceTopic = kernel.findServiceTopic(c); - if (serviceTopic != null) { - String version = Coerce.toString(serviceTopic.find(VERSION_CONFIG_KEY)); - rootComponents.put(c, version); - } - }); - } - document = DeploymentDocumentConverter - .convertFromLocalOverrideRequestAndRoot(localOverrideRequest, rootComponents); - break; - case IOT_JOBS: - case SHADOW: - - // Note: This is the data contract that gets sending down from FCS::CreateDeployment - // Configuration is really a bad name choice as it is too generic but we can change it later - // since it is only a internal model - Configuration configuration = SerializerFactory.getFailSafeJsonObjectMapper() - .readValue(jobDocumentString, Configuration.class); - document = DeploymentDocumentConverter.convertFromDeploymentConfiguration(configuration); - - break; - default: - throw new IllegalArgumentException("Invalid deployment type: " + deployment.getDeploymentType()); + case LOCAL: + LocalOverrideRequest localOverrideRequest = SerializerFactory.getFailSafeJsonObjectMapper() + .readValue(jobDocumentString, LocalOverrideRequest.class); + Map rootComponents = new HashMap<>(); + Set rootComponentsInRequestedGroup = new HashSet<>(); + config.lookupTopics(GROUP_TO_ROOT_COMPONENTS_TOPICS, + localOverrideRequest.getGroupName() == null + ? LOCAL_DEPLOYMENT_GROUP_NAME + : THING_GROUP_RESOURCE_NAME_PREFIX + localOverrideRequest.getGroupName()) + .forEach(t -> rootComponentsInRequestedGroup.add(t.getName())); + if (!Utils.isEmpty(rootComponentsInRequestedGroup)) { + rootComponentsInRequestedGroup.forEach(c -> { + Topics serviceTopic = kernel.findServiceTopic(c); + if (serviceTopic != null) { + String version = Coerce.toString(serviceTopic.find(VERSION_CONFIG_KEY)); + rootComponents.put(c, version); + } + }); + } + document = DeploymentDocumentConverter.convertFromLocalOverrideRequestAndRoot(localOverrideRequest, + rootComponents); + break; + case IOT_JOBS: + case SHADOW: + + // Note: This is the data contract that gets sending down from FCS::CreateDeployment + // Configuration is really a bad name choice as it is too generic but we can change it later + // since it is only a internal model + Configuration configuration = SerializerFactory.getFailSafeJsonObjectMapper() + .readValue(jobDocumentString, Configuration.class); + document = DeploymentDocumentConverter.convertFromDeploymentConfiguration(configuration); + + break; + default: + throw new IllegalArgumentException("Invalid deployment type: " + deployment.getDeploymentType()); } } catch (JsonProcessingException e) { throw new InvalidRequestException("Unable to parse the deployment document", e, deployment.getDeploymentType()) .withErrorContext(e, DeploymentErrorCode.DEPLOYMENT_DOCUMENT_PARSE_ERROR); } catch (IllegalArgumentException e) { - throw new InvalidRequestException("Unable to parse the deployment document", e) - .withErrorContext(e, DeploymentErrorCode.DEPLOYMENT_TYPE_NOT_VALID); + throw new InvalidRequestException("Unable to parse the deployment document", e).withErrorContext(e, + DeploymentErrorCode.DEPLOYMENT_TYPE_NOT_VALID); } deployment.setDeploymentDocumentObj(document); return document; @@ -944,12 +961,9 @@ void setComponentsToGroupsMapping(Topics groupsToRootComponents) { Map componentsToGroupsMappingCache = new ConcurrentHashMap<>(); Topics componentsToGroupsTopics = getConfig().lookupTopics(COMPONENTS_TO_GROUPS_TOPICS); /* - * Structure of COMPONENTS_TO_GROUPS_TOPICS is: - * COMPONENTS_TO_GROUPS_TOPICS : - * |_ : - * |_ : - * This stores all the components with the list of deployment IDs associated to it along with the thing group - * (if available) to be associated to the deployment. + * Structure of COMPONENTS_TO_GROUPS_TOPICS is: COMPONENTS_TO_GROUPS_TOPICS : |_ : |_ + * : This stores all the components with the list of deployment IDs associated to it + * along with the thing group (if available) to be associated to the deployment. */ // Get all the groups associated to the root components. groupsToRootComponents.forEach(groupNode -> ((Topics) groupNode).forEach(componentNode -> { @@ -1053,7 +1067,7 @@ public boolean isComponentRoot(String componentName) { for (Node node : groupToRootComponentsTopics.children.values()) { if (node instanceof Topics) { Topics groupTopics = (Topics) node; - for (Node componentNode: groupTopics.children.values()) { + for (Node componentNode : groupTopics.children.values()) { if (componentNode instanceof Topics) { Topics componentTopics = (Topics) componentNode; if (componentName.equals(componentTopics.getName())) { diff --git a/src/main/java/com/aws/greengrass/deployment/DeploymentStatusKeeper.java b/src/main/java/com/aws/greengrass/deployment/DeploymentStatusKeeper.java index 14cf2fbba8..ffd307221a 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeploymentStatusKeeper.java +++ b/src/main/java/com/aws/greengrass/deployment/DeploymentStatusKeeper.java @@ -39,8 +39,8 @@ public class DeploymentStatusKeeper { public static final String DEPLOYMENT_STATUS_KEY_NAME = "DeploymentStatus"; public static final String DEPLOYMENT_STATUS_DETAILS_KEY_NAME = "DeploymentStatusDetails"; private static final Logger logger = LogManager.getLogger(DeploymentStatusKeeper.class); - private final Map, Boolean>>> deploymentStatusConsumerMap - = new ConcurrentHashMap<>(); + private final Map, Boolean>>> deploymentStatusConsumerMap = + new ConcurrentHashMap<>(); private static final Map lockMap = new DefaultConcurrentHashMap<>(() -> LockFactory.newReentrantLock("deploymentStatusKeeper")); @Setter @@ -50,16 +50,15 @@ public class DeploymentStatusKeeper { /** * Register call backs for receiving deployment status updates for a particular deployment type . * - * @param type determines which deployment type the call back consumes - * @param consumer deployment status details + * @param type determines which deployment type the call back consumes + * @param consumer deployment status details * @param serviceName subscribing service name * @return true if call back is registered. */ public boolean registerDeploymentStatusConsumer(DeploymentType type, - Function, Boolean> consumer, - String serviceName) { - Map, Boolean>> map = deploymentStatusConsumerMap - .getOrDefault(type, new ConcurrentHashMap<>()); + Function, Boolean> consumer, String serviceName) { + Map, Boolean>> map = + deploymentStatusConsumerMap.getOrDefault(type, new ConcurrentHashMap<>()); map.putIfAbsent(serviceName, consumer); return deploymentStatusConsumerMap.put(type, map) == null; } @@ -67,25 +66,28 @@ public boolean registerDeploymentStatusConsumer(DeploymentType type, /** * Persist deployment status in kernel config. * - * @param deploymentId id for the deployment - job id for jobs and config arn for shadow - * @param ggDeploymentId greengrass deployment id for the deployment from GG cloud + * @param deploymentId id for the deployment - job id for jobs and config arn for shadow + * @param ggDeploymentId greengrass deployment id for the deployment from GG cloud * @param configurationArn arn for deployment target configuration. - * @param deploymentType type of deployment. - * @param status status of deployment. - * @param statusDetails other details of deployment status. - * @param rootPackages root packages in the deployment. + * @param deploymentType type of deployment. + * @param status status of deployment. + * @param statusDetails other details of deployment status. + * @param rootPackages root packages in the deployment. * @throws IllegalArgumentException for invalid deployment type */ @SuppressWarnings("PMD.UseObjectForClearerAPI") public void persistAndPublishDeploymentStatus(String deploymentId, String ggDeploymentId, String configurationArn, - DeploymentType deploymentType, String status, - Map statusDetails, List rootPackages) { + DeploymentType deploymentType, String status, Map statusDetails, + List rootPackages) { - //While this method is being run, another thread could be running the publishPersistedStatusUpdates + // While this method is being run, another thread could be running the publishPersistedStatusUpdates // method which consumes the data in config from the same topics. These two thread needs to be synchronized try (LockScope ls = LockScope.lock(lockMap.get(deploymentType))) { - logger.atDebug().kv(GG_DEPLOYMENT_ID_KEY_NAME, ggDeploymentId).kv(DEPLOYMENT_ID_KEY_NAME, deploymentId) - .kv(DEPLOYMENT_STATUS_KEY_NAME, status).log("Storing deployment status"); + logger.atDebug() + .kv(GG_DEPLOYMENT_ID_KEY_NAME, ggDeploymentId) + .kv(DEPLOYMENT_ID_KEY_NAME, deploymentId) + .kv(DEPLOYMENT_STATUS_KEY_NAME, status) + .log("Storing deployment status"); Map deploymentDetails = new HashMap<>(); deploymentDetails.put(DEPLOYMENT_ID_KEY_NAME, deploymentId); deploymentDetails.put(GG_DEPLOYMENT_ID_KEY_NAME, ggDeploymentId); @@ -94,20 +96,23 @@ public void persistAndPublishDeploymentStatus(String deploymentId, String ggDepl deploymentDetails.put(DEPLOYMENT_STATUS_KEY_NAME, status); deploymentDetails.put(DEPLOYMENT_STATUS_DETAILS_KEY_NAME, statusDetails); deploymentDetails.put(DEPLOYMENT_ROOT_PACKAGES_KEY_NAME, rootPackages); - //Each status update is uniquely stored + // Each status update is uniquely stored Topics processedDeployments = getProcessedDeployments(); Topics thisJob = processedDeployments.createInteriorChild(String.valueOf(System.currentTimeMillis())); thisJob.replaceAndWait(deploymentDetails); - logger.atInfo().kv(GG_DEPLOYMENT_ID_KEY_NAME, ggDeploymentId).kv(DEPLOYMENT_ID_KEY_NAME, deploymentId) - .kv(DEPLOYMENT_STATUS_KEY_NAME, status).log("Stored deployment status"); + logger.atInfo() + .kv(GG_DEPLOYMENT_ID_KEY_NAME, ggDeploymentId) + .kv(DEPLOYMENT_ID_KEY_NAME, deploymentId) + .kv(DEPLOYMENT_STATUS_KEY_NAME, status) + .log("Stored deployment status"); } publishPersistedStatusUpdates(deploymentType); } /** - * Invokes the call-backs with persisted deployment status updates for deployments with specified type. - * This is called by IotJobsHelper/MqttJobsHelper when connection is re-established to update cloud of all - * all deployments the device performed when offline + * Invokes the call-backs with persisted deployment status updates for deployments with specified type. This is + * called by IotJobsHelper/MqttJobsHelper when connection is re-established to update cloud of all all deployments + * the device performed when offline * * @param type deployment type */ @@ -117,8 +122,8 @@ public void publishPersistedStatusUpdates(DeploymentType type) { ArrayList deployments = new ArrayList<>(); processedDeployments.forEach(node -> { Topics deploymentDetails = (Topics) node; - DeploymentType deploymentType = Coerce.toEnum(DeploymentType.class, deploymentDetails - .find(DEPLOYMENT_TYPE_KEY_NAME)); + DeploymentType deploymentType = + Coerce.toEnum(DeploymentType.class, deploymentDetails.find(DEPLOYMENT_TYPE_KEY_NAME)); if (Objects.equals(deploymentType, type)) { deployments.add(deploymentDetails); } @@ -137,11 +142,12 @@ public void publishPersistedStatusUpdates(DeploymentType type) { }).collect(Collectors.toList()); List, Boolean>> consumers = getConsumersForDeploymentType(type); - logger.atDebug().kv("deploymentType", type).kv("numberOfSubscribers", consumers.size()) + logger.atDebug() + .kv("deploymentType", type) + .kv("numberOfSubscribers", consumers.size()) .log("Updating status of persisted deployments to subscribers"); for (Topics topics : sortedByTimestamp) { - boolean allConsumersUpdated = consumers.stream() - .allMatch(consumer -> consumer.apply(topics.toPOJO())); + boolean allConsumersUpdated = consumers.stream().allMatch(consumer -> consumer.apply(topics.toPOJO())); if (!allConsumersUpdated) { // If one deployment update fails, exit the loop to ensure the update order. logger.atDebug().log("Unable to update status of persisted deployments. Retry later"); diff --git a/src/main/java/com/aws/greengrass/deployment/DeviceConfiguration.java b/src/main/java/com/aws/greengrass/deployment/DeviceConfiguration.java index fb07c06577..20d0c239a3 100644 --- a/src/main/java/com/aws/greengrass/deployment/DeviceConfiguration.java +++ b/src/main/java/com/aws/greengrass/deployment/DeviceConfiguration.java @@ -69,7 +69,9 @@ /** * Class for providing device configuration information. */ -@SuppressWarnings({"PMD.DataClass", "PMD.ExcessivePublicCount"}) +@SuppressWarnings({ + "PMD.DataClass", "PMD.ExcessivePublicCount" +}) @SuppressFBWarnings("IS2_INCONSISTENT_SYNC") public class DeviceConfiguration { public static final String DEFAULT_NUCLEUS_COMPONENT_NAME = "aws.greengrass.Nucleus"; @@ -170,23 +172,22 @@ public DeviceConfiguration(Configuration config, KernelCommandLine kernelCommand /** * Constructor to use when setting the device configuration to kernel config. * - * @param config Device configuration - * @param kernelCommandLine deTilde - * @param thingName IoT thing name - * @param iotDataEndpoint IoT data endpoint - * @param iotCredEndpoint IoT cert endpoint - * @param privateKeyPath private key location on device + * @param config Device configuration + * @param kernelCommandLine deTilde + * @param thingName IoT thing name + * @param iotDataEndpoint IoT data endpoint + * @param iotCredEndpoint IoT cert endpoint + * @param privateKeyPath private key location on device * @param certificateFilePath certificate location on device - * @param rootCaFilePath downloaded RootCA location on device - * @param awsRegion aws region for the device - * @param tesRoleAliasName aws region for the device + * @param rootCaFilePath downloaded RootCA location on device + * @param awsRegion aws region for the device + * @param tesRoleAliasName aws region for the device * @throws DeviceConfigurationException when the configuration parameters are not valid */ @SuppressWarnings("PMD.ExcessiveParameterList") public DeviceConfiguration(Configuration config, KernelCommandLine kernelCommandLine, String thingName, - String iotDataEndpoint, String iotCredEndpoint, String privateKeyPath, - String certificateFilePath, String rootCaFilePath, String awsRegion, - String tesRoleAliasName) throws DeviceConfigurationException { + String iotDataEndpoint, String iotCredEndpoint, String privateKeyPath, String certificateFilePath, + String rootCaFilePath, String awsRegion, String tesRoleAliasName) throws DeviceConfigurationException { this(config, kernelCommandLine); getThingName().withValue(thingName); getIotDataEndpoint().withValue(iotDataEndpoint); @@ -249,11 +250,12 @@ public Topics getStatusConfigurationTopics() { */ private String initNucleusComponentName() { Optional nucleusComponent = - config.lookupTopics(SERVICES_NAMESPACE_TOPIC).children.keySet().stream() + config.lookupTopics(SERVICES_NAMESPACE_TOPIC).children.keySet() + .stream() .filter(s -> ComponentType.NUCLEUS.name().equals(getComponentType(s.toString()))) .findAny(); - String nucleusComponentName = nucleusComponent.isPresent() ? nucleusComponent.get().toString() : - DEFAULT_NUCLEUS_COMPONENT_NAME; + String nucleusComponentName = + nucleusComponent.isPresent() ? nucleusComponent.get().toString() : DEFAULT_NUCLEUS_COMPONENT_NAME; // Initialize default/inferred required config if it doesn't exist initializeNucleusComponentConfig(nucleusComponentName); return nucleusComponentName; @@ -290,37 +292,39 @@ public void handleLoggingConfigurationChanges(WhatHappened what, Node node) { try (LockScope ls = LockScope.lock(lock)) { logger.atDebug().kv("logging-change-what", what).kv("logging-change-node", node).log(); switch (what) { - case initialized: - // fallthrough - case childChanged: - LogConfigUpdate logConfigUpdate; - try { - logConfigUpdate = fromPojo(loggingTopics.toPOJO()); - } catch (IllegalArgumentException e) { - logger.atError().kv("logging-config", loggingTopics).cause(e) - .log("Unable to parse logging config."); - return; - } - if (currentConfiguration == null || !currentConfiguration.equals(logConfigUpdate)) { - reconfigureLogging(logConfigUpdate); - } - break; - case childRemoved: - LogManager.resetAllLoggers(node.getName()); - break; - case removed: - LogManager.resetAllLoggers(null); - break; - default: - // do nothing - break; + case initialized: + // fallthrough + case childChanged: + LogConfigUpdate logConfigUpdate; + try { + logConfigUpdate = fromPojo(loggingTopics.toPOJO()); + } catch (IllegalArgumentException e) { + logger.atError() + .kv("logging-config", loggingTopics) + .cause(e) + .log("Unable to parse logging config."); + return; + } + if (currentConfiguration == null || !currentConfiguration.equals(logConfigUpdate)) { + reconfigureLogging(logConfigUpdate); + } + break; + case childRemoved: + LogManager.resetAllLoggers(node.getName()); + break; + case removed: + LogManager.resetAllLoggers(null); + break; + default: + // do nothing + break; } } } private void reconfigureLogging(LogConfigUpdate logConfigUpdate) { - if (logConfigUpdate.getOutputDirectory() != null && (currentConfiguration == null || !Objects - .equals(currentConfiguration.getOutputDirectory(), logConfigUpdate.getOutputDirectory()))) { + if (logConfigUpdate.getOutputDirectory() != null && (currentConfiguration == null + || !Objects.equals(currentConfiguration.getOutputDirectory(), logConfigUpdate.getOutputDirectory()))) { try { NucleusPaths.setLoggerPath(Paths.get(logConfigUpdate.getOutputDirectory())); } catch (IOException e) { @@ -363,12 +367,11 @@ private Validator getRegionValidator() { } config.lookup(SETENV_CONFIG_NAMESPACE, "AWS_DEFAULT_REGION").withValue(region); - config.lookup(SETENV_CONFIG_NAMESPACE, SdkSystemSetting.AWS_REGION.environmentVariable()) - .withValue(region); + config.lookup(SETENV_CONFIG_NAMESPACE, SdkSystemSetting.AWS_REGION.environmentVariable()).withValue(region); // Get the current FIPS mode for the AWS SDK. Default will be false (no FIPS). String useFipsMode = Boolean.toString(Coerce.toBoolean(getFipsMode())); - //Download CA3 to support iotDataEndpoint + // Download CA3 to support iotDataEndpoint if (Coerce.toBoolean(getFipsMode()) && !rootCA3Downloaded.get()) { rootCA3Downloaded.set(RootCAUtils.downloadRootCAsWithPath(Coerce.toString(getRootCAFilePath()), RootCAUtils.AMAZON_ROOT_CA_3_URL)); @@ -403,6 +406,7 @@ public Topic getRunWithDefaultWindowsUser() { /** * Find the RunWithDefault.SystemResourceLimits topics. + * * @return topics */ public Topics findRunWithDefaultSystemResourceLimits() { @@ -427,24 +431,24 @@ public Topics getPlatformOverrideTopic() { */ public Topic getThingName() { Topic thingNameTopic = config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_THING_NAME).dflt(""); - config.lookup(SETENV_CONFIG_NAMESPACE, AWS_IOT_THING_NAME_ENV) - .withValue(Coerce.toString(thingNameTopic)); + config.lookup(SETENV_CONFIG_NAMESPACE, AWS_IOT_THING_NAME_ENV).withValue(Coerce.toString(thingNameTopic)); return thingNameTopic; } public Topic getCertificateFilePath() { - return config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_CERTIFICATE_FILE_PATH).dflt("") + return config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_CERTIFICATE_FILE_PATH) + .dflt("") .addValidator(deTildeValidator); } public Topic getPrivateKeyFilePath() { - return config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_PRIVATE_KEY_PATH).dflt("") + return config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_PRIVATE_KEY_PATH) + .dflt("") .addValidator(deTildeValidator); } public Topic getRootCAFilePath() { - return config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_ROOT_CA_PATH).dflt("") - .addValidator(deTildeValidator); + return config.lookup(SYSTEM_NAMESPACE_KEY, DEVICE_PARAM_ROOT_CA_PATH).dflt("").addValidator(deTildeValidator); } public Topic getIpcSocketPath() { @@ -613,8 +617,8 @@ public boolean isDeviceConfiguredToTalkToCloud() { * * @param node what may have changed during device provisioning * @param checkThingNameOnly has initial setup has been done for a given service - * @return true if any device provisioning values have changed before initial service setup - * or if the thing name has changed after + * @return true if any device provisioning values have changed before initial service setup or if the thing name has + * changed after */ public static boolean provisionInfoNodeChanged(Node node, Boolean checkThingNameOnly) { if (checkThingNameOnly) { @@ -622,21 +626,19 @@ public static boolean provisionInfoNodeChanged(Node node, Boolean checkThingName } else { // List of configuration nodes that may change during device provisioning return node.childOf(DEVICE_PARAM_THING_NAME) || node.childOf(DEVICE_PARAM_IOT_DATA_ENDPOINT) - || node.childOf(DEVICE_PARAM_PRIVATE_KEY_PATH) - || node.childOf(DEVICE_PARAM_CERTIFICATE_FILE_PATH) || node.childOf(DEVICE_PARAM_ROOT_CA_PATH) - || node.childOf(DEVICE_PARAM_AWS_REGION); + || node.childOf(DEVICE_PARAM_PRIVATE_KEY_PATH) || node.childOf(DEVICE_PARAM_CERTIFICATE_FILE_PATH) + || node.childOf(DEVICE_PARAM_ROOT_CA_PATH) || node.childOf(DEVICE_PARAM_AWS_REGION); } } private Topic getTopic(String parameterName) { - return config - .lookup(SERVICES_NAMESPACE_TOPIC, getNucleusComponentName(), CONFIGURATION_CONFIG_KEY, parameterName); + return config.lookup(SERVICES_NAMESPACE_TOPIC, getNucleusComponentName(), CONFIGURATION_CONFIG_KEY, + parameterName); } private Topics getTopics(String parameterName) { - return config - .lookupTopics(SERVICES_NAMESPACE_TOPIC, getNucleusComponentName(), - CONFIGURATION_CONFIG_KEY, parameterName); + return config.lookupTopics(SERVICES_NAMESPACE_TOPIC, getNucleusComponentName(), CONFIGURATION_CONFIG_KEY, + parameterName); } /** @@ -659,8 +661,7 @@ public String getNucleusVersion() { } /** - * Get the Nucleus version from the ZIP file. - * Get the Nucleus version from the ZIP file + * Get the Nucleus version from the ZIP file. Get the Nucleus version from the ZIP file * * @return version from the zip file, or a default if the version can't be determined */ @@ -668,22 +669,22 @@ public static String getVersionFromBuildRecipeFile() { try { com.amazon.aws.iot.greengrass.component.common.ComponentRecipe recipe = getRecipeSerializer() .readValue(locateCurrentKernelUnpackDir().resolve(NUCLEUS_BUILD_METADATA_DIRECTORY) - .resolve(NUCLEUS_RECIPE_FILENAME).toFile(), - com.amazon.aws.iot.greengrass.component.common.ComponentRecipe.class); + .resolve(NUCLEUS_RECIPE_FILENAME) + .toFile(), com.amazon.aws.iot.greengrass.component.common.ComponentRecipe.class); if (recipe != null) { - return recipe.getComponentVersion().toString(); + return recipe.getComponentVersion().toString(); } } catch (IOException | URISyntaxException e) { logger.atError().log("Unable to determine Greengrass version", e); } - logger.atError().log("Unable to determine Greengrass version from build recipe file. " - + "Build file not found, or version not found in file. Falling back to {}", FALLBACK_VERSION); + logger.atError() + .log("Unable to determine Greengrass version from build recipe file. " + + "Build file not found, or version not found in file. Falling back to {}", FALLBACK_VERSION); return FALLBACK_VERSION; } private void validateDeviceConfiguration(String thingName, String certificateFilePath, String privateKeyPath, - String rootCAPath, String iotDataEndpoint, String iotCredEndpoint, - String awsRegion, boolean cloudOnly) + String rootCAPath, String iotDataEndpoint, String iotCredEndpoint, String awsRegion, boolean cloudOnly) throws DeviceConfigurationException { List errors = new ArrayList<>(); if (Utils.isEmpty(thingName)) { @@ -725,11 +726,11 @@ private void validateDeviceConfiguration(String thingName, String certificateFil * Validate the IoT credential and data endpoint with the provided AWS region. Currently it checks that the if the * endpoints are provided, then the AWS region should be a part of the URL. * - * @param awsRegion the provided AWS region. + * @param awsRegion the provided AWS region. * @param iotCredEndpoint the provided IoT credentials endpoint * @param iotDataEndpoint the providedIoT data endpoint * @throws ComponentConfigurationValidationException if the region is not valid or if the IoT endpoints do not have - * the AWS region as a part of its URL. + * the AWS region as a part of its URL. */ public void validateEndpoints(String awsRegion, String iotCredEndpoint, String iotDataEndpoint) throws ComponentConfigurationValidationException { @@ -742,13 +743,15 @@ public void validateEndpoints(String awsRegion, String iotCredEndpoint, String i && !iotCredEndpoint.contains(awsRegion)) { throw new ComponentConfigurationValidationException( String.format("IoT credential endpoint region %s does not match the AWS region %s of the device", - iotCredEndpoint, awsRegion), DeploymentErrorCode.IOT_CRED_ENDPOINT_FORMAT_NOT_VALID); + iotCredEndpoint, awsRegion), + DeploymentErrorCode.IOT_CRED_ENDPOINT_FORMAT_NOT_VALID); } if (Utils.isNotEmpty(iotDataEndpoint) && iotDataEndpoint.contains(AMAZON_DOMAIN_SEQUENCE) && !iotDataEndpoint.contains(awsRegion)) { throw new ComponentConfigurationValidationException( String.format("IoT data endpoint region %s does not match the AWS region %s of the device", - iotDataEndpoint, awsRegion), DeploymentErrorCode.IOT_DATA_ENDPOINT_FORMAT_NOT_VALID); + iotDataEndpoint, awsRegion), + DeploymentErrorCode.IOT_DATA_ENDPOINT_FORMAT_NOT_VALID); } } @@ -763,34 +766,34 @@ private LogConfigUpdate fromPojo(Map pojoMap) { LogConfigUpdate.LogConfigUpdateBuilder configUpdate = LogConfigUpdate.builder(); pojoMap.forEach((s, o) -> { switch (s) { - case "level": - configUpdate.level(Level.valueOf(Coerce.toString(o))); - break; - case "fileSizeKB": - configUpdate.fileSizeKB(Coerce.toLong(o)); - break; - case "totalLogsSizeKB": - configUpdate.totalLogsSizeKB(Coerce.toLong(o)); - break; - case "format": - configUpdate.format(LogFormat.valueOf(Coerce.toString(o))); - break; - case "outputDirectory": - configUpdate.outputDirectory(Coerce.toString(o)); - break; - case "outputType": - configUpdate.outputType(LogStore.valueOf(Coerce.toString(o))); - break; - default: - throw new IllegalArgumentException("Unexpected value: " + s); + case "level": + configUpdate.level(Level.valueOf(Coerce.toString(o))); + break; + case "fileSizeKB": + configUpdate.fileSizeKB(Coerce.toLong(o)); + break; + case "totalLogsSizeKB": + configUpdate.totalLogsSizeKB(Coerce.toLong(o)); + break; + case "format": + configUpdate.format(LogFormat.valueOf(Coerce.toString(o))); + break; + case "outputDirectory": + configUpdate.outputDirectory(Coerce.toString(o)); + break; + case "outputType": + configUpdate.outputType(LogStore.valueOf(Coerce.toString(o))); + break; + default: + throw new IllegalArgumentException("Unexpected value: " + s); } }); return configUpdate.build(); } /* - * shadow manager plugin depends on this directly - * shadow manager, cda, and ggdcm depends on getConfiguredClientBuilder in ClientConfigurationUtils calls this + * shadow manager plugin depends on this directly shadow manager, cda, and ggdcm depends on + * getConfiguredClientBuilder in ClientConfigurationUtils calls this */ public KeyManager[] getDeviceIdentityKeyManagers() throws TLSAuthException { return securityService.getDeviceIdentityKeyManagers(); @@ -804,12 +807,12 @@ public Topics getHttpClientOptions() { * Set device config based on existing System property. */ private void handleExistingSystemProperty() { - //handle s3 endpoint type + // handle s3 endpoint type if (System.getProperty(S3_ENDPOINT_PROP_NAME) != null && System.getProperty(S3_ENDPOINT_PROP_NAME).equalsIgnoreCase(S3EndpointType.REGIONAL.name())) { gets3EndpointType().withValue(S3EndpointType.REGIONAL.name()); } - //handle fips mode + // handle fips mode String useFipsMode = System.getProperty(SdkSystemSetting.AWS_USE_FIPS_ENDPOINT.property()); if (Coerce.toBoolean(useFipsMode)) { getFipsMode().withValue(useFipsMode); diff --git a/src/main/java/com/aws/greengrass/deployment/DynamicComponentConfigurationValidator.java b/src/main/java/com/aws/greengrass/deployment/DynamicComponentConfigurationValidator.java index 8a932e72b2..908c817eb8 100644 --- a/src/main/java/com/aws/greengrass/deployment/DynamicComponentConfigurationValidator.java +++ b/src/main/java/com/aws/greengrass/deployment/DynamicComponentConfigurationValidator.java @@ -64,21 +64,21 @@ public class DynamicComponentConfigurationValidator { /** * Dynamically validate proposed configuration for a deployment. * - * @param servicesConfig aggregate configuration map for services proposed by the deployment - * @param deployment deployment context + * @param servicesConfig aggregate configuration map for services proposed by the deployment + * @param deployment deployment context * @param deploymentResultFuture deployment result future, completed with failure result when validation fails * @return if all component processes reported that their proposed configuration is valid */ public boolean validate(Map servicesConfig, Deployment deployment, - CompletableFuture deploymentResultFuture) { + CompletableFuture deploymentResultFuture) { logger.addDefaultKeyValue(DEPLOYMENT_ID_LOG_KEY, deployment.getGreengrassDeploymentId()); Set componentsToValidate; try { componentsToValidate = getComponentsToValidate(servicesConfig, deployment.getDeploymentDocumentObj().getTimestamp()); } catch (InvalidConfigFormatException e) { - deploymentResultFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, + deploymentResultFuture + .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, new ComponentConfigurationValidationException(e, DeploymentErrorCode.COMPONENT_CONFIGURATION_NOT_VALID))); return false; @@ -121,8 +121,9 @@ private Set getComponentsToValidate(Map ser Map proposedServiceConfig = (Map) serviceConfig; if (!willChildTopicChange(proposedServiceConfig, currentServiceConfig, VERSION_CONFIG_KEY, - proposedTimestamp) && willChildTopicsChange(proposedServiceConfig, currentServiceConfig, - CONFIGURATION_CONFIG_KEY, proposedTimestamp)) { + proposedTimestamp) + && willChildTopicsChange(proposedServiceConfig, currentServiceConfig, CONFIGURATION_CONFIG_KEY, + proposedTimestamp)) { componentsToValidate.add(new ComponentToValidate(serviceName, (Map) proposedServiceConfig.get(CONFIGURATION_CONFIG_KEY))); } @@ -131,7 +132,7 @@ private Set getComponentsToValidate(Map ser } private boolean willChildTopicsChange(Map proposedServiceConfig, Topics currentServiceConfig, - String key, long proposedTimestamp) throws InvalidConfigFormatException { + String key, long proposedTimestamp) throws InvalidConfigFormatException { Object proposed = proposedServiceConfig.get(key); Topics current = currentServiceConfig.findTopics(key); // If both are null then there is no change @@ -148,18 +149,19 @@ private boolean willChildTopicsChange(Map proposedServiceConfig, } private boolean willChildTopicChange(Map proposedServiceConfig, Topics currentServiceConfig, - String key, long proposedTimestamp) { + String key, long proposedTimestamp) { return willNodeChange(proposedServiceConfig.get(key), currentServiceConfig.findNode(key), proposedTimestamp); } private boolean willNodeChange(Object proposedConfig, Node currentConfig, long proposedTimestamp) { - return Objects.isNull(currentConfig) ? Objects.nonNull(proposedConfig) - : proposedTimestamp > currentConfig.getModtime() && !Objects - .deepEquals(proposedConfig, currentConfig.toPOJO()); + return Objects.isNull(currentConfig) + ? Objects.nonNull(proposedConfig) + : proposedTimestamp > currentConfig.getModtime() + && !Objects.deepEquals(proposedConfig, currentConfig.toPOJO()); } private boolean validateOverIpc(Deployment deployment, Set componentsToValidate, - CompletableFuture deploymentResultFuture) { + CompletableFuture deploymentResultFuture) { String deploymentId = deployment.getId(); Integer timeoutSec = deployment.getDeploymentDocumentObj().getConfigurationValidationPolicy().timeoutInSeconds(); @@ -173,10 +175,8 @@ private boolean validateOverIpc(Deployment deployment, Set boolean valid = true; for (ComponentToValidate componentToValidate : componentsToValidate) { try { - if (configStoreIPCEventStreamAgent - .validateConfiguration(componentToValidate.componentName, deploymentId, - componentToValidate.configuration, - componentToValidate.response)) { + if (configStoreIPCEventStreamAgent.validateConfiguration(componentToValidate.componentName, + deploymentId, componentToValidate.configuration, componentToValidate.response)) { validationRequested = true; } // Do nothing if service has not subscribed for validation @@ -189,9 +189,10 @@ private boolean validateOverIpc(Deployment deployment, Set } if (validationRequested) { try { - CompletableFuture.allOf(componentsToValidate.stream().map(ComponentToValidate::getResponse) - .collect(Collectors.toSet()).toArray(new CompletableFuture[0])) - .get(timeoutMs, TimeUnit.MILLISECONDS); + CompletableFuture.allOf(componentsToValidate.stream() + .map(ComponentToValidate::getResponse) + .collect(Collectors.toSet()) + .toArray(new CompletableFuture[0])).get(timeoutMs, TimeUnit.MILLISECONDS); failureMsg = "Components reported that their to-be-deployed configuration is invalid"; for (ComponentToValidate componentToValidate : componentsToValidate) { @@ -204,7 +205,8 @@ private boolean validateOverIpc(Deployment deployment, Set if (ConfigurationValidityStatus.REJECTED.equals(report.getStatus())) { failureMsg = String.format("%s { name = %s, message = %s }", failureMsg, componentToValidate.componentName, report.getMessage()); - logger.atError().kv("component", componentToValidate.componentName) + logger.atError() + .kv("component", componentToValidate.componentName) .kv("message", report.getMessage()) .log("Component reported that its to-be-deployed configuration is invalid"); valid = false; @@ -219,8 +221,8 @@ private boolean validateOverIpc(Deployment deployment, Set } } if (!valid) { - deploymentResultFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, + deploymentResultFuture + .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, new ComponentConfigurationValidationException(failureMsg, DeploymentErrorCode.COMPONENT_CONFIGURATION_NOT_VALID))); } diff --git a/src/main/java/com/aws/greengrass/deployment/IotJobsClientWrapper.java b/src/main/java/com/aws/greengrass/deployment/IotJobsClientWrapper.java index 285e83a87a..6033745f18 100644 --- a/src/main/java/com/aws/greengrass/deployment/IotJobsClientWrapper.java +++ b/src/main/java/com/aws/greengrass/deployment/IotJobsClientWrapper.java @@ -40,33 +40,28 @@ @SuppressWarnings("PMD.AvoidCatchingGenericException") @SuppressFBWarnings("NM_METHOD_NAMING_CONVENTION") public class IotJobsClientWrapper extends IotJobsClient { - private static final String UPDATE_JOB_TOPIC = - "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/update"; + private static final String UPDATE_JOB_TOPIC = "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/update"; static final String JOB_UPDATE_ACCEPTED_TOPIC = "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/update/accepted"; static final String JOB_UPDATE_REJECTED_TOPIC = "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/update/rejected"; - private static final String DESCRIBE_JOB_TOPIC = - "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/get"; - static final String JOB_DESCRIBE_ACCEPTED_TOPIC = - "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/get/accepted"; - static final String JOB_DESCRIBE_REJECTED_TOPIC = - "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/get/rejected"; - static final String JOB_EXECUTIONS_CHANGED_TOPIC = - "$aws/things/%s/jobs/notify-namespace-aws-gg-deployment"; + private static final String DESCRIBE_JOB_TOPIC = "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/get"; + static final String JOB_DESCRIBE_ACCEPTED_TOPIC = "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/get/accepted"; + static final String JOB_DESCRIBE_REJECTED_TOPIC = "$aws/things/%s/jobs/%s/namespace-aws-gg-deployment/get/rejected"; + static final String JOB_EXECUTIONS_CHANGED_TOPIC = "$aws/things/%s/jobs/notify-namespace-aws-gg-deployment"; private final MqttClientConnection connection; private final Gson gson = this.getGson(); - private final Map, Consumer>, Consumer> - updateJobExecutionCbs = new ConcurrentHashMap<>(); - private final Map, Consumer>, Consumer> - updateJobExecutionSubscriptionCbs = new ConcurrentHashMap<>(); - private final Map, Consumer>, Consumer> - describeJobCbs = new ConcurrentHashMap<>(); - private final Map, Consumer>, Consumer> - describeJobSubscriptionCbs = new ConcurrentHashMap<>(); - private final Map, Consumer>, Consumer> - jobExecutionCbs = new ConcurrentHashMap<>(); + private final Map, Consumer>, Consumer> updateJobExecutionCbs = + new ConcurrentHashMap<>(); + private final Map, Consumer>, Consumer> updateJobExecutionSubscriptionCbs = + new ConcurrentHashMap<>(); + private final Map, Consumer>, Consumer> describeJobCbs = + new ConcurrentHashMap<>(); + private final Map, Consumer>, Consumer> describeJobSubscriptionCbs = + new ConcurrentHashMap<>(); + private final Map, Consumer>, Consumer> jobExecutionCbs = + new ConcurrentHashMap<>(); public IotJobsClientWrapper(MqttClientConnection connection) { super(connection); @@ -89,11 +84,11 @@ private void addTypeAdapters(GsonBuilder gson) { @Override public CompletableFuture PublishUpdateJobExecution(UpdateJobExecutionRequest request, - QualityOfService qos) { + QualityOfService qos) { if (request.thingName == null || request.jobId == null) { CompletableFuture result = new CompletableFuture(); - result.completeExceptionally(new MqttException( - "UpdateJobExecutionRequest must have a non-null thingName and a non-null jobId")); + result.completeExceptionally( + new MqttException("UpdateJobExecutionRequest must have a non-null thingName and a non-null jobId")); return result; } String topic = String.format(UPDATE_JOB_TOPIC, request.thingName, request.jobId); @@ -157,7 +152,7 @@ public CompletableFuture SubscribeToUpdateJobExecutionRejected( @Override public CompletableFuture PublishDescribeJobExecution(DescribeJobExecutionRequest request, - QualityOfService qos) { + QualityOfService qos) { if (request.thingName == null || request.jobId == null) { CompletableFuture result = new CompletableFuture(); result.completeExceptionally(new MqttException( @@ -232,8 +227,8 @@ public CompletableFuture SubscribeToJobExecutionsChangedEvents( if (request.thingName == null) { CompletableFuture result = new CompletableFuture(); - result.completeExceptionally(new MqttException( - "JobExecutionsChangedSubscriptionRequest must have a non-null thingName")); + result.completeExceptionally( + new MqttException("JobExecutionsChangedSubscriptionRequest must have a non-null thingName")); return result; } diff --git a/src/main/java/com/aws/greengrass/deployment/IotJobsHelper.java b/src/main/java/com/aws/greengrass/deployment/IotJobsHelper.java index 1102a01de7..b134bbba53 100644 --- a/src/main/java/com/aws/greengrass/deployment/IotJobsHelper.java +++ b/src/main/java/com/aws/greengrass/deployment/IotJobsHelper.java @@ -84,8 +84,8 @@ public class IotJobsHelper implements InjectionActions { public static final String UPDATE_DEPLOYMENT_STATUS_MQTT_ERROR_LOG = "Caught exception while updating job status"; public static final String UPDATE_DEPLOYMENT_STATUS_ACCEPTED = "Job status update was accepted"; public static final String STATUS_LOG_KEY_NAME = "Status"; - public static final String DEVICE_OFFLINE_MESSAGE = "Device not configured to talk to AWS Iot cloud. IOT job " - + "deployment is offline"; + public static final String DEVICE_OFFLINE_MESSAGE = + "Device not configured to talk to AWS Iot cloud. IOT job " + "deployment is offline"; public static final String SUBSCRIBING_TO_TOPICS_MESSAGE = "Subscribing to Iot Jobs Topics"; protected static final String SUBSCRIPTION_JOB_DESCRIPTION_RETRY_MESSAGE = "No connection available during subscribing to Iot Jobs descriptions topic. Will retry in sometime"; @@ -153,11 +153,9 @@ public class IotJobsHelper implements InjectionActions { private final Consumer eventHandler = event -> { /* - * This message is received when either of these things happen - * 1. Last job completed (successful/failed) - * 2. A new job was queued - * 3. A job was cancelled - * This message receives the list of Queued and InProgress jobs at the time of this message + * This message is received when either of these things happen 1. Last job completed (successful/failed) 2. A + * new job was queued 3. A job was cancelled This message receives the list of Queued and InProgress jobs at the + * time of this message */ Map> jobs = event.jobs; if (jobs.containsKey(JobStatus.QUEUED)) { @@ -165,8 +163,8 @@ public class IotJobsHelper implements InjectionActions { // each new job QUEUED. unprocessedJobs.incrementAndGet(); logger.atInfo().log("Received new deployment notification. Requesting details"); - //Do not wait on the future in this async handler, - //as it will block the thread which establishes + // Do not wait on the future in this async handler, + // as it will block the thread which establishes // the MQTT connection. This will result in frozen MQTT connection requestNextPendingJobDocument(); return; @@ -180,9 +178,8 @@ public class IotJobsHelper implements InjectionActions { logger.atInfo().kv("jobs", jobs).log("Received other deployment notification. Not supported yet"); }; /** - * Handler that gets invoked when a job description is received. - * Next pending job description is requested when an mqtt message - * is published using {@code requestNextPendingJobDocument} in {@link IotJobsHelper} + * Handler that gets invoked when a job description is received. Next pending job description is requested when an + * mqtt message is published using {@code requestNextPendingJobDocument} in {@link IotJobsHelper} */ private final Consumer describeJobExecutionResponseConsumer = response -> { if (response.execution == null) { @@ -199,10 +196,14 @@ public class IotJobsHelper implements InjectionActions { } JobExecutionData jobExecutionData = response.execution; - logger.atInfo().kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId).kv(STATUS_LOG_KEY_NAME, jobExecutionData.status) - .kv("queueAt", jobExecutionData.queuedAt).log("Received Iot job description"); + logger.atInfo() + .kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId) + .kv(STATUS_LOG_KEY_NAME, jobExecutionData.status) + .kv("queueAt", jobExecutionData.queuedAt) + .log("Received Iot job description"); if (!latestQueuedJobs.addNewJobIfAbsent(jobExecutionData.queuedAt.toInstant(), jobExecutionData.jobId)) { - logger.atInfo().kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId) + logger.atInfo() + .kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId) .log("Duplicate or outdated job notification. Ignoring."); return; } @@ -213,7 +214,9 @@ public class IotJobsHelper implements InjectionActions { SerializerFactory.getFailSafeJsonObjectMapper().writeValueAsString(jobExecutionData.jobDocument); } catch (JsonProcessingException e) { // This should not happen as we are converting a HashMap - logger.atError().kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId).setCause(e) + logger.atError() + .kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId) + .setCause(e) .log("Failed to serialize job document"); return; } @@ -226,8 +229,7 @@ public class IotJobsHelper implements InjectionActions { evaluateCancellationAndCancelDeploymentIfNeeded(); // Add the job queued in cloud to the deployment queue so it's picked up on its turn - Deployment deployment = - new Deployment(documentString, DeploymentType.IOT_JOBS, jobExecutionData.jobId); + Deployment deployment = new Deployment(documentString, DeploymentType.IOT_JOBS, jobExecutionData.jobId); if (deploymentQueue.offer(deployment)) { logger.atInfo().kv(JOB_ID_LOG_KEY_NAME, jobExecutionData.jobId).log("Added the job to the queue"); @@ -251,15 +253,10 @@ public void onConnectionResumed(boolean sessionPresent) { /** * Constructor for unit testing. */ - IotJobsHelper(DeviceConfiguration deviceConfiguration, - IotJobsClientFactory iotJobsClientFactory, - DeploymentQueue deploymentQueue, - DeploymentStatusKeeper deploymentStatusKeeper, - ExecutorService executorService, - Kernel kernel, - WrapperMqttConnectionFactory wrapperMqttConnectionFactory, - MqttClient mqttClient, - FleetStatusService fleetStatusService) { + IotJobsHelper(DeviceConfiguration deviceConfiguration, IotJobsClientFactory iotJobsClientFactory, + DeploymentQueue deploymentQueue, DeploymentStatusKeeper deploymentStatusKeeper, + ExecutorService executorService, Kernel kernel, WrapperMqttConnectionFactory wrapperMqttConnectionFactory, + MqttClient mqttClient, FleetStatusService fleetStatusService) { this.deviceConfiguration = deviceConfiguration; this.iotJobsClientFactory = iotJobsClientFactory; this.deploymentQueue = deploymentQueue; @@ -329,19 +326,20 @@ private void setupCommWithIotJobs(Boolean isConfigurationUpdate) { } // only one consumer per service name will be registered - deploymentStatusKeeper.registerDeploymentStatusConsumer(DeploymentType.IOT_JOBS, - this::deploymentStatusChanged, IotJobsHelper.class.getName()); - logger.dfltKv("ThingName", (Supplier) () -> - Coerce.toString(deviceConfiguration.getThingName())); + deploymentStatusKeeper.registerDeploymentStatusConsumer(DeploymentType.IOT_JOBS, this::deploymentStatusChanged, + IotJobsHelper.class.getName()); + logger.dfltKv("ThingName", (Supplier) () -> Coerce.toString(deviceConfiguration.getThingName())); this.thingName = Coerce.toString(deviceConfiguration.getThingName()); if (subscriptionFuture == null || subscriptionFuture.isDone()) { subscriptionFuture = executorService.submit(() -> { try { // Wait for all node updates to come through before we subscribe to the topics - Throwable ex = kernel.getContext().runOnPublishQueueAndWait(() -> {}); + Throwable ex = kernel.getContext().runOnPublishQueueAndWait(() -> { + }); if (ex instanceof InterruptedException) { - logger.atDebug().log("Got interrupted while waiting for publish queue to clear, during Iot " - + "Jobs subscriptions"); + logger.atDebug() + .log("Got interrupted while waiting for publish queue to clear, during Iot " + + "Jobs subscriptions"); return; } subscribeToJobsTopics(); @@ -375,43 +373,53 @@ private Boolean deploymentStatusChanged(Map deploymentDetails) { } } }); - logger.atInfo().kv(JOB_ID_LOG_KEY_NAME, jobId).kv(STATUS_LOG_KEY_NAME, status) - .kv("StatusDetails", statusDetails).log("Updating status of persisted deployment"); + logger.atInfo() + .kv(JOB_ID_LOG_KEY_NAME, jobId) + .kv(STATUS_LOG_KEY_NAME, status) + .kv("StatusDetails", statusDetails) + .log("Updating status of persisted deployment"); try { updateJobStatus(jobId, JobStatus.valueOf(status), jobStatusDetails); return true; } catch (ExecutionException e) { if (e.getCause() instanceof MqttException) { - //caused due to connectivity issue - logger.atError().setCause(e).kv(STATUS_LOG_KEY_NAME, status) + // caused due to connectivity issue + logger.atError() + .setCause(e) + .kv(STATUS_LOG_KEY_NAME, status) .log(UPDATE_DEPLOYMENT_STATUS_MQTT_ERROR_LOG); return false; } // This happens when job status update gets rejected from the Iot Cloud // Want to remove this job from the list and continue updating others - logger.atError().kv(STATUS_LOG_KEY_NAME, status).kv(JOB_ID_LOG_KEY_NAME, jobId).setCause(e) + logger.atError() + .kv(STATUS_LOG_KEY_NAME, status) + .kv(JOB_ID_LOG_KEY_NAME, jobId) + .setCause(e) .log("Job status update rejected"); return true; } catch (TimeoutException e) { // assuming this is due to network issue logger.info(UPDATE_DEPLOYMENT_STATUS_TIMEOUT_ERROR_LOG); } catch (InterruptedException e) { - logger.atError().kv(JOB_ID_LOG_KEY_NAME, jobId).kv(STATUS_LOG_KEY_NAME, status) + logger.atError() + .kv(JOB_ID_LOG_KEY_NAME, jobId) + .kv(STATUS_LOG_KEY_NAME, status) .log("Got interrupted while updating the job status"); } return false; } /** - * Subscribes to the topic which receives confirmation message of Job update for a given JobId. - * Updates the status of an Iot Job with given JobId to a given status. + * Subscribes to the topic which receives confirmation message of Job update for a given JobId. Updates the status + * of an Iot Job with given JobId to a given status. * - * @param jobId The jobId to be updated - * @param status The {@link JobStatus} to which to update + * @param jobId The jobId to be updated + * @param status The {@link JobStatus} to which to update * @param statusDetailsMap map with job status details - * @throws ExecutionException if update fails + * @throws ExecutionException if update fails * @throws InterruptedException if the thread gets interrupted - * @throws TimeoutException if the operation does not complete within the given time + * @throws TimeoutException if the operation does not complete within the given time */ @SuppressWarnings("PMD.LooseCoupling") public void updateJobStatus(String jobId, JobStatus status, HashMap statusDetailsMap) @@ -422,14 +430,18 @@ public void updateJobStatus(String jobId, JobStatus status, HashMap gotResponse = new CompletableFuture<>(); iotJobsClientWrapper.SubscribeToUpdateJobExecutionAccepted(subscriptionRequest, QualityOfService.AT_LEAST_ONCE, (response) -> { - logger.atInfo().kv(JOB_ID_LOG_KEY_NAME, jobId).kv(STATUS_LOG_KEY_NAME, status) + logger.atInfo() + .kv(JOB_ID_LOG_KEY_NAME, jobId) + .kv(STATUS_LOG_KEY_NAME, status) .log(UPDATE_DEPLOYMENT_STATUS_ACCEPTED); gotResponse.complete(null); }); iotJobsClientWrapper.SubscribeToUpdateJobExecutionRejected(subscriptionRequest, QualityOfService.AT_LEAST_ONCE, (response) -> { - logger.atWarn().kv(JOB_ID_LOG_KEY_NAME, jobId).kv(STATUS_LOG_KEY_NAME, status) + logger.atWarn() + .kv(JOB_ID_LOG_KEY_NAME, jobId) + .kv(STATUS_LOG_KEY_NAME, status) .log("Job status updated rejected"); gotResponse.completeExceptionally(new Exception(response.message)); }); @@ -464,15 +476,15 @@ public void updateJobStatus(String jobId, JobStatus status, HashMap consumerAccept, - Consumer consumerReject) - throws InterruptedException { + Consumer consumerReject) throws InterruptedException { logger.atDebug().log("Subscribing to deployment job execution update."); DescribeJobExecutionSubscriptionRequest describeJobExecutionSubscriptionRequest = @@ -529,14 +539,12 @@ protected void subscribeToGetNextJobDescription(Consumer subscribed; try { - subscribed = iotJobsClientWrapper - .SubscribeToDescribeJobExecutionAccepted(describeJobExecutionSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, consumerAccept); + subscribed = iotJobsClientWrapper.SubscribeToDescribeJobExecutionAccepted( + describeJobExecutionSubscriptionRequest, QualityOfService.AT_LEAST_ONCE, consumerAccept); subscribed.get(mqttClient.getMqttOperationTimeoutMillis(), TimeUnit.MILLISECONDS); - subscribed = iotJobsClientWrapper - .SubscribeToDescribeJobExecutionRejected(describeJobExecutionSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, consumerReject); + subscribed = iotJobsClientWrapper.SubscribeToDescribeJobExecutionRejected( + describeJobExecutionSubscriptionRequest, QualityOfService.AT_LEAST_ONCE, consumerReject); subscribed.get(mqttClient.getMqttOperationTimeoutMillis(), TimeUnit.MILLISECONDS); logger.atDebug().log("Subscribed to deployment job execution update."); @@ -572,12 +580,10 @@ protected void subscribeToGetNextJobDescription(Consumer eventHandler) throws InterruptedException { @@ -635,8 +641,7 @@ protected void subscribeToEventNotifications(Consumer private void unsubscribeFromEventNotifications() { if (connection != null) { - String topic = String.format(JOB_EXECUTIONS_CHANGED_TOPIC, - Coerce.toString(this.thingName)); + String topic = String.format(JOB_EXECUTIONS_CHANGED_TOPIC, Coerce.toString(this.thingName)); connection.unsubscribe(topic); } } @@ -687,7 +692,7 @@ static class LatestQueuedJobs { * Track IoT jobs with the latest timestamp. * * @param queueAt QueueAt timestamp in IoT Job Execution Data - * @param jobId IoT job ID + * @param jobId IoT job ID * @return true if IoT job with the given ID is a new job yet to be processed, false otherwise */ public boolean addNewJobIfAbsent(Instant queueAt, String jobId) { diff --git a/src/main/java/com/aws/greengrass/deployment/KernelUpdateDeploymentTask.java b/src/main/java/com/aws/greengrass/deployment/KernelUpdateDeploymentTask.java index 4efe62a165..e16fb22e7c 100644 --- a/src/main/java/com/aws/greengrass/deployment/KernelUpdateDeploymentTask.java +++ b/src/main/java/com/aws/greengrass/deployment/KernelUpdateDeploymentTask.java @@ -55,13 +55,13 @@ public class KernelUpdateDeploymentTask implements DeploymentTask { /** * Constructor for DefaultDeploymentTask. * - * @param kernel Kernel instance - * @param logger Logger instance - * @param deployment Deployment instance + * @param kernel Kernel instance + * @param logger Logger instance + * @param deployment Deployment instance * @param componentManager ComponentManager instance */ public KernelUpdateDeploymentTask(Kernel kernel, Logger logger, Deployment deployment, - ComponentManager componentManager) { + ComponentManager componentManager) { this.kernel = kernel; this.deployment = deployment; this.logger = logger.dfltKv(DEPLOYMENT_ID_LOG_KEY, deployment.getGreengrassDeploymentId()); @@ -70,7 +70,9 @@ public KernelUpdateDeploymentTask(Kernel kernel, Logger logger, Deployment deplo this.loaderLogsPath = kernel.getNucleusPaths().loaderLogsPath(); } - @SuppressWarnings({"PMD.AvoidDuplicateLiterals"}) + @SuppressWarnings({ + "PMD.AvoidDuplicateLiterals" + }) @Override public DeploymentResult call() { kernel.getContext().get(ExecutorService.class).execute(this::waitForServicesToStart); @@ -93,7 +95,9 @@ private void waitForServicesToStart() { Set servicesToTrack = kernel.findAutoStartableServicesToTrack(); long mergeTimestamp = kernel.getConfig().lookup("system", "rootpath").getModtime(); - logger.atInfo().kv("serviceToTrack", servicesToTrack).kv("mergeTime", mergeTimestamp) + logger.atInfo() + .kv("serviceToTrack", servicesToTrack) + .kv("mergeTime", mergeTimestamp) .log("Nucleus update workflow waiting for services to complete update"); DeploymentConfigMerger.waitForServicesToStart(servicesToTrack, mergeTimestamp, kernel, deploymentResultCompletableFuture); @@ -158,39 +162,42 @@ private void saveDeploymentStatusDetails(Throwable failureCause) throws IOExcept private DeploymentException getDeploymentStatusDetails() { if (Utils.isEmpty(deployment.getStageDetails())) { try { - if (Files.deleteIfExists( - kernel.getNucleusPaths().workPath(DEFAULT_NUCLEUS_COMPONENT_NAME) - .resolve(RESTART_PANIC_FILE_NAME).toAbsolutePath())) { + if (Files.deleteIfExists(kernel.getNucleusPaths() + .workPath(DEFAULT_NUCLEUS_COMPONENT_NAME) + .resolve(RESTART_PANIC_FILE_NAME) + .toAbsolutePath())) { String loaderLogs; try { loaderLogs = new String(Files.readAllBytes(this.loaderLogsPath), StandardCharsets.UTF_8); return new DeploymentException( - String.format("Nucleus update workflow failed to restart Nucleus.%n%s", - LoaderLogsSummarizer.summarizeLogs(loaderLogs)), - DeploymentErrorCode.NUCLEUS_RESTART_FAILURE); + String.format("Nucleus update workflow failed to restart Nucleus.%n%s", + LoaderLogsSummarizer.summarizeLogs(loaderLogs)), + DeploymentErrorCode.NUCLEUS_RESTART_FAILURE); } catch (IOException e) { logger.atWarn().log("Unable to read Nucleus logs for restart failure", e); return new DeploymentException( - "Nucleus update workflow failed to restart Nucleus. Please look at the device and loader " - + "logs for more info.", - DeploymentErrorCode.NUCLEUS_RESTART_FAILURE); + "Nucleus update workflow failed to restart Nucleus. Please look at the device and loader " + + "logs for more info.", + DeploymentErrorCode.NUCLEUS_RESTART_FAILURE); } } else { return new DeploymentException("Nucleus update workflow failed to restart Nucleus due to an " - + "unexpected device IO error", - DeploymentErrorCode.IO_WRITE_ERROR); + + "unexpected device IO error", DeploymentErrorCode.IO_WRITE_ERROR); } } catch (IOException e) { - return new DeploymentException("Nucleus update workflow failed to restart Nucleus due to an " - + "unexpected device IO error. See loader logs for more details", e, - DeploymentErrorCode.IO_WRITE_ERROR); + return new DeploymentException( + "Nucleus update workflow failed to restart Nucleus due to an " + + "unexpected device IO error. See loader logs for more details", + e, DeploymentErrorCode.IO_WRITE_ERROR); } } - - List errorStack = deployment.getErrorStack() == null ? Collections.emptyList() + + List errorStack = deployment.getErrorStack() == null + ? Collections.emptyList() : deployment.getErrorStack().stream().map(DeploymentErrorCode::valueOf).collect(Collectors.toList()); - List errorTypes = deployment.getErrorTypes() == null ? Collections.emptyList() + List errorTypes = deployment.getErrorTypes() == null + ? Collections.emptyList() : deployment.getErrorTypes().stream().map(DeploymentErrorType::valueOf).collect(Collectors.toList()); return new DeploymentException(deployment.getStageDetails(), errorStack, errorTypes); diff --git a/src/main/java/com/aws/greengrass/deployment/ShadowDeploymentListener.java b/src/main/java/com/aws/greengrass/deployment/ShadowDeploymentListener.java index 1e29b1c74e..1a0d0594b3 100644 --- a/src/main/java/com/aws/greengrass/deployment/ShadowDeploymentListener.java +++ b/src/main/java/com/aws/greengrass/deployment/ShadowDeploymentListener.java @@ -87,14 +87,14 @@ public class ShadowDeploymentListener implements InjectionActions { public static final String GGC_VERSION_KEY = "ggcVersion"; public static final String DESIRED_STATUS_CANCELED = "CANCELED"; public static final String DEPLOYMENT_SHADOW_NAME = "AWSManagedGreengrassV2Deployment"; - public static final String DEVICE_OFFLINE_MESSAGE = "Device not configured to talk to AWS Iot cloud. " - + "Single device deployment is offline"; + public static final String DEVICE_OFFLINE_MESSAGE = + "Device not configured to talk to AWS Iot cloud. " + "Single device deployment is offline"; public static final String SUBSCRIBING_TO_SHADOW_TOPICS_MESSAGE = "Subscribing to Iot Shadow topics"; - private static final String SHADOW_UPDATE_ACCEPTED_TOPIC = "$aws/things/{thingName}/shadow/name/{shadowName}" - + "/update/accepted"; - private static final String SHADOW_UPDATE_REJECTED_TOPIC = "$aws/things/{thingName}/shadow/name/{shadowName}" - + "/update/rejected"; + private static final String SHADOW_UPDATE_ACCEPTED_TOPIC = + "$aws/things/{thingName}/shadow/name/{shadowName}" + "/update/accepted"; + private static final String SHADOW_UPDATE_REJECTED_TOPIC = + "$aws/things/{thingName}/shadow/name/{shadowName}" + "/update/rejected"; private static final String SHADOW_GET_TOPIC = "$aws/things/{thingName}/shadow/name/{shadowName}/get/accepted"; private static final String SUBSCRIBE_ERROR_RETRY_MESSAGE = "Caught exception while subscribing to shadow topics, will retry shortly"; @@ -141,6 +141,7 @@ public void onConnectionResumed(boolean sessionPresent) { /** * Constructor for unit testing. + * * @param deploymentQueue {@link DeploymentQueue} * @param deploymentStatusKeeper {@link DeploymentStatusKeeper} * @param mqttClient {@link MqttClient} @@ -150,9 +151,8 @@ public void onConnectionResumed(boolean sessionPresent) { * @param kernel {@link Kernel} */ public ShadowDeploymentListener(DeploymentQueue deploymentQueue, DeploymentStatusKeeper deploymentStatusKeeper, - MqttClient mqttClient, ExecutorService executorService, - DeviceConfiguration deviceConfiguration, IotShadowClient iotShadowClient, - Kernel kernel) { + MqttClient mqttClient, ExecutorService executorService, DeviceConfiguration deviceConfiguration, + IotShadowClient iotShadowClient, Kernel kernel) { this.deploymentQueue = deploymentQueue; this.deploymentStatusKeeper = deploymentStatusKeeper; this.mqttClient = mqttClient; @@ -189,8 +189,7 @@ public void postInject() { } } - private void connectToShadowService(DeviceConfiguration deviceConfiguration) - throws DeviceConfigurationException { + private void connectToShadowService(DeviceConfiguration deviceConfiguration) throws DeviceConfigurationException { deviceConfiguration.validate(); setupShadowCommunications(); } @@ -203,16 +202,18 @@ private void setupShadowCommunications() { if (isSubscribedToShadowTopics.get()) { unsubscribeToShadowTopics(); } - deploymentStatusKeeper.registerDeploymentStatusConsumer(DeploymentType.SHADOW, - this::deploymentStatusChanged, ShadowDeploymentListener.class.getName()); + deploymentStatusKeeper.registerDeploymentStatusConsumer(DeploymentType.SHADOW, this::deploymentStatusChanged, + ShadowDeploymentListener.class.getName()); this.thingName = Coerce.toString(deviceConfiguration.getThingName()); if (subscriptionFuture == null || subscriptionFuture.isDone()) { subscriptionFuture = executorService.submit(() -> { // Wait for all node updates to come through before we subscribe to the topics - Throwable ex = kernel.getContext().runOnPublishQueueAndWait(() -> {}); + Throwable ex = kernel.getContext().runOnPublishQueueAndWait(() -> { + }); if (ex instanceof InterruptedException) { - logger.atDebug().log("Got interrupted while waiting for publish queue to clear, during Iot " - + "Shadow subscriptions"); + logger.atDebug() + .log("Got interrupted while waiting for publish queue to clear, during Iot " + + "Shadow subscriptions"); return; } subscribeToShadowTopics(); @@ -224,9 +225,9 @@ private void setupShadowCommunications() { } /* - Subscribe to "$aws/things/{thingName}/shadow/update/accepted" topic to get notified when shadow is updated - Subscribe to "$aws/things/{thingName}/shadow/update/rejected" topic to get notified when an update is rejected - Subscribe to "$aws/things/{thingName}/shadow/get/accepted" topic to retrieve shadow by publishing to get topic + * Subscribe to "$aws/things/{thingName}/shadow/update/accepted" topic to get notified when shadow is updated + * Subscribe to "$aws/things/{thingName}/shadow/update/rejected" topic to get notified when an update is rejected + * Subscribe to "$aws/things/{thingName}/shadow/get/accepted" topic to retrieve shadow by publishing to get topic */ @SuppressWarnings("PMD.AvoidCatchingThrowable") private void subscribeToShadowTopics() { @@ -237,29 +238,33 @@ private void subscribeToShadowTopics() { new UpdateNamedShadowSubscriptionRequest(); updateNamedShadowSubscriptionRequest.shadowName = DEPLOYMENT_SHADOW_NAME; updateNamedShadowSubscriptionRequest.thingName = thingName; - iotShadowClient.SubscribeToUpdateNamedShadowAccepted(updateNamedShadowSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, - updateShadowResponse -> shadowUpdated(updateShadowResponse.state.desired, - updateShadowResponse.state.reported, updateShadowResponse.version), - (e) -> logger.atError().log("Error processing updateShadowResponse", e)) + iotShadowClient + .SubscribeToUpdateNamedShadowAccepted(updateNamedShadowSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + updateShadowResponse -> shadowUpdated(updateShadowResponse.state.desired, + updateShadowResponse.state.reported, updateShadowResponse.version), + (e) -> logger.atError().log("Error processing updateShadowResponse", e)) .get(TIMEOUT_FOR_SUBSCRIBING_TO_TOPICS_SECONDS, TimeUnit.SECONDS); logger.debug("Subscribed to update named shadow accepted topic"); - iotShadowClient.SubscribeToUpdateNamedShadowRejected(updateNamedShadowSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, - updateShadowRejected -> handleNamedShadowRejectedEvent(), - (e) -> logger.atError().log("Error processing named shadow update rejected response", e)) + iotShadowClient + .SubscribeToUpdateNamedShadowRejected(updateNamedShadowSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + updateShadowRejected -> handleNamedShadowRejectedEvent(), + (e) -> logger.atError() + .log("Error processing named shadow update rejected response", e)) .get(TIMEOUT_FOR_SUBSCRIBING_TO_TOPICS_SECONDS, TimeUnit.SECONDS); logger.debug("Subscribed to update named shadow rejected topic"); - GetNamedShadowSubscriptionRequest getNamedShadowSubscriptionRequest - = new GetNamedShadowSubscriptionRequest(); + GetNamedShadowSubscriptionRequest getNamedShadowSubscriptionRequest = + new GetNamedShadowSubscriptionRequest(); getNamedShadowSubscriptionRequest.shadowName = DEPLOYMENT_SHADOW_NAME; getNamedShadowSubscriptionRequest.thingName = thingName; - iotShadowClient.SubscribeToGetNamedShadowAccepted(getNamedShadowSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, - getShadowResponse -> shadowUpdated(getShadowResponse.state.desired, - getShadowResponse.state.reported, getShadowResponse.version), - (e) -> logger.atError().log("Error processing getShadowResponse", e)) + iotShadowClient + .SubscribeToGetNamedShadowAccepted(getNamedShadowSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + getShadowResponse -> shadowUpdated(getShadowResponse.state.desired, + getShadowResponse.state.reported, getShadowResponse.version), + (e) -> logger.atError().log("Error processing getShadowResponse", e)) .get(TIMEOUT_FOR_SUBSCRIBING_TO_TOPICS_SECONDS, TimeUnit.SECONDS); logger.debug("Subscribed to get named shadow topic"); return; @@ -276,15 +281,13 @@ private void subscribeToShadowTopics() { } catch (TimeoutException e) { logger.atWarn().setCause(e).log("Subscribe to shadow topics timed out, will retry shortly"); } catch (InterruptedException e) { - //Since this method can run as runnable cannot throw exception so handling exceptions here + // Since this method can run as runnable cannot throw exception so handling exceptions here logger.atWarn().log("Interrupted while subscribing to shadow topics"); return; } catch (Throwable t) { logger.atWarn().setCause(t).log(SUBSCRIBE_ERROR_RETRY_MESSAGE); } - - try { // Wait for sometime and then try to subscribe again Thread.sleep(WAIT_TIME_TO_SUBSCRIBE_AGAIN_IN_MS + JITTER.nextInt(10_000)); @@ -296,10 +299,10 @@ private void subscribeToShadowTopics() { } /* - UnSubscribe to "$aws/things/{thingName}/shadow/update/accepted" topic to get notified when shadow is updated - UnSubscribe to "$aws/things/{thingName}/shadow/update/rejected" topic to get notified when an update is rejected - UnSubscribe to "$aws/things/{thingName}/shadow/get/accepted" topic to retrieve shadow by publishing to get topic - */ + * UnSubscribe to "$aws/things/{thingName}/shadow/update/accepted" topic to get notified when shadow is updated + * UnSubscribe to "$aws/things/{thingName}/shadow/update/rejected" topic to get notified when an update is rejected + * UnSubscribe to "$aws/things/{thingName}/shadow/get/accepted" topic to retrieve shadow by publishing to get topic + */ @SuppressWarnings("PMD.CloseResource") private void unsubscribeToShadowTopics() { MqttClientConnection connection = getMqttClientConnection(); @@ -308,8 +311,8 @@ private void unsubscribeToShadowTopics() { .replace("{shadowName}", DEPLOYMENT_SHADOW_NAME)); connection.unsubscribe(SHADOW_UPDATE_REJECTED_TOPIC.replace("{thingName}", thingName) .replace("{shadowName}", DEPLOYMENT_SHADOW_NAME)); - connection.unsubscribe(SHADOW_GET_TOPIC.replace("{thingName}", thingName) - .replace("{shadowName}", DEPLOYMENT_SHADOW_NAME)); + connection.unsubscribe( + SHADOW_GET_TOPIC.replace("{thingName}", thingName).replace("{shadowName}", DEPLOYMENT_SHADOW_NAME)); } private void handleNamedShadowRejectedEvent() { @@ -346,12 +349,13 @@ private boolean updateReportedSectionOfShadowWithDeploymentStatus() { iotShadowClient.PublishUpdateNamedShadow(updateNamedShadowRequest, QualityOfService.AT_LEAST_ONCE) .get(TIMEOUT_FOR_PUBLISHING_TO_TOPICS_SECONDS, TimeUnit.SECONDS); - logger.atInfo().kv(CONFIGURATION_ARN_LOG_KEY_NAME, deploymentDetails.get(CONFIGURATION_ARN_KEY_NAME)) + logger.atInfo() + .kv(CONFIGURATION_ARN_LOG_KEY_NAME, deploymentDetails.get(CONFIGURATION_ARN_KEY_NAME)) .kv(STATUS_KEY, shadowState.reported.get(STATUS_KEY)) .log("Updated reported state for deployment"); return true; } catch (InterruptedException e) { - //Since this method can run as runnable cannot throw exception so handling exceptions here + // Since this method can run as runnable cannot throw exception so handling exceptions here logger.atWarn().log("Interrupted while publishing reported state"); } catch (ExecutionException e) { logger.atError().setCause(e).log("Caught exception while publishing reported state"); @@ -371,14 +375,11 @@ private HashMap populateReportedSectionOfShadow(Map reported = new HashMap<>(); reported.put(ARN_FOR_STATUS_KEY, deploymentDetails.get(CONFIGURATION_ARN_KEY_NAME)); @@ -390,12 +391,11 @@ private HashMap populateReportedSectionOfShadow(Map desired, Map reported, Integer version) { if (lastVersion.get() > version) { - logger.atDebug().kv("SHADOW_VERSION", version) - .log("Received an older version of shadow. Ignoring..."); + logger.atDebug().kv("SHADOW_VERSION", version).log("Received an older version of shadow. Ignoring..."); return; } lastVersion.set(version); - //the reported section of the shadow was updated + // the reported section of the shadow was updated if (reported != null && !reported.isEmpty()) { syncShadowDeploymentStatus(reported); } @@ -406,8 +406,8 @@ protected void shadowUpdated(Map desired, Map re String fleetConfigStr = (String) desired.get(FLEET_CONFIG_KEY); Configuration configuration; try { - configuration = SerializerFactory.getFailSafeJsonObjectMapper() - .readValue(fleetConfigStr, Configuration.class); + configuration = + SerializerFactory.getFailSafeJsonObjectMapper().readValue(fleetConfigStr, Configuration.class); } catch (JsonProcessingException e) { logger.atError().log("failed to process shadow update", e); @@ -429,7 +429,8 @@ protected void shadowUpdated(Map desired, Map re if (lastConfigurationArn.compareAndSet(null, configurationArn)) { // Ignore if the latest deployment was canceled if (cancelDeployment) { - logger.atInfo().kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) + logger.atInfo() + .kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) .log("Deployment was canceled. Ignoring shadow update at startup"); return; } @@ -437,7 +438,8 @@ protected void shadowUpdated(Map desired, Map re // the reported status is terminal (i.e. not in_progress) because it's already fully processed if (reported != null && configurationArn.equals(reported.get(ARN_FOR_STATUS_KEY)) && !JobStatus.IN_PROGRESS.toString().equals(reported.get(STATUS_KEY))) { - logger.atInfo().kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) + logger.atInfo() + .kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) .log("Deployment result already reported. Ignoring shadow update at startup"); return; } @@ -451,7 +453,8 @@ protected void shadowUpdated(Map desired, Map re DeploymentTaskMetadata currentDeployment = ((DeploymentService) deploymentServiceLocateResult).getCurrentDeploymentTaskMetadata(); if (currentDeployment != null && configurationArn.equals(currentDeployment.getDeploymentId())) { - logger.atInfo().kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) + logger.atInfo() + .kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) .log("Ongoing deployment. Ignoring shadow update at startup"); return; } @@ -461,7 +464,8 @@ protected void shadowUpdated(Map desired, Map re } } else { if (lastConfigurationArn.get().equals(configurationArn) && !cancelDeployment) { - logger.atInfo().kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) + logger.atInfo() + .kv(CONFIGURATION_ARN_LOG_KEY_NAME, configurationArn) .log("Duplicate deployment notification. Ignoring shadow update"); return; } @@ -500,7 +504,7 @@ private MqttClientConnection getMqttClientConnection() { /** * Threadsafe setter for lastConfigurationArn. * - * @param configurationArn value to set lastConfigurationArn to + * @param configurationArn value to set lastConfigurationArn to */ public void setLastConfigurationArn(String configurationArn) { try (LockScope ls = LockScope.lock(lock)) { diff --git a/src/main/java/com/aws/greengrass/deployment/ThingGroupHelper.java b/src/main/java/com/aws/greengrass/deployment/ThingGroupHelper.java index caa6953758..c911717767 100644 --- a/src/main/java/com/aws/greengrass/deployment/ThingGroupHelper.java +++ b/src/main/java/com/aws/greengrass/deployment/ThingGroupHelper.java @@ -36,17 +36,17 @@ public class ThingGroupHelper { // Retry on internal service errors as well as offline indicative exceptions static final List RETRYABLE_EXCEPTIONS = Arrays.asList(SdkClientException.class, - DeviceConfigurationException.class, - RetryableServerErrorException.class); + DeviceConfigurationException.class, RetryableServerErrorException.class); private final GreengrassServiceClientFactory clientFactory; private final DeviceConfiguration deviceConfiguration; @Setter(AccessLevel.PACKAGE) @Getter(AccessLevel.PACKAGE) - private RetryUtils.RetryConfig clientExceptionRetryConfig = RetryUtils.RetryConfig.builder().initialRetryInterval( - Duration.ofMinutes(1)) + private RetryUtils.RetryConfig clientExceptionRetryConfig = RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMinutes(1)) .maxRetryInterval(Duration.ofMinutes(1)) - .retryableExceptions(RETRYABLE_EXCEPTIONS).build(); + .retryableExceptions(RETRYABLE_EXCEPTIONS) + .build(); @Inject public ThingGroupHelper(GreengrassServiceClientFactory clientFactory, DeviceConfiguration deviceConfiguration) { @@ -61,7 +61,9 @@ public ThingGroupHelper(GreengrassServiceClientFactory clientFactory, DeviceConf * @return list of thing group names * @throws Exception when not able to fetch thing group names */ - @SuppressWarnings({"PMD.SignatureDeclareThrowsException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.SignatureDeclareThrowsException", "PMD.AvoidRethrowingException" + }) public Optional> listThingGroupsForDevice(int maxAttemptCount) throws Exception { if (!deviceConfiguration.isDeviceConfiguredToTalkToCloud()) { @@ -75,7 +77,8 @@ public Optional> listThingGroupsForDevice(int maxAttemptCount) throw do { ListThingGroupsForCoreDeviceRequest request = ListThingGroupsForCoreDeviceRequest.builder() .coreDeviceThingName(Coerce.toString(deviceConfiguration.getThingName())) - .nextToken(nextToken.get()).build(); + .nextToken(nextToken.get()) + .build(); ListThingGroupsForCoreDeviceResponse response; try { @@ -89,17 +92,18 @@ public Optional> listThingGroupsForDevice(int maxAttemptCount) throw throw e; } - response.thingGroups().forEach(thingGroup -> { - //adding direct thing groups - thingGroupNames.add(THING_GROUP_RESOURCE_TYPE_PREFIX + thingGroup.thingGroupName()); - //adding parent thing groups - thingGroup.rootToParentThingGroups().forEach(parentGroup -> thingGroupNames - .add(THING_GROUP_RESOURCE_TYPE_PREFIX + parentGroup.thingGroupName())); - }); - nextToken.set(response.nextToken()); - } while (nextToken.get() != null); + response.thingGroups().forEach(thingGroup -> { + // adding direct thing groups + thingGroupNames.add(THING_GROUP_RESOURCE_TYPE_PREFIX + thingGroup.thingGroupName()); + // adding parent thing groups + thingGroup.rootToParentThingGroups() + .forEach(parentGroup -> thingGroupNames + .add(THING_GROUP_RESOURCE_TYPE_PREFIX + parentGroup.thingGroupName())); + }); + nextToken.set(response.nextToken()); + } while (nextToken.get() != null); - return Optional.of(thingGroupNames); - }, "get-thing-group-hierarchy", logger); + return Optional.of(thingGroupNames); + }, "get-thing-group-hierarchy", logger); } } diff --git a/src/main/java/com/aws/greengrass/deployment/activator/DefaultActivator.java b/src/main/java/com/aws/greengrass/deployment/activator/DefaultActivator.java index 9976395eca..2ba0bde263 100644 --- a/src/main/java/com/aws/greengrass/deployment/activator/DefaultActivator.java +++ b/src/main/java/com/aws/greengrass/deployment/activator/DefaultActivator.java @@ -40,7 +40,7 @@ public DefaultActivator(Kernel kernel) { @Override @SuppressWarnings("PMD.PrematureDeclaration") public void activate(Map newConfig, Deployment deployment, long configMergeTimestamp, - CompletableFuture totallyCompleteFuture) { + CompletableFuture totallyCompleteFuture) { Map serviceConfig; if (newConfig.containsKey(SERVICES_NAMESPACE_TOPIC)) { serviceConfig = (Map) newConfig.get(SERVICES_NAMESPACE_TOPIC); @@ -85,24 +85,28 @@ public void activate(Map newConfig, Deployment deployment, long .stream() .map(GreengrassService::getName) .collect(Collectors.toSet()); - servicesToTrack = servicesToTrack - .stream() + servicesToTrack = servicesToTrack.stream() .filter(service -> autoStartableServiceNames.contains(service.getName())) .collect(Collectors.toSet()); - logger.atDebug(MERGE_CONFIG_EVENT_KEY).kv("serviceToTrack", servicesToTrack).kv("mergeTime", mergeTime) + logger.atDebug(MERGE_CONFIG_EVENT_KEY) + .kv("serviceToTrack", servicesToTrack) + .kv("mergeTime", mergeTime) .log("Applied new service config. Waiting for services to complete update"); waitForServicesToStart(servicesToTrack, mergeTime, kernel, totallyCompleteFuture); logger.atDebug(MERGE_CONFIG_EVENT_KEY) .log("new/updated services are running, will now remove old services"); servicesChangeManager.removeObsoleteServices(); - logger.atInfo(MERGE_CONFIG_EVENT_KEY).kv(DEPLOYMENT_ID_LOG_KEY, deploymentDocument.getDeploymentId()) + logger.atInfo(MERGE_CONFIG_EVENT_KEY) + .kv(DEPLOYMENT_ID_LOG_KEY, deploymentDocument.getDeploymentId()) .log("All services updated"); totallyCompleteFuture.complete(new DeploymentResult(DeploymentResult.DeploymentStatus.SUCCESSFUL, null)); } catch (InterruptedException e) { // Treat interrupts distinctly: we don't want to start a rollback while the kernel is shutting down. // This applies even when our failure handling policy is configured to rollback. - logger.atWarn(MERGE_CONFIG_EVENT_KEY).kv(DEPLOYMENT_ID_LOG_KEY, deploymentDocument.getDeploymentId()) - .setCause(e).log("Deployment interrupted: will not attempt rollback, regardless of policy"); + logger.atWarn(MERGE_CONFIG_EVENT_KEY) + .kv(DEPLOYMENT_ID_LOG_KEY, deploymentDocument.getDeploymentId()) + .setCause(e) + .log("Deployment interrupted: will not attempt rollback, regardless of policy"); totallyCompleteFuture.complete(null); } catch (ServiceUpdateException | ServiceLoadException e) { handleFailure(servicesChangeManager, deploymentDocument, totallyCompleteFuture, e); @@ -110,24 +114,25 @@ public void activate(Map newConfig, Deployment deployment, long } private void handleFailure(DeploymentConfigMerger.AggregateServicesChangeManager servicesChangeManager, - DeploymentDocument deploymentDocument, CompletableFuture totallyCompleteFuture, - Throwable failureCause) { - logger.atError(MERGE_CONFIG_EVENT_KEY).kv(DEPLOYMENT_ID_LOG_KEY, deploymentDocument.getDeploymentId()) - .setCause(failureCause).log("Deployment failed"); + DeploymentDocument deploymentDocument, CompletableFuture totallyCompleteFuture, Throwable failureCause) { + logger.atError(MERGE_CONFIG_EVENT_KEY) + .kv(DEPLOYMENT_ID_LOG_KEY, deploymentDocument.getDeploymentId()) + .setCause(failureCause) + .log("Deployment failed"); if (isAutoRollbackRequested(deploymentDocument)) { rollback(deploymentDocument, totallyCompleteFuture, failureCause, servicesChangeManager.createRollbackManager()); } else { - totallyCompleteFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_ROLLBACK_NOT_REQUESTED, - failureCause)); + totallyCompleteFuture.complete(new DeploymentResult( + DeploymentResult.DeploymentStatus.FAILED_ROLLBACK_NOT_REQUESTED, failureCause)); } } void rollback(DeploymentDocument deploymentDocument, CompletableFuture totallyCompleteFuture, - Throwable failureCause, DeploymentConfigMerger.AggregateServicesChangeManager rollbackManager) { + Throwable failureCause, DeploymentConfigMerger.AggregateServicesChangeManager rollbackManager) { String deploymentId = deploymentDocument.getDeploymentId(); - logger.atInfo(MERGE_CONFIG_EVENT_KEY).kv(DEPLOYMENT_ID_LOG_KEY, deploymentId) + logger.atInfo(MERGE_CONFIG_EVENT_KEY) + .kv(DEPLOYMENT_ID_LOG_KEY, deploymentId) .log("Rolling back failed deployment"); // Get the timestamp before merging snapshot. It will be used to check whether services have started. @@ -137,9 +142,9 @@ void rollback(DeploymentDocument deploymentDocument, CompletableFuture { - rollbackManager.startNewServices(); - rollbackManager.replaceUnloadableService(); - rollbackManager.reinstallBrokenServices(); + rollbackManager.startNewServices(); + rollbackManager.replaceUnloadableService(); + rollbackManager.reinstallBrokenServices(); }); if (setDesiredStateFailureCause != null) { handleFailureRollback(totallyCompleteFuture, failureCause, setDesiredStateFailureCause); @@ -162,15 +167,18 @@ void rollback(DeploymentDocument deploymentDocument, CompletableFuture newConfig, Deployment deployment, long configMergeTimestamp, - CompletableFuture totallyCompleteFuture); + CompletableFuture totallyCompleteFuture); protected boolean takeConfigSnapshot(CompletableFuture totallyCompleteFuture) { - if (totallyCompleteFuture.isCancelled()) { + if (totallyCompleteFuture.isCancelled()) { return false; } try { @@ -52,12 +52,14 @@ protected boolean takeConfigSnapshot(CompletableFuture totally return true; } catch (IOException e) { // Failed to record snapshot hence did not execute merge, no rollback needed - logger.atError().setEventType(MERGE_ERROR_LOG_EVENT_KEY).setCause(e) + logger.atError() + .setEventType(MERGE_ERROR_LOG_EVENT_KEY) + .setCause(e) .log("Failed to take a snapshot for rollback"); - totallyCompleteFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, - new DeploymentException("Failed to take a snapshot for rollback", e) - .withErrorContext(e, DeploymentErrorCode.IO_WRITE_ERROR))); + totallyCompleteFuture + .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, + new DeploymentException("Failed to take a snapshot for rollback", e).withErrorContext(e, + DeploymentErrorCode.IO_WRITE_ERROR))); return false; } } @@ -77,11 +79,12 @@ protected long rollbackConfig(CompletableFuture totallyComplet } catch (IOException e) { mergeTime.set(-1); // Could not merge old snapshot transaction log, rollback failed - logger.atError().setEventType(MERGE_ERROR_LOG_EVENT_KEY).setCause(e) + logger.atError() + .setEventType(MERGE_ERROR_LOG_EVENT_KEY) + .setCause(e) .log("Failed to rollback deployment"); - totallyCompleteFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_UNABLE_TO_ROLLBACK, - failureCause)); + totallyCompleteFuture.complete(new DeploymentResult( + DeploymentResult.DeploymentStatus.FAILED_UNABLE_TO_ROLLBACK, failureCause)); } }); return mergeTime.get(); @@ -98,19 +101,20 @@ protected void updateConfiguration(long timestamp, Map newConfig // when deployment adds a new dependency (component B) to component A // the config for component B has to be merged in before externalDependenciesTopic of component A trigger // executing mergeMap using publish thread ensures this - kernel.getContext().runOnPublishQueueAndWait(() -> kernel.getConfig().updateMap( - newConfig, createDeploymentMergeBehavior(timestamp, newConfig))); + kernel.getContext() + .runOnPublishQueueAndWait(() -> kernel.getConfig() + .updateMap(newConfig, createDeploymentMergeBehavior(timestamp, newConfig))); } protected UpdateBehaviorTree createDeploymentMergeBehavior(long deploymentTimestamp, - Map newConfig) { + Map newConfig) { // root: MERGE - // services: MERGE - // *: REPLACE - // runtime: MERGE - // _private: MERGE - // configuration: REPLACE with deployment timestamp - // AUTH_TOKEN: MERGE + // services: MERGE + // *: REPLACE + // runtime: MERGE + // _private: MERGE + // configuration: REPLACE with deployment timestamp + // AUTH_TOKEN: MERGE long now = System.currentTimeMillis(); UpdateBehaviorTree rootMergeBehavior = new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, now); @@ -124,11 +128,13 @@ protected UpdateBehaviorTree createDeploymentMergeBehavior(long deploymentTimest rootMergeBehavior.getChildOverride().put(SERVICES_NAMESPACE_TOPIC, servicesMergeBehavior); servicesMergeBehavior.getChildOverride().put(UpdateBehaviorTree.WILDCARD, insideServiceMergeBehavior); - servicesMergeBehavior.getChildOverride().put(AUTHENTICATION_TOKEN_LOOKUP_KEY, - new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, now)); + servicesMergeBehavior.getChildOverride() + .put(AUTHENTICATION_TOKEN_LOOKUP_KEY, + new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, now)); // Set merge mode for all builtin services - kernel.orderedDependencies().stream() + kernel.orderedDependencies() + .stream() .filter(GreengrassService::isBuiltin) // If the builtin service is somehow in the new config, then keep the default behavior of // replacing the existing values @@ -136,28 +142,28 @@ protected UpdateBehaviorTree createDeploymentMergeBehavior(long deploymentTimest .forEach(s -> servicesMergeBehavior.getChildOverride() .put(s.getServiceName(), new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, now))); - insideServiceMergeBehavior.getChildOverride().put( - GreengrassService.RUNTIME_STORE_NAMESPACE_TOPIC, serviceRuntimeMergeBehavior); - insideServiceMergeBehavior.getChildOverride().put( - GreengrassService.PRIVATE_STORE_NAMESPACE_TOPIC, servicePrivateMergeBehavior); + insideServiceMergeBehavior.getChildOverride() + .put(GreengrassService.RUNTIME_STORE_NAMESPACE_TOPIC, serviceRuntimeMergeBehavior); + insideServiceMergeBehavior.getChildOverride() + .put(GreengrassService.PRIVATE_STORE_NAMESPACE_TOPIC, servicePrivateMergeBehavior); UpdateBehaviorTree serviceConfigurationMergeBehavior = new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE, deploymentTimestamp); - insideServiceMergeBehavior.getChildOverride().put( - CONFIGURATION_CONFIG_KEY, serviceConfigurationMergeBehavior); + insideServiceMergeBehavior.getChildOverride().put(CONFIGURATION_CONFIG_KEY, serviceConfigurationMergeBehavior); - logger.atDebug().kv("Root merge behavior", rootMergeBehavior) + logger.atDebug() + .kv("Root merge behavior", rootMergeBehavior) .log("Created deployment configuration root merge behavior."); return rootMergeBehavior; } private UpdateBehaviorTree createRollbackMergeBehavior() { // root: MERGE - // services: MERGE - // *: REPLACE - // runtime: MERGE - // _private: MERGE - // configuration: REPLACE - // AUTH_TOKEN: MERGE + // services: MERGE + // *: REPLACE + // runtime: MERGE + // _private: MERGE + // configuration: REPLACE + // AUTH_TOKEN: MERGE // For rollback the timestamp from the snapshot will be used and not this timestamp long now = System.currentTimeMillis(); @@ -172,17 +178,17 @@ private UpdateBehaviorTree createRollbackMergeBehavior() { rootMergeBehavior.getChildOverride().put(SERVICES_NAMESPACE_TOPIC, servicesMergeBehavior); servicesMergeBehavior.getChildOverride().put(UpdateBehaviorTree.WILDCARD, insideServiceMergeBehavior); - servicesMergeBehavior.getChildOverride().put(AUTHENTICATION_TOKEN_LOOKUP_KEY, - new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, now)); - - insideServiceMergeBehavior.getChildOverride().put( - GreengrassService.RUNTIME_STORE_NAMESPACE_TOPIC, serviceRuntimeMergeBehavior); - insideServiceMergeBehavior.getChildOverride().put( - GreengrassService.PRIVATE_STORE_NAMESPACE_TOPIC, servicePrivateMergeBehavior); + servicesMergeBehavior.getChildOverride() + .put(AUTHENTICATION_TOKEN_LOOKUP_KEY, + new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.MERGE, now)); + + insideServiceMergeBehavior.getChildOverride() + .put(GreengrassService.RUNTIME_STORE_NAMESPACE_TOPIC, serviceRuntimeMergeBehavior); + insideServiceMergeBehavior.getChildOverride() + .put(GreengrassService.PRIVATE_STORE_NAMESPACE_TOPIC, servicePrivateMergeBehavior); UpdateBehaviorTree serviceConfigurationMergeBehavior = new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE, now); - insideServiceMergeBehavior.getChildOverride().put( - CONFIGURATION_CONFIG_KEY, serviceConfigurationMergeBehavior); + insideServiceMergeBehavior.getChildOverride().put(CONFIGURATION_CONFIG_KEY, serviceConfigurationMergeBehavior); return rootMergeBehavior; } diff --git a/src/main/java/com/aws/greengrass/deployment/activator/DeploymentActivatorFactory.java b/src/main/java/com/aws/greengrass/deployment/activator/DeploymentActivatorFactory.java index 914dd1acc0..df6e80b3c9 100644 --- a/src/main/java/com/aws/greengrass/deployment/activator/DeploymentActivatorFactory.java +++ b/src/main/java/com/aws/greengrass/deployment/activator/DeploymentActivatorFactory.java @@ -5,7 +5,6 @@ package com.aws.greengrass.deployment.activator; - import com.aws.greengrass.deployment.bootstrap.BootstrapManager; import com.aws.greengrass.deployment.exceptions.ComponentConfigurationValidationException; import com.aws.greengrass.deployment.exceptions.ServiceUpdateException; @@ -24,11 +23,11 @@ public class DeploymentActivatorFactory { * * @param newConfig new configuration from deployment * @return DeploymentActivator instance - * @throws ServiceUpdateException if processing new configuration for activation fails + * @throws ServiceUpdateException if processing new configuration for activation fails * @throws ComponentConfigurationValidationException If changed nucleus component configuration is invalid */ - public DeploymentActivator getDeploymentActivator(Map newConfig) throws ServiceUpdateException, - ComponentConfigurationValidationException { + public DeploymentActivator getDeploymentActivator(Map newConfig) + throws ServiceUpdateException, ComponentConfigurationValidationException { BootstrapManager bootstrapManager = kernel.getContext().get(BootstrapManager.class); if (bootstrapManager.isBootstrapRequired(newConfig)) { return kernel.getContext().get(KernelUpdateActivator.class); diff --git a/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java b/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java index db6857bbe2..6073d559e4 100644 --- a/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java +++ b/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java @@ -54,7 +54,7 @@ public class KernelUpdateActivator extends DeploymentActivator { /** * Constructor of KernelUpdateActivator. * - * @param kernel Kernel instance + * @param kernel Kernel instance * @param bootstrapManager BootstrapManager instance */ @Inject @@ -67,7 +67,7 @@ public KernelUpdateActivator(Kernel kernel, BootstrapManager bootstrapManager) { @Override public void activate(Map newConfig, Deployment deployment, long configMergeTimestamp, - CompletableFuture totallyCompleteFuture) { + CompletableFuture totallyCompleteFuture) { if (!takeConfigSnapshot(totallyCompleteFuture)) { return; } @@ -75,23 +75,25 @@ public void activate(Map newConfig, Deployment deployment, long kernelAlternatives.validateLaunchDirSetupVerbose(); } catch (DirectoryValidationException e) { if (!canRecoverMissingLaunchDirSetup()) { - totallyCompleteFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, - new DeploymentException("Unable to process deployment. Greengrass launch directory" - + " is not set up or Greengrass is not set up as a system service", e))); + totallyCompleteFuture + .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, + new DeploymentException( + "Unable to process deployment. Greengrass launch directory" + + " is not set up or Greengrass is not set up as a system service", + e))); return; } try { kernelAlternatives.validateLoaderAsExecutable(); } catch (DeploymentException ex) { - totallyCompleteFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e)); + totallyCompleteFuture + .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e)); return; } } catch (DeploymentException e) { - totallyCompleteFuture.complete( - new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e)); + totallyCompleteFuture + .complete(new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e)); return; } @@ -106,7 +108,8 @@ public void activate(Map newConfig, Deployment deployment, long // Try and delete restart panic file if it exists try { Files.deleteIfExists(nucleusPaths.workPath(DEFAULT_NUCLEUS_COMPONENT_NAME) - .resolve(RESTART_PANIC_FILE_NAME).toAbsolutePath()); + .resolve(RESTART_PANIC_FILE_NAME) + .toAbsolutePath()); } catch (IOException e) { logger.atWarn().log("Unable to delete an existing restart panic file", e); } @@ -130,8 +133,9 @@ public void activate(Map newConfig, Deployment deployment, long } // If exitCode is 0, which happens when all bootstrap tasks are completed, restart in new launch // directories and verify handover is complete. As a result, exit code 0 is treated as 100 here. - logger.atInfo().log((exitCode == REQUEST_REBOOT ? "device reboot" : "Nucleus restart") - + " requested to complete bootstrap task"); + logger.atInfo() + .log((exitCode == REQUEST_REBOOT ? "device reboot" : "Nucleus restart") + + " requested to complete bootstrap task"); kernel.shutdown(30, exitCode == REQUEST_REBOOT ? REQUEST_REBOOT : REQUEST_RESTART); } catch (ServiceUpdateException | IOException e) { @@ -150,8 +154,8 @@ void rollback(Deployment deployment, Throwable failureCause) { deployment.setErrorTypes(errorReport.getRight()); deployment.setStageDetails(Utils.generateFailureMessage(failureCause)); - final boolean bootstrapOnRollbackRequired = kernelAlternatives.prepareBootstrapOnRollbackIfNeeded( - kernel.getContext(), deploymentDirectoryManager, bootstrapManager); + final boolean bootstrapOnRollbackRequired = kernelAlternatives + .prepareBootstrapOnRollbackIfNeeded(kernel.getContext(), deploymentDirectoryManager, bootstrapManager); deployment.setDeploymentStage(bootstrapOnRollbackRequired ? ROLLBACK_BOOTSTRAP : KERNEL_ROLLBACK); @@ -171,16 +175,16 @@ void rollback(Deployment deployment, Throwable failureCause) { protected boolean canRecoverMissingLaunchDirSetup() { /* - Try and relink launch dir with the following replacement criteria - 1. check if current Nucleus execution package is valid - 2. un-archive current Nucleus version from component store - 3. fail with DirectoryValidationException if above steps do not satisfy + * Try and relink launch dir with the following replacement criteria 1. check if current Nucleus execution + * package is valid 2. un-archive current Nucleus version from component store 3. fail with + * DirectoryValidationException if above steps do not satisfy */ try { Path currentNucleusExecutablePath = KernelAlternatives.locateCurrentKernelUnpackDir(); if (Files.exists(currentNucleusExecutablePath.resolve(KERNEL_BIN_DIR) .resolve(Platform.getInstance().loaderFilename()))) { - logger.atDebug().kv("path", currentNucleusExecutablePath) + logger.atDebug() + .kv("path", currentNucleusExecutablePath) .log("Current Nucleus executable is valid, setting up launch dir"); kernelAlternatives.relinkInitLaunchDir(currentNucleusExecutablePath, true); return true; @@ -190,11 +194,12 @@ protected boolean canRecoverMissingLaunchDirSetup() { List localNucleusExecutablePaths = componentManager.unArchiveCurrentNucleusVersionArtifacts(); if (!localNucleusExecutablePaths.isEmpty()) { Optional validNucleusExecutablePath = localNucleusExecutablePaths.stream() - .filter(path -> Files.exists(path.resolve(KERNEL_BIN_DIR) - .resolve(Platform.getInstance().loaderFilename()))) + .filter(path -> Files + .exists(path.resolve(KERNEL_BIN_DIR).resolve(Platform.getInstance().loaderFilename()))) .findFirst(); if (validNucleusExecutablePath.isPresent()) { - logger.atDebug().kv("path", validNucleusExecutablePath.get()) + logger.atDebug() + .kv("path", validNucleusExecutablePath.get()) .log("Un-archived current Nucleus artifact"); kernelAlternatives.relinkInitLaunchDir(validNucleusExecutablePath.get(), true); return true; @@ -202,7 +207,7 @@ protected boolean canRecoverMissingLaunchDirSetup() { } logger.atInfo().log("Cannot recover missing launch dir setup as no local Nucleus artifact is present"); return false; - } catch (IOException | URISyntaxException | PackageLoadingException e) { + } catch (IOException | URISyntaxException | PackageLoadingException e) { logger.atWarn().setCause(e).log("Could not recover missing launch dir setup"); return false; } diff --git a/src/main/java/com/aws/greengrass/deployment/bootstrap/BootstrapManager.java b/src/main/java/com/aws/greengrass/deployment/bootstrap/BootstrapManager.java index ef32f87e22..d458878f90 100644 --- a/src/main/java/com/aws/greengrass/deployment/bootstrap/BootstrapManager.java +++ b/src/main/java/com/aws/greengrass/deployment/bootstrap/BootstrapManager.java @@ -82,7 +82,7 @@ * Generates a list of bootstrap tasks from deployments, manages the execution and persists status. */ @NotThreadSafe -public class BootstrapManager implements Iterator { +public class BootstrapManager implements Iterator { private static final String COMPONENT_NAME_LOG_KEY_NAME = "componentName"; private static final String RESTART_REQUIRED_MESSAGE = "Restart required due to configuration change"; @@ -127,12 +127,11 @@ public Set getUnstartedTasks() { } /** - * Check if any bootstrap tasks are pending based on new configuration. Meanwhile resolve a list of bootstrap - * tasks. + * Check if any bootstrap tasks are pending based on new configuration. Meanwhile resolve a list of bootstrap tasks. * * @param newConfig new configuration from deployment * @return true if there are bootstrap tasks, false otherwise - * @throws ServiceUpdateException if parsing bootstrap tasks from new configuration fails + * @throws ServiceUpdateException if parsing bootstrap tasks from new configuration fails * @throws ComponentConfigurationValidationException If changed nucleus component configuration is invalid */ @SuppressWarnings("PMD.PrematureDeclaration") @@ -142,13 +141,12 @@ public boolean isBootstrapRequired(Map newConfig) } /** - * Check if any bootstrap tasks are pending based on new configuration. Meanwhile resolve a list of bootstrap - * tasks. + * Check if any bootstrap tasks are pending based on new configuration. Meanwhile resolve a list of bootstrap tasks. * * @param newConfig new configuration from deployment * @param componentsToExclude set of components to exclude from consideration for bootstrapping * @return true if there are bootstrap tasks, false otherwise - * @throws ServiceUpdateException if parsing bootstrap tasks from new configuration fails + * @throws ServiceUpdateException if parsing bootstrap tasks from new configuration fails * @throws ComponentConfigurationValidationException If changed nucleus component configuration is invalid */ @SuppressWarnings("PMD.PrematureDeclaration") @@ -158,8 +156,8 @@ public boolean isBootstrapRequired(Map newConfig, Set co cursor = 0; if (newConfig == null || !newConfig.containsKey(SERVICES_NAMESPACE_TOPIC)) { - logger.atInfo().log( - "No bootstrap tasks found: Deployment configuration is missing or has no service changes"); + logger.atInfo() + .log("No bootstrap tasks found: Deployment configuration is missing or has no service changes"); return false; } @@ -186,10 +184,9 @@ public boolean isBootstrapRequired(Map newConfig, Set co } List errors = new ArrayList<>(); // Figure out the dependency order within the subset of components which require changes - LinkedHashSet dependencyFound = - new DependencyOrder().computeOrderedDependencies(componentsRequiresBootstrapTask, - name -> getDependenciesWithinSubset(name, componentsRequiresBootstrapTask, - (Map) serviceConfig.get(name), errors)); + LinkedHashSet dependencyFound = new DependencyOrder() + .computeOrderedDependencies(componentsRequiresBootstrapTask, name -> getDependenciesWithinSubset(name, + componentsRequiresBootstrapTask, (Map) serviceConfig.get(name), errors)); if (!errors.isEmpty()) { throw new ServiceUpdateException(errors.toString(), DeploymentErrorCode.COMPONENT_DEPENDENCY_NOT_VALID); } @@ -204,21 +201,23 @@ private boolean isIncompleteOrErrored(BootstrapTaskStatus task) { } private boolean willRemovePlugins(Map serviceConfig) { - Set pluginsToRemove = kernel.orderedDependencies().stream() + Set pluginsToRemove = kernel.orderedDependencies() + .stream() .filter(s -> s instanceof PluginService) .filter(s -> !s.isBuiltin()) .filter(s -> !serviceConfig.containsKey(s.getName())) .map(GreengrassService::getName) .collect(Collectors.toSet()); if (!pluginsToRemove.isEmpty()) { - logger.atInfo().kv("plugins-to-remove", pluginsToRemove) + logger.atInfo() + .kv("plugins-to-remove", pluginsToRemove) .log("Bootstrap required for cleaning up plugin(s)"); } return !pluginsToRemove.isEmpty(); } private boolean fipsModeHasChanged(Map newNucleusParameters, - DeviceConfiguration currentDeviceConfiguration) { + DeviceConfiguration currentDeviceConfiguration) { boolean currentFipsMode = Coerce.toBoolean(currentDeviceConfiguration.getFipsMode()); boolean newFipsMode = Coerce.toBoolean(newNucleusParameters.get(DEVICE_PARAM_FIPS_MODE)); if (currentFipsMode != newFipsMode) { @@ -229,9 +228,9 @@ private boolean fipsModeHasChanged(Map newNucleusParameters, } private boolean mqttVersionHasChanged(Map newNucleusParameters, - DeviceConfiguration currentDeviceConfiguration) { - String currentMqttVersion = Coerce.toString( - currentDeviceConfiguration.getMQTTNamespace().findOrDefault(DEFAULT_MQTT_VERSION, "version")); + DeviceConfiguration currentDeviceConfiguration) { + String currentMqttVersion = Coerce + .toString(currentDeviceConfiguration.getMQTTNamespace().findOrDefault(DEFAULT_MQTT_VERSION, "version")); Map newMqtt = (Map) newNucleusParameters.get(DEVICE_MQTT_NAMESPACE); Object newVersion = newMqtt == null ? null : newMqtt.get("version"); if (newVersion == null && !DEFAULT_MQTT_VERSION.equalsIgnoreCase(currentMqttVersion) @@ -243,18 +242,17 @@ private boolean mqttVersionHasChanged(Map newNucleusParameters, } private boolean spoolerStorageTypeHasChanged(Map newNucleusParameters, - DeviceConfiguration currentDeviceConfiguration) { - String currentSpoolerStorageType = Coerce.toString( - currentDeviceConfiguration.getSpoolerNamespace().findOrDefault(DEFAULT_SPOOL_STORAGE_TYPE, - SPOOL_STORAGE_TYPE_KEY)); + DeviceConfiguration currentDeviceConfiguration) { + String currentSpoolerStorageType = Coerce.toString(currentDeviceConfiguration.getSpoolerNamespace() + .findOrDefault(DEFAULT_SPOOL_STORAGE_TYPE, SPOOL_STORAGE_TYPE_KEY)); Map newMqtt = (Map) newNucleusParameters.get(DEVICE_MQTT_NAMESPACE); - Map newSpooler = newMqtt == null ? null - : (Map) newMqtt.get(DEVICE_SPOOLER_NAMESPACE); + Map newSpooler = + newMqtt == null ? null : (Map) newMqtt.get(DEVICE_SPOOLER_NAMESPACE); Object newStorageType = newSpooler == null ? null : newSpooler.get(SPOOL_STORAGE_TYPE_KEY); if (newStorageType == null && !(DEFAULT_SPOOL_STORAGE_TYPE.toString().equalsIgnoreCase(currentSpoolerStorageType)) || newStorageType instanceof String - && !currentSpoolerStorageType.equalsIgnoreCase((String) newStorageType)) { + && !currentSpoolerStorageType.equalsIgnoreCase((String) newStorageType)) { logger.atInfo().kv(DEVICE_SPOOLER_NAMESPACE, newSpooler).log(RESTART_REQUIRED_MESSAGE); return true; } @@ -262,7 +260,7 @@ private boolean spoolerStorageTypeHasChanged(Map newNucleusParam } private boolean networkProxyHasChanged(Map newNucleusParameters, - DeviceConfiguration currentDeviceConfiguration) { + DeviceConfiguration currentDeviceConfiguration) { Map newNetworkProxy = (Map) newNucleusParameters.get(DEVICE_NETWORK_PROXY_NAMESPACE); if (newNetworkProxy == null) { @@ -307,25 +305,28 @@ private boolean networkProxyHasChanged(Map newNucleusParameters, private boolean defaultRunWithChanged(Map newNucleusParameters, DeviceConfiguration currentDeviceConfiguration) throws ComponentConfigurationValidationException { - Map runWithDefault = (Map)newNucleusParameters.getOrDefault(RUN_WITH_TOPIC, - Collections.emptyMap()); + Map runWithDefault = + (Map) newNucleusParameters.getOrDefault(RUN_WITH_TOPIC, Collections.emptyMap()); Map currentValues = currentDeviceConfiguration.getRunWithTopic().toPOJO(); boolean changed = false; if (Utils.stringHasChanged(Coerce.toString(currentValues.get(RUN_WITH_DEFAULT_POSIX_USER)), Coerce.toString(runWithDefault.get(RUN_WITH_DEFAULT_POSIX_USER)))) { - logger.atInfo().kv(RUN_WITH_TOPIC + "." + RUN_WITH_DEFAULT_POSIX_USER, - runWithDefault.get(RUN_WITH_DEFAULT_POSIX_USER)) + logger.atInfo() + .kv(RUN_WITH_TOPIC + "." + RUN_WITH_DEFAULT_POSIX_USER, + runWithDefault.get(RUN_WITH_DEFAULT_POSIX_USER)) .log(RESTART_REQUIRED_MESSAGE); changed = true; } - if (Utils.stringHasChanged(Coerce.toString(currentValues.getOrDefault(RUN_WITH_DEFAULT_POSIX_SHELL, - RUN_WITH_DEFAULT_POSIX_SHELL_VALUE)), + if (Utils.stringHasChanged( + Coerce.toString( + currentValues.getOrDefault(RUN_WITH_DEFAULT_POSIX_SHELL, RUN_WITH_DEFAULT_POSIX_SHELL_VALUE)), Coerce.toString(runWithDefault.getOrDefault(RUN_WITH_DEFAULT_POSIX_SHELL, RUN_WITH_DEFAULT_POSIX_SHELL_VALUE)))) { - logger.atInfo().kv(RUN_WITH_TOPIC + "." + RUN_WITH_DEFAULT_POSIX_SHELL, - runWithDefault.get(RUN_WITH_DEFAULT_POSIX_SHELL)) + logger.atInfo() + .kv(RUN_WITH_TOPIC + "." + RUN_WITH_DEFAULT_POSIX_SHELL, + runWithDefault.get(RUN_WITH_DEFAULT_POSIX_SHELL)) .log(RESTART_REQUIRED_MESSAGE); changed = true; } @@ -337,7 +338,8 @@ private boolean defaultRunWithChanged(Map newNucleusParameters, throw new ComponentConfigurationValidationException(e, DeploymentErrorCode.RUN_WITH_CONFIG_NOT_VALID); } try { - logger.atInfo().kv("changed", RUN_WITH_TOPIC) + logger.atInfo() + .kv("changed", RUN_WITH_TOPIC) .kv("old", SerializerFactory.getFailSafeJsonObjectMapper().writeValueAsString(currentValues)) .kv("new", SerializerFactory.getFailSafeJsonObjectMapper().writeValueAsString(runWithDefault)) .log(RESTART_REQUIRED_MESSAGE); @@ -350,14 +352,13 @@ private boolean defaultRunWithChanged(Map newNucleusParameters, } private boolean nucleusConfigChangeRequiresRestart(Map newNucleusParameters, - DeviceConfiguration currentDeviceConfiguration) - throws ComponentConfigurationValidationException { + DeviceConfiguration currentDeviceConfiguration) throws ComponentConfigurationValidationException { // validation must not be skipped - otherwise the nucleus will be restarted with invalid config - boolean proxyChanged = networkProxyHasChanged(newNucleusParameters, currentDeviceConfiguration); + boolean proxyChanged = networkProxyHasChanged(newNucleusParameters, currentDeviceConfiguration); boolean runWithChanged = defaultRunWithChanged(newNucleusParameters, currentDeviceConfiguration); boolean mqttVersionChanged = mqttVersionHasChanged(newNucleusParameters, currentDeviceConfiguration); - boolean spoolerStorageTypeChanged = spoolerStorageTypeHasChanged(newNucleusParameters, - currentDeviceConfiguration); + boolean spoolerStorageTypeChanged = + spoolerStorageTypeHasChanged(newNucleusParameters, currentDeviceConfiguration); boolean fipsModeChanged = fipsModeHasChanged(newNucleusParameters, currentDeviceConfiguration); return proxyChanged || runWithChanged || mqttVersionChanged || spoolerStorageTypeChanged || fipsModeChanged; @@ -390,8 +391,8 @@ private boolean nucleusConfigValidAndNeedsRestart(Map deployment } private Map getProposedNucleusConfig(Map deploymentConfig) { - Map services = (Map) deploymentConfig.getOrDefault(SERVICES_NAMESPACE_TOPIC, - Collections.emptyMap()); + Map services = + (Map) deploymentConfig.getOrDefault(SERVICES_NAMESPACE_TOPIC, Collections.emptyMap()); for (Map.Entry serviceConfig : services.entrySet()) { if (serviceConfig.getValue() instanceof Map) { Map serviceConfigMap = (Map) serviceConfig.getValue(); @@ -413,7 +414,7 @@ private Map getProposedNucleusConfig(Map deploym * @param componentConfig config of the component */ private Set getDependenciesWithinSubset(String componentName, Set subset, - Map componentConfig, List errors) { + Map componentConfig, List errors) { Set relevantDependencies = new HashSet<>(); if (!componentConfig.containsKey(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC)) { return relevantDependencies; @@ -426,8 +427,10 @@ private Set getDependenciesWithinSubset(String componentName, Set getDependenciesWithinSubset(String componentName, Set newServiceConfig) { // For existing components, call service to decide @@ -450,18 +453,21 @@ boolean serviceBootstrapRequired(String componentName, Map newSe } // For newly added components, check if bootstrap is specified in config map if (!newServiceConfig.containsKey(SERVICE_LIFECYCLE_NAMESPACE_TOPIC)) { - logger.atDebug().kv(COMPONENT_NAME_LOG_KEY_NAME, componentName) + logger.atDebug() + .kv(COMPONENT_NAME_LOG_KEY_NAME, componentName) .log("Bootstrap is not required: service lifecycle config not found"); return false; } Map newServiceLifecycle = (Map) newServiceConfig.get(SERVICE_LIFECYCLE_NAMESPACE_TOPIC); if (serviceLifecycleDefined(newServiceLifecycle, LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC).isEmpty()) { - logger.atDebug().kv(COMPONENT_NAME_LOG_KEY_NAME, componentName) + logger.atDebug() + .kv(COMPONENT_NAME_LOG_KEY_NAME, componentName) .log("Bootstrap is not required: service lifecycle bootstrap not found"); return false; } - logger.atInfo().kv(COMPONENT_NAME_LOG_KEY_NAME, componentName) + logger.atInfo() + .kv(COMPONENT_NAME_LOG_KEY_NAME, componentName) .log("Bootstrap is required: new service with bootstrap defined"); return true; } @@ -514,7 +520,8 @@ public void loadBootstrapTaskList(Path persistedTaskFilePath) throws IOException CommitableReader.of(persistedTaskFilePath).read(in -> { bootstrapTaskStatusList.clear(); bootstrapTaskStatusList.addAll(SerializerFactory.getFailSafeJsonObjectMapper() - .readValue(in, new TypeReference>(){})); + .readValue(in, new TypeReference>() { + })); return null; }); } @@ -541,8 +548,8 @@ protected int executeOneBootstrapTask(BootstrapTaskStatus next) throws ServiceUp } /** - * Execute all bootstrap steps one by one, until kernel restart or device reboot is requested to complete any one - * of the bootstrap steps. + * Execute all bootstrap steps one by one, until kernel restart or device reboot is requested to complete any one of + * the bootstrap steps. * * @param persistedTaskFilePath Path to the persisted file of bootstrap task list * @return 100 if kernel restart is needed, 101 if device reboot is needed, 0 if no-op. @@ -555,22 +562,24 @@ public int executeAllBootstrapTasksSequentially(Path persistedTaskFilePath) int exitCode; while (hasNext()) { BootstrapTaskStatus next = next(); - logger.atInfo().kv(COMPONENT_NAME_LOG_KEY_NAME, next.getComponentName()) + logger.atInfo() + .kv(COMPONENT_NAME_LOG_KEY_NAME, next.getComponentName()) .log("Execute component bootstrap step"); exitCode = executeOneBootstrapTask(next); switch (exitCode) { - case NO_OP: - case REQUEST_RESTART: - case REQUEST_REBOOT: - persistBootstrapTaskList(persistedTaskFilePath); - break; - default: - persistBootstrapTaskList(persistedTaskFilePath); - throw new ServiceUpdateException( - String.format("Fail to execute bootstrap step for %s, exit code: %d", - next.getComponentName(), exitCode), DeploymentErrorCode.COMPONENT_BOOTSTRAP_ERROR, - DeploymentErrorCodeUtils.classifyComponentError(next.getComponentName(), kernel)); + case NO_OP: + case REQUEST_RESTART: + case REQUEST_REBOOT: + persistBootstrapTaskList(persistedTaskFilePath); + break; + default: + persistBootstrapTaskList(persistedTaskFilePath); + throw new ServiceUpdateException( + String.format("Fail to execute bootstrap step for %s, exit code: %d", next.getComponentName(), + exitCode), + DeploymentErrorCode.COMPONENT_BOOTSTRAP_ERROR, + DeploymentErrorCodeUtils.classifyComponentError(next.getComponentName(), kernel)); } if (exitCode != 0) { return exitCode; diff --git a/src/main/java/com/aws/greengrass/deployment/converter/DeploymentDocumentConverter.java b/src/main/java/com/aws/greengrass/deployment/converter/DeploymentDocumentConverter.java index 1f418a8dfc..842c1d83dc 100644 --- a/src/main/java/com/aws/greengrass/deployment/converter/DeploymentDocumentConverter.java +++ b/src/main/java/com/aws/greengrass/deployment/converter/DeploymentDocumentConverter.java @@ -51,7 +51,6 @@ public final class DeploymentDocumentConverter { public static final String ANY_VERSION = "*"; - private DeploymentDocumentConverter() { // So that this can't be initialized } @@ -59,7 +58,7 @@ private DeploymentDocumentConverter() { /** * Convert to a DeploymentDocument from a LocalOverrideRequest and the current running root Components. * - * @param localOverrideRequest local override request + * @param localOverrideRequest local override request * @param runningRootComponents current running root component name to version * @return a converted DeploymentDocument */ @@ -85,24 +84,26 @@ public static DeploymentDocument convertFromLocalOverrideRequestAndRoot(LocalOve List packageConfigurations = buildDeploymentPackageConfigurations(localOverrideRequest, newRootComponents); - return DeploymentDocument.builder().timestamp(localOverrideRequest.getRequestTimestamp()) + return DeploymentDocument.builder() + .timestamp(localOverrideRequest.getRequestTimestamp()) .deploymentId(localOverrideRequest.getRequestId()) .deploymentPackageConfigurationList(packageConfigurations) .requiredCapabilities(localOverrideRequest.getRequiredCapabilities()) - .failureHandlingPolicy(convertFailureHandlingPolicyFromSDK( - localOverrideRequest.getFailureHandlingPolicy())) + .failureHandlingPolicy( + convertFailureHandlingPolicyFromSDK(localOverrideRequest.getFailureHandlingPolicy())) // Currently we skip update policy check for local deployment to not slow down testing for customers // If we make this configurable in local development then we can plug that input in here // NO_OP_TIMEOUT is not used since the policy is SKIP_NOTIFY_COMPONENTS - .configurationValidationPolicy( - DeploymentConfigurationValidationPolicy.builder().timeoutInSeconds(DEFAULT_TIMEOUT_SECOND) - .build()) + .configurationValidationPolicy(DeploymentConfigurationValidationPolicy.builder() + .timeoutInSeconds(DEFAULT_TIMEOUT_SECOND) + .build()) .componentUpdatePolicy(new ComponentUpdatePolicy(NO_OP_TIMEOUT, SKIP_NOTIFY_COMPONENTS)) - .groupName(StringUtils.isEmpty(localOverrideRequest.getGroupName()) ? LOCAL_DEPLOYMENT_GROUP_NAME - : THING_GROUP_RESOURCE_NAME_PREFIX + localOverrideRequest.getGroupName()).build(); + .groupName(StringUtils.isEmpty(localOverrideRequest.getGroupName()) + ? LOCAL_DEPLOYMENT_GROUP_NAME + : THING_GROUP_RESOURCE_NAME_PREFIX + localOverrideRequest.getGroupName()) + .build(); } - private static List buildDeploymentPackageConfigurations( LocalOverrideRequest localOverrideRequest, Map newRootComponents) { Map packageConfigurations = new HashMap<>(); @@ -127,7 +128,8 @@ private static List buildDeploymentPackageConfig localOverrideRequest.getComponentToRunWithInfo().forEach((componentName, runWithInfo) -> { if (runWithInfo != null) { packageConfigurations.computeIfAbsent(componentName, DeploymentPackageConfiguration::new); - RunWith runWith = RunWith.builder().posixUser(runWithInfo.getPosixUser()) + RunWith runWith = RunWith.builder() + .posixUser(runWithInfo.getPosixUser()) .windowsUser(runWithInfo.getWindowsUser()) .systemResourceLimits(convertSystemResourceLimits(runWithInfo.getSystemResourceLimits())) .build(); @@ -142,29 +144,29 @@ private static List buildDeploymentPackageConfig * Converts deployment configuration {@link Configuration} that is generated by CreateDeployment and gets sent down * via IoT Job and shadow to the Nucleus's core {@link DeploymentDocument}. * - * @param config Fleet configuration that is generated by CreateDeployment and gets sent down via IoT Job and - * shadow + * @param config Fleet configuration that is generated by CreateDeployment and gets sent down via IoT Job and shadow * @return Nucleus's core {@link DeploymentDocument} * @throws InvalidRequestException if failed to parsing deployment document from configuration. */ public static DeploymentDocument convertFromDeploymentConfiguration(Configuration config) throws InvalidRequestException { - DeploymentDocument.DeploymentDocumentBuilder builder = - DeploymentDocument.builder().configurationArn(config.getConfigurationArn()) - .deploymentId(config.getDeploymentId()) - .requiredCapabilities(config.getRequiredCapabilities()) - .deploymentPackageConfigurationList(convertComponents(config.getComponents())) - .groupName(parseGroupNameFromConfigurationArn(config)) - .timestamp(config.getCreationTimestamp()); + DeploymentDocument.DeploymentDocumentBuilder builder = DeploymentDocument.builder() + .configurationArn(config.getConfigurationArn()) + .deploymentId(config.getDeploymentId()) + .requiredCapabilities(config.getRequiredCapabilities()) + .deploymentPackageConfigurationList(convertComponents(config.getComponents())) + .groupName(parseGroupNameFromConfigurationArn(config)) + .timestamp(config.getCreationTimestamp()); convertThingGroupArns(builder, config); if (config.getFailureHandlingPolicy() == null) { // FailureHandlingPolicy should be provided per contract with CreateDeployment API. // However if it is not, device could proceed with default for resilience. - logger.atWarn().log("FailureHandlingPolicy should be provided but is not provided. " - + "Proceeding with default failure handling policy."); + logger.atWarn() + .log("FailureHandlingPolicy should be provided but is not provided. " + + "Proceeding with default failure handling policy."); } else { builder.failureHandlingPolicy(convertFailureHandlingPolicy(config.getFailureHandlingPolicy())); } @@ -172,8 +174,9 @@ public static DeploymentDocument convertFromDeploymentConfiguration(Configuratio if (config.getComponentUpdatePolicy() == null) { // ComponentUpdatePolicy should be provided per contract with CreateDeployment API. // However if it is not, device could proceed with default for resilience. - logger.atWarn().log("ComponentUpdatePolicy should be provided but is not provided. " - + "Proceeding with default failure handling policy."); + logger.atWarn() + .log("ComponentUpdatePolicy should be provided but is not provided. " + + "Proceeding with default failure handling policy."); } else { builder.componentUpdatePolicy(convertComponentUpdatePolicy(config.getComponentUpdatePolicy())); } @@ -181,12 +184,12 @@ public static DeploymentDocument convertFromDeploymentConfiguration(Configuratio if (config.getConfigurationValidationPolicy() == null) { // ConfigurationValidationPolicy should be provided per contract with CreateDeployment API. // However if it is not, device could proceed with default for resilience. - logger.atWarn().log("ConfigurationValidationPolicy should be provided but is not provided. " - + "Proceeding with default failure handling policy."); + logger.atWarn() + .log("ConfigurationValidationPolicy should be provided but is not provided. " + + "Proceeding with default failure handling policy."); } else { - builder.configurationValidationPolicy(convertConfigurationValidationPolicy( - config.getConfigurationValidationPolicy()) - ); + builder.configurationValidationPolicy( + convertConfigurationValidationPolicy(config.getConfigurationValidationPolicy())); } return builder.build(); @@ -208,7 +211,7 @@ private static String parseGroupNameFromConfigurationArn(Configuration config) { @SuppressWarnings("PMD.PreserveStackTrace") private static void convertThingGroupArns(DeploymentDocument.DeploymentDocumentBuilder deployDocBuilder, - Configuration config) throws InvalidRequestException { + Configuration config) throws InvalidRequestException { if (Utils.isNotEmpty(config.getOnBehalfOf())) { try { // IoT thingGroupArn only gives thing group name without 'thinggroup/' prefix @@ -260,18 +263,21 @@ private static DeploymentPackageConfiguration convertComponent(String componentN } DeploymentPackageConfiguration.DeploymentPackageConfigurationBuilder builder = - DeploymentPackageConfiguration.builder().packageName(componentName) - .resolvedVersion(componentUpdate.getVersion().getValue()) - .rootComponent(true) // As of now, CreateDeployment API only gives root component - .configurationUpdateOperation( - convertComponentUpdateOperation(componentUpdate.getConfigurationUpdate())); + DeploymentPackageConfiguration.builder() + .packageName(componentName) + .resolvedVersion(componentUpdate.getVersion().getValue()) + .rootComponent(true) // As of now, + // CreateDeployment API only gives root component + .configurationUpdateOperation( + convertComponentUpdateOperation(componentUpdate.getConfigurationUpdate())); // We always want to set the RunWith even if the passed in RunWith is null in order to allow for either // keeping the existing run with user, updating it, or reverting it to the default. builder = builder.runWith(RunWith.builder() .posixUser(componentUpdate.getRunWith() == null ? null : componentUpdate.getRunWith().getPosixUser()) .windowsUser( componentUpdate.getRunWith() == null ? null : componentUpdate.getRunWith().getWindowsUser()) - .systemResourceLimits(componentUpdate.getRunWith() == null ? null + .systemResourceLimits(componentUpdate.getRunWith() == null + ? null : convertSystemResourceLimits(componentUpdate.getRunWith().getSystemResourceLimits())) .build()); return builder.build(); @@ -279,8 +285,9 @@ private static DeploymentPackageConfiguration convertComponent(String componentN /** * Convert configuration update from Cloud/Device shared model to the device-side model. - * @param configurationUpdate common model shared between cloud and device - * @return device-side model for configuration update + * + * @param configurationUpdate common model shared between cloud and device + * @return device-side model for configuration update */ public static ConfigurationUpdateOperation convertComponentUpdateOperation( @Nullable ConfigurationUpdate configurationUpdate) { @@ -315,8 +322,7 @@ private static ComponentUpdatePolicy convertComponentUpdatePolicy( } private static DeploymentConfigurationValidationPolicy convertConfigurationValidationPolicy( - @Nonnull com.amazon.aws.iot.greengrass.configuration.common.ConfigurationValidationPolicy - configurationValidationPolicy) { + @Nonnull com.amazon.aws.iot.greengrass.configuration.common.ConfigurationValidationPolicy configurationValidationPolicy) { DeploymentConfigurationValidationPolicy.Builder converted = DeploymentConfigurationValidationPolicy.builder(); if (configurationValidationPolicy.getTimeout() != null) { diff --git a/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCode.java b/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCode.java index 4ee20370d4..6e41757706 100644 --- a/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCode.java +++ b/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCode.java @@ -9,140 +9,130 @@ public enum DeploymentErrorCode { /* Generic types */ - DEPLOYMENT_FAILURE(DeploymentErrorType.NONE), - DEPLOYMENT_REJECTED(DeploymentErrorType.NONE), - DEPLOYMENT_INTERRUPTED(DeploymentErrorType.NONE), - ARTIFACT_DOWNLOAD_ERROR(DeploymentErrorType.NONE), - NO_AVAILABLE_COMPONENT_VERSION(DeploymentErrorType.NONE), - COMPONENT_PACKAGE_LOADING_ERROR(DeploymentErrorType.NONE), + DEPLOYMENT_FAILURE(DeploymentErrorType.NONE), DEPLOYMENT_REJECTED(DeploymentErrorType.NONE), DEPLOYMENT_INTERRUPTED( + DeploymentErrorType.NONE), ARTIFACT_DOWNLOAD_ERROR( + DeploymentErrorType.NONE), NO_AVAILABLE_COMPONENT_VERSION( + DeploymentErrorType.NONE), COMPONENT_PACKAGE_LOADING_ERROR(DeploymentErrorType.NONE), /* Deployment request errors */ - REJECTED_STALE_DEPLOYMENT(DeploymentErrorType.NONE), - NUCLEUS_MISSING_REQUIRED_CAPABILITIES(DeploymentErrorType.REQUEST_ERROR), - COMPONENT_CIRCULAR_DEPENDENCY_ERROR(DeploymentErrorType.REQUEST_ERROR), - UNAUTHORIZED_NUCLEUS_MINOR_VERSION_UPDATE(DeploymentErrorType.REQUEST_ERROR), - MISSING_DOCKER_APPLICATION_MANAGER(DeploymentErrorType.REQUEST_ERROR), - MISSING_TOKEN_EXCHANGE_SERVICE(DeploymentErrorType.REQUEST_ERROR), - COMPONENT_VERSION_REQUIREMENTS_NOT_MET(DeploymentErrorType.REQUEST_ERROR), + REJECTED_STALE_DEPLOYMENT(DeploymentErrorType.NONE), NUCLEUS_MISSING_REQUIRED_CAPABILITIES( + DeploymentErrorType.REQUEST_ERROR), COMPONENT_CIRCULAR_DEPENDENCY_ERROR( + DeploymentErrorType.REQUEST_ERROR), UNAUTHORIZED_NUCLEUS_MINOR_VERSION_UPDATE( + DeploymentErrorType.REQUEST_ERROR), MISSING_DOCKER_APPLICATION_MANAGER( + DeploymentErrorType.REQUEST_ERROR), MISSING_TOKEN_EXCHANGE_SERVICE( + DeploymentErrorType.REQUEST_ERROR), COMPONENT_VERSION_REQUIREMENTS_NOT_MET( + DeploymentErrorType.REQUEST_ERROR), // deployment resolved multiple nucleus types MULTIPLE_NUCLEUS_RESOLVED_ERROR(DeploymentErrorType.REQUEST_ERROR), /* Greengrass cloud service errors */ - CLOUD_API_ERROR(DeploymentErrorType.NONE), - BAD_REQUEST(DeploymentErrorType.NUCLEUS_ERROR), - ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - THROTTLING_ERROR(DeploymentErrorType.REQUEST_ERROR), - SERVER_ERROR(DeploymentErrorType.SERVER_ERROR), - CONFLICTED_REQUEST(DeploymentErrorType.REQUEST_ERROR), - RESOURCE_NOT_FOUND(DeploymentErrorType.REQUEST_ERROR), - GET_DEPLOYMENT_CONFIGURATION_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - GET_COMPONENT_VERSION_ARTIFACT_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - RESOLVE_COMPONENT_CANDIDATES_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), + CLOUD_API_ERROR(DeploymentErrorType.NONE), BAD_REQUEST(DeploymentErrorType.NUCLEUS_ERROR), ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), THROTTLING_ERROR(DeploymentErrorType.REQUEST_ERROR), SERVER_ERROR( + DeploymentErrorType.SERVER_ERROR), CONFLICTED_REQUEST( + DeploymentErrorType.REQUEST_ERROR), RESOURCE_NOT_FOUND( + DeploymentErrorType.REQUEST_ERROR), GET_DEPLOYMENT_CONFIGURATION_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), GET_COMPONENT_VERSION_ARTIFACT_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), RESOLVE_COMPONENT_CANDIDATES_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), /* Network / http */ - NETWORK_ERROR(DeploymentErrorType.NETWORK_ERROR), - HTTP_REQUEST_ERROR(DeploymentErrorType.HTTP_ERROR), - DOWNLOAD_DEPLOYMENT_DOCUMENT_ERROR(DeploymentErrorType.HTTP_ERROR), - GET_GREENGRASS_ARTIFACT_SIZE_ERROR(DeploymentErrorType.HTTP_ERROR), - DOWNLOAD_GREENGRASS_ARTIFACT_ERROR(DeploymentErrorType.HTTP_ERROR), + NETWORK_ERROR(DeploymentErrorType.NETWORK_ERROR), HTTP_REQUEST_ERROR( + DeploymentErrorType.HTTP_ERROR), DOWNLOAD_DEPLOYMENT_DOCUMENT_ERROR( + DeploymentErrorType.HTTP_ERROR), GET_GREENGRASS_ARTIFACT_SIZE_ERROR( + DeploymentErrorType.HTTP_ERROR), DOWNLOAD_GREENGRASS_ARTIFACT_ERROR( + DeploymentErrorType.HTTP_ERROR), /* IO errors */ IO_ERROR(DeploymentErrorType.NONE), // it could be both recipe parse error or deployment doc error - IO_MAPPING_ERROR(DeploymentErrorType.NONE), - IO_WRITE_ERROR(DeploymentErrorType.DEVICE_ERROR), - IO_READ_ERROR(DeploymentErrorType.DEVICE_ERROR), - DISK_SPACE_CRITICAL(DeploymentErrorType.DEVICE_ERROR), - IO_FILE_ATTRIBUTE_ERROR(DeploymentErrorType.DEVICE_ERROR), - SET_PERMISSION_ERROR(DeploymentErrorType.DEVICE_ERROR), - IO_UNZIP_ERROR(DeploymentErrorType.DEVICE_ERROR), + IO_MAPPING_ERROR(DeploymentErrorType.NONE), IO_WRITE_ERROR(DeploymentErrorType.DEVICE_ERROR), IO_READ_ERROR( + DeploymentErrorType.DEVICE_ERROR), DISK_SPACE_CRITICAL( + DeploymentErrorType.DEVICE_ERROR), IO_FILE_ATTRIBUTE_ERROR( + DeploymentErrorType.DEVICE_ERROR), SET_PERMISSION_ERROR( + DeploymentErrorType.DEVICE_ERROR), IO_UNZIP_ERROR(DeploymentErrorType.DEVICE_ERROR), /* Local file issues */ - LOCAL_RECIPE_NOT_FOUND(DeploymentErrorType.DEVICE_ERROR), - LOCAL_RECIPE_CORRUPTED(DeploymentErrorType.DEVICE_ERROR), - LOCAL_RECIPE_METADATA_NOT_FOUND(DeploymentErrorType.DEVICE_ERROR), + LOCAL_RECIPE_NOT_FOUND(DeploymentErrorType.DEVICE_ERROR), LOCAL_RECIPE_CORRUPTED( + DeploymentErrorType.DEVICE_ERROR), LOCAL_RECIPE_METADATA_NOT_FOUND(DeploymentErrorType.DEVICE_ERROR), // JVM hashing issue HASHING_ALGORITHM_UNAVAILABLE(DeploymentErrorType.DEVICE_ERROR), // Could be a local file issue or a Nucleus issue; we will categorize as the latter for visibility LAUNCH_DIRECTORY_CORRUPTED(DeploymentErrorType.DEVICE_ERROR), /* Component recipe errors */ - RECIPE_PARSE_ERROR(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - RECIPE_METADATA_PARSE_ERROR(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - ARTIFACT_URI_NOT_VALID(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - S3_ARTIFACT_URI_NOT_VALID(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - DOCKER_ARTIFACT_URI_NOT_VALID(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - EMPTY_ARTIFACT_URI(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - EMPTY_ARTIFACT_SCHEME(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - UNSUPPORTED_ARTIFACT_SCHEME(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - RECIPE_MISSING_MANIFEST(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - RECIPE_MISSING_ARTIFACT_HASH_ALGORITHM(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - ARTIFACT_CHECKSUM_MISMATCH(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - COMPONENT_DEPENDENCY_NOT_VALID(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - CONFIG_INTERPOLATE_ERROR(DeploymentErrorType.COMPONENT_RECIPE_ERROR), - COMPONENT_VERSION_NOT_VALID(DeploymentErrorType.COMPONENT_RECIPE_ERROR), + RECIPE_PARSE_ERROR(DeploymentErrorType.COMPONENT_RECIPE_ERROR), RECIPE_METADATA_PARSE_ERROR( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), ARTIFACT_URI_NOT_VALID( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), S3_ARTIFACT_URI_NOT_VALID( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), DOCKER_ARTIFACT_URI_NOT_VALID( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), EMPTY_ARTIFACT_URI( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), EMPTY_ARTIFACT_SCHEME( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), UNSUPPORTED_ARTIFACT_SCHEME( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), RECIPE_MISSING_MANIFEST( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), RECIPE_MISSING_ARTIFACT_HASH_ALGORITHM( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), ARTIFACT_CHECKSUM_MISMATCH( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), COMPONENT_DEPENDENCY_NOT_VALID( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), CONFIG_INTERPOLATE_ERROR( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), COMPONENT_VERSION_NOT_VALID( + DeploymentErrorType.COMPONENT_RECIPE_ERROR), /* Config issues */ - DEVICE_CONFIG_NOT_VALID_FOR_ARTIFACT_DOWNLOAD(DeploymentErrorType.DEVICE_ERROR), - RUN_WITH_CONFIG_NOT_VALID(DeploymentErrorType.REQUEST_ERROR), - UNSUPPORTED_REGION(DeploymentErrorType.REQUEST_ERROR), - IOT_CRED_ENDPOINT_FORMAT_NOT_VALID(DeploymentErrorType.REQUEST_ERROR), - IOT_DATA_ENDPOINT_FORMAT_NOT_VALID(DeploymentErrorType.REQUEST_ERROR), + DEVICE_CONFIG_NOT_VALID_FOR_ARTIFACT_DOWNLOAD(DeploymentErrorType.DEVICE_ERROR), RUN_WITH_CONFIG_NOT_VALID( + DeploymentErrorType.REQUEST_ERROR), UNSUPPORTED_REGION( + DeploymentErrorType.REQUEST_ERROR), IOT_CRED_ENDPOINT_FORMAT_NOT_VALID( + DeploymentErrorType.REQUEST_ERROR), IOT_DATA_ENDPOINT_FORMAT_NOT_VALID( + DeploymentErrorType.REQUEST_ERROR), /* Docker issues */ - DOCKER_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), - GET_ECR_CREDENTIAL_ERROR(DeploymentErrorType.PERMISSION_ERROR), - USER_NOT_AUTHORIZED_FOR_DOCKER(DeploymentErrorType.PERMISSION_ERROR), - DOCKER_SERVICE_UNAVAILABLE(DeploymentErrorType.DEPENDENCY_ERROR), - DOCKER_IMAGE_QUERY_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), - DOCKER_LOGIN_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), - DOCKER_PULL_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), - DOCKER_IMAGE_NOT_VALID(DeploymentErrorType.DEPENDENCY_ERROR), + DOCKER_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), GET_ECR_CREDENTIAL_ERROR( + DeploymentErrorType.PERMISSION_ERROR), USER_NOT_AUTHORIZED_FOR_DOCKER( + DeploymentErrorType.PERMISSION_ERROR), DOCKER_SERVICE_UNAVAILABLE( + DeploymentErrorType.DEPENDENCY_ERROR), DOCKER_IMAGE_QUERY_ERROR( + DeploymentErrorType.DEPENDENCY_ERROR), DOCKER_LOGIN_ERROR( + DeploymentErrorType.DEPENDENCY_ERROR), DOCKER_PULL_ERROR( + DeploymentErrorType.DEPENDENCY_ERROR), DOCKER_IMAGE_NOT_VALID( + DeploymentErrorType.DEPENDENCY_ERROR), /* S3 issues */ - S3_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), - S3_RESOURCE_NOT_FOUND(DeploymentErrorType.DEPENDENCY_ERROR), - S3_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - S3_BAD_REQUEST(DeploymentErrorType.DEPENDENCY_ERROR), - S3_SERVER_ERROR(DeploymentErrorType.SERVER_ERROR), - S3_HEAD_OBJECT_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - S3_HEAD_OBJECT_RESOURCE_NOT_FOUND(DeploymentErrorType.REQUEST_ERROR), - S3_GET_BUCKET_LOCATION_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - S3_GET_BUCKET_LOCATION_RESOURCE_NOT_FOUND(DeploymentErrorType.REQUEST_ERROR), - S3_GET_OBJECT_ACCESS_DENIED(DeploymentErrorType.PERMISSION_ERROR), - S3_GET_OBJECT_RESOURCE_NOT_FOUND(DeploymentErrorType.REQUEST_ERROR), + S3_ERROR(DeploymentErrorType.DEPENDENCY_ERROR), S3_RESOURCE_NOT_FOUND( + DeploymentErrorType.DEPENDENCY_ERROR), S3_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), S3_BAD_REQUEST( + DeploymentErrorType.DEPENDENCY_ERROR), S3_SERVER_ERROR( + DeploymentErrorType.SERVER_ERROR), S3_HEAD_OBJECT_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), S3_HEAD_OBJECT_RESOURCE_NOT_FOUND( + DeploymentErrorType.REQUEST_ERROR), S3_GET_BUCKET_LOCATION_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), S3_GET_BUCKET_LOCATION_RESOURCE_NOT_FOUND( + DeploymentErrorType.REQUEST_ERROR), S3_GET_OBJECT_ACCESS_DENIED( + DeploymentErrorType.PERMISSION_ERROR), S3_GET_OBJECT_RESOURCE_NOT_FOUND( + DeploymentErrorType.REQUEST_ERROR), /* Cloud service errors */ // resolve component candidates returned more than one version - RESOLVE_COMPONENT_CANDIDATES_BAD_RESPONSE(DeploymentErrorType.CLOUD_SERVICE_ERROR), - DEPLOYMENT_DOCUMENT_SIZE_EXCEEDED(DeploymentErrorType.CLOUD_SERVICE_ERROR), - GREENGRASS_ARTIFACT_SIZE_NOT_FOUND(DeploymentErrorType.CLOUD_SERVICE_ERROR), + RESOLVE_COMPONENT_CANDIDATES_BAD_RESPONSE( + DeploymentErrorType.CLOUD_SERVICE_ERROR), DEPLOYMENT_DOCUMENT_SIZE_EXCEEDED( + DeploymentErrorType.CLOUD_SERVICE_ERROR), GREENGRASS_ARTIFACT_SIZE_NOT_FOUND( + DeploymentErrorType.CLOUD_SERVICE_ERROR), /* Errors that could be cloud errors OR nucleus errors */ // An invalid deployment doc is received // it's a nucleus error if local deployment // a cloud service error is cloud deployment - DEPLOYMENT_DOCUMENT_NOT_VALID(DeploymentErrorType.NONE), - EMPTY_DEPLOYMENT_REQUEST(DeploymentErrorType.NONE), - DEPLOYMENT_DOCUMENT_PARSE_ERROR(DeploymentErrorType.NONE), + DEPLOYMENT_DOCUMENT_NOT_VALID(DeploymentErrorType.NONE), EMPTY_DEPLOYMENT_REQUEST( + DeploymentErrorType.NONE), DEPLOYMENT_DOCUMENT_PARSE_ERROR(DeploymentErrorType.NONE), // unknown error since we don't know it's from local or cloud - DEPLOYMENT_TYPE_NOT_VALID(DeploymentErrorType.UNKNOWN_ERROR), - COMPONENT_METADATA_NOT_VALID_IN_DEPLOYMENT(DeploymentErrorType.NONE), + DEPLOYMENT_TYPE_NOT_VALID(DeploymentErrorType.UNKNOWN_ERROR), COMPONENT_METADATA_NOT_VALID_IN_DEPLOYMENT( + DeploymentErrorType.NONE), /* Nucleus errors */ - NUCLEUS_VERSION_NOT_FOUND(DeploymentErrorType.NUCLEUS_ERROR), - NUCLEUS_RESTART_FAILURE(DeploymentErrorType.NUCLEUS_ERROR), - COMPONENT_LOAD_FAILURE(DeploymentErrorType.NUCLEUS_ERROR), + NUCLEUS_VERSION_NOT_FOUND(DeploymentErrorType.NUCLEUS_ERROR), NUCLEUS_RESTART_FAILURE( + DeploymentErrorType.NUCLEUS_ERROR), COMPONENT_LOAD_FAILURE(DeploymentErrorType.NUCLEUS_ERROR), /* Component issues */ - CUSTOM_PLUGIN_NOT_SUPPORTED(DeploymentErrorType.USER_COMPONENT_ERROR), - COMPONENT_UPDATE_ERROR(DeploymentErrorType.NONE), - COMPONENT_BROKEN(DeploymentErrorType.NONE), - REMOVE_COMPONENT_ERROR(DeploymentErrorType.NONE), - COMPONENT_BOOTSTRAP_TIMEOUT(DeploymentErrorType.NONE), - COMPONENT_BOOTSTRAP_ERROR(DeploymentErrorType.NONE), - COMPONENT_CONFIGURATION_NOT_VALID(DeploymentErrorType.NONE); - + CUSTOM_PLUGIN_NOT_SUPPORTED(DeploymentErrorType.USER_COMPONENT_ERROR), COMPONENT_UPDATE_ERROR( + DeploymentErrorType.NONE), COMPONENT_BROKEN(DeploymentErrorType.NONE), REMOVE_COMPONENT_ERROR( + DeploymentErrorType.NONE), COMPONENT_BOOTSTRAP_TIMEOUT( + DeploymentErrorType.NONE), COMPONENT_BOOTSTRAP_ERROR( + DeploymentErrorType.NONE), COMPONENT_CONFIGURATION_NOT_VALID( + DeploymentErrorType.NONE); @Getter private final DeploymentErrorType errorType; diff --git a/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCodeUtils.java b/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCodeUtils.java index 51f8ed84a9..fb3d2fd02b 100644 --- a/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCodeUtils.java +++ b/src/main/java/com/aws/greengrass/deployment/errorcode/DeploymentErrorCodeUtils.java @@ -113,30 +113,32 @@ public static Pair, List> generateErrorReportFromExceptionS List errorStack = errorCodeSet.stream().map(Enum::toString).collect(Collectors.toList()); // remove duplicate types - List errorTypes = Stream.concat(errorTypesFromException.stream(), - errorCodeSet.stream().map(DeploymentErrorCode::getErrorType)).distinct() - .filter(type -> !type.equals(DeploymentErrorType.NONE)).map(Enum::toString) + List errorTypes = Stream + .concat(errorTypesFromException.stream(), errorCodeSet.stream().map(DeploymentErrorCode::getErrorType)) + .distinct() + .filter(type -> !type.equals(DeploymentErrorType.NONE)) + .map(Enum::toString) .collect(Collectors.toList()); return new Pair<>(errorStack, errorTypes); } /** - * Walk through exception chain and generate deployment error report. - * Use deployment type to check if it's a user component error. + * Walk through exception chain and generate deployment error report. Use deployment type to check if it's a user + * component error. * - * @param e exception passed to DeploymentResult + * @param e exception passed to DeploymentResult * @param deploymentType deployment type * @return error code stack and error types in a pair */ public static Pair, List> generateErrorReportFromExceptionStack(Throwable e, - Deployment.DeploymentType deploymentType) { + Deployment.DeploymentType deploymentType) { Pair, List> errorReport = generateErrorReportFromExceptionStack(e); // update error type from deployment type // if it's a local deployment, then a component update error is due to user component error - if (errorReport.getRight().contains(DeploymentErrorType.COMPONENT_ERROR.name()) && deploymentType.equals( - Deployment.DeploymentType.LOCAL)) { + if (errorReport.getRight().contains(DeploymentErrorType.COMPONENT_ERROR.name()) + && deploymentType.equals(Deployment.DeploymentType.LOCAL)) { errorReport.getRight().remove(DeploymentErrorType.COMPONENT_ERROR.name()); errorReport.getRight().add(DeploymentErrorType.USER_COMPONENT_ERROR.name()); } @@ -144,8 +146,7 @@ public static Pair, List> generateErrorReportFromExceptionS } private static void translateExceptionToErrorCode(Set errorCodeSet, Throwable e, - Map errorContext, - List errorTypeList) { + Map errorContext, List errorTypeList) { if (e instanceof DeploymentException) { errorContext.putAll(((DeploymentException) e).getErrorContext()); errorCodeSet.addAll(((DeploymentException) e).getErrorCodes()); @@ -184,7 +185,7 @@ private static void collectErrorCodesFromIOException(Set er } private static void collectErrorCodesFromGreengrassV2DataException(Set errorCodeSet, - GreengrassV2DataException e) { + GreengrassV2DataException e) { errorCodeSet.add(CLOUD_API_ERROR); if (e instanceof ResourceNotFoundException || e.statusCode() == HttpStatusCode.NOT_FOUND) { errorCodeSet.add(RESOURCE_NOT_FOUND); @@ -201,7 +202,6 @@ private static void collectErrorCodesFromGreengrassV2DataException(Set errorCodeSet, S3Exception e) { errorCodeSet.add(S3_ERROR); int s3StatusCode = e.statusCode(); @@ -232,15 +232,15 @@ private static void collectErrorCodesFromServiceLoadException(Set errorCodes) { } public DeploymentException(String message, List errorCodes, - List errorTypes) { + List errorTypes) { super(message); this.errorCodes.addAll(errorCodes); this.errorTypes.addAll(errorTypes); diff --git a/src/main/java/com/aws/greengrass/deployment/exceptions/InvalidRequestException.java b/src/main/java/com/aws/greengrass/deployment/exceptions/InvalidRequestException.java index bd026957e4..a8bd4ff856 100644 --- a/src/main/java/com/aws/greengrass/deployment/exceptions/InvalidRequestException.java +++ b/src/main/java/com/aws/greengrass/deployment/exceptions/InvalidRequestException.java @@ -37,15 +37,14 @@ public InvalidRequestException(String message, DeploymentErrorCode errorCode) { } public InvalidRequestException(String message, DeploymentErrorCode errorCode, - Deployment.DeploymentType deploymentType) { + Deployment.DeploymentType deploymentType) { super(message); super.addErrorCode(DEPLOYMENT_DOCUMENT_NOT_VALID); super.addErrorCode(errorCode); super.addErrorType(DeploymentErrorCodeUtils.getDeploymentRequestErrorType(deploymentType)); } - public InvalidRequestException(String message, Throwable e, - Deployment.DeploymentType deploymentType) { + public InvalidRequestException(String message, Throwable e, Deployment.DeploymentType deploymentType) { super(message, e); super.addErrorCode(DEPLOYMENT_DOCUMENT_NOT_VALID); super.addErrorType(DeploymentErrorCodeUtils.getDeploymentRequestErrorType(deploymentType)); diff --git a/src/main/java/com/aws/greengrass/deployment/exceptions/MissingRequiredCapabilitiesException.java b/src/main/java/com/aws/greengrass/deployment/exceptions/MissingRequiredCapabilitiesException.java index b590944fa7..bc3c9bab26 100644 --- a/src/main/java/com/aws/greengrass/deployment/exceptions/MissingRequiredCapabilitiesException.java +++ b/src/main/java/com/aws/greengrass/deployment/exceptions/MissingRequiredCapabilitiesException.java @@ -5,7 +5,6 @@ package com.aws.greengrass.deployment.exceptions; - import static com.aws.greengrass.deployment.errorcode.DeploymentErrorCode.NUCLEUS_MISSING_REQUIRED_CAPABILITIES; public class MissingRequiredCapabilitiesException extends DeploymentException { diff --git a/src/main/java/com/aws/greengrass/deployment/exceptions/RetryableDeploymentDocumentDownloadException.java b/src/main/java/com/aws/greengrass/deployment/exceptions/RetryableDeploymentDocumentDownloadException.java index 38e1fa10dc..039d0fe269 100644 --- a/src/main/java/com/aws/greengrass/deployment/exceptions/RetryableDeploymentDocumentDownloadException.java +++ b/src/main/java/com/aws/greengrass/deployment/exceptions/RetryableDeploymentDocumentDownloadException.java @@ -32,8 +32,7 @@ public RetryableDeploymentDocumentDownloadException(String message, Throwable e) } @Override - public RetryableDeploymentDocumentDownloadException withErrorContext(Throwable t, - DeploymentErrorCode errorCode) { + public RetryableDeploymentDocumentDownloadException withErrorContext(Throwable t, DeploymentErrorCode errorCode) { super.withErrorContext(t, errorCode); return this; } diff --git a/src/main/java/com/aws/greengrass/deployment/exceptions/ServiceUpdateException.java b/src/main/java/com/aws/greengrass/deployment/exceptions/ServiceUpdateException.java index 42479041a9..99f757694b 100644 --- a/src/main/java/com/aws/greengrass/deployment/exceptions/ServiceUpdateException.java +++ b/src/main/java/com/aws/greengrass/deployment/exceptions/ServiceUpdateException.java @@ -31,8 +31,7 @@ public ServiceUpdateException(String message, DeploymentErrorCode errorCode) { super.addErrorCode(errorCode); } - public ServiceUpdateException(String message, DeploymentErrorCode errorCode, - DeploymentErrorType errorType) { + public ServiceUpdateException(String message, DeploymentErrorCode errorCode, DeploymentErrorType errorType) { super(message); super.addErrorCode(COMPONENT_UPDATE_ERROR); super.addErrorCode(errorCode); @@ -40,15 +39,14 @@ public ServiceUpdateException(String message, DeploymentErrorCode errorCode, } public ServiceUpdateException(String message, Throwable e, DeploymentErrorCode errorCode, - DeploymentErrorType errorType) { + DeploymentErrorType errorType) { super(message, e); super.addErrorCode(COMPONENT_UPDATE_ERROR); super.addErrorCode(errorCode); super.addErrorType(errorType); } - public ServiceUpdateException(Throwable e, DeploymentErrorCode errorCode, - DeploymentErrorType errorType) { + public ServiceUpdateException(Throwable e, DeploymentErrorCode errorCode, DeploymentErrorType errorType) { super(e); super.addErrorCode(COMPONENT_UPDATE_ERROR); super.addErrorCode(errorCode); diff --git a/src/main/java/com/aws/greengrass/deployment/model/ConfigurationUpdateOperation.java b/src/main/java/com/aws/greengrass/deployment/model/ConfigurationUpdateOperation.java index 9fbaf31fd9..2b2ef4f790 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/ConfigurationUpdateOperation.java +++ b/src/main/java/com/aws/greengrass/deployment/model/ConfigurationUpdateOperation.java @@ -27,4 +27,3 @@ public class ConfigurationUpdateOperation { @JsonProperty(RESET_KEY) List pathsToReset; } - diff --git a/src/main/java/com/aws/greengrass/deployment/model/Deployment.java b/src/main/java/com/aws/greengrass/deployment/model/Deployment.java index a55d0ad897..97e448c069 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/Deployment.java +++ b/src/main/java/com/aws/greengrass/deployment/model/Deployment.java @@ -79,7 +79,7 @@ public Deployment(DeploymentType deploymentType, String id, boolean isCancelled) * @param deploymentStage deployment stage, only applicable to deployments which require Kernel restart */ public Deployment(DeploymentDocument deploymentDetails, DeploymentType deploymentType, String id, - DeploymentStage deploymentStage) { + DeploymentStage deploymentStage) { this.deploymentDocumentObj = deploymentDetails; this.deploymentType = deploymentType; this.id = id; @@ -89,13 +89,11 @@ public Deployment(DeploymentDocument deploymentDetails, DeploymentType deploymen // Get the deployment id set by GG cloud from deployment doc; // this is different from the job id for job deployments public String getGreengrassDeploymentId() { - return Objects.nonNull(deploymentDocumentObj) ? deploymentDocumentObj.getDeploymentId() - : null; + return Objects.nonNull(deploymentDocumentObj) ? deploymentDocumentObj.getDeploymentId() : null; } public String getConfigurationArn() { - return Objects.nonNull(deploymentDocumentObj) ? deploymentDocumentObj.getConfigurationArn() - : null; + return Objects.nonNull(deploymentDocumentObj) ? deploymentDocumentObj.getConfigurationArn() : null; } public enum DeploymentType { diff --git a/src/main/java/com/aws/greengrass/deployment/model/DeploymentDocument.java b/src/main/java/com/aws/greengrass/deployment/model/DeploymentDocument.java index d5454a03f4..4a51039fe9 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/DeploymentDocument.java +++ b/src/main/java/com/aws/greengrass/deployment/model/DeploymentDocument.java @@ -38,7 +38,8 @@ * Class to model the deployment configuration coming from cloud, local, or any other sources that can trigger a * deployment. * - *

JSON Annotations are only in tests to easily generate this model from a JSON file. They are not part of business + *

+ * JSON Annotations are only in tests to easily generate this model from a JSON file. They are not part of business * logic. */ @Getter @@ -90,7 +91,6 @@ public class DeploymentDocument { private DeploymentConfigurationValidationPolicy configurationValidationPolicy = DeploymentConfigurationValidationPolicy.builder().build(); - /** * For sub-group deployments root group name is used otherwise group name. * @@ -111,8 +111,10 @@ public List getRootPackages() { if (deploymentPackageConfigurationList == null || deploymentPackageConfigurationList.isEmpty()) { return Collections.emptyList(); } - return deploymentPackageConfigurationList.stream().filter(DeploymentPackageConfiguration::isRootComponent) - .map(DeploymentPackageConfiguration::getPackageName).collect(Collectors.toList()); + return deploymentPackageConfigurationList.stream() + .filter(DeploymentPackageConfiguration::isRootComponent) + .map(DeploymentPackageConfiguration::getPackageName) + .collect(Collectors.toList()); } // Custom serializer for AWS SDK model since Jackson can't figure it out itself @@ -130,8 +132,9 @@ public void serialize(Object value, JsonGenerator gen, SerializerProvider serial } } - private static class SDKDeserializer implements - Converter, DeploymentConfigurationValidationPolicy> { + private static class SDKDeserializer + implements + Converter, DeploymentConfigurationValidationPolicy> { @Override public DeploymentConfigurationValidationPolicy convert(Map value) { diff --git a/src/main/java/com/aws/greengrass/deployment/model/DeploymentPackageConfiguration.java b/src/main/java/com/aws/greengrass/deployment/model/DeploymentPackageConfiguration.java index 9fd1590829..fcba29182d 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/DeploymentPackageConfiguration.java +++ b/src/main/java/com/aws/greengrass/deployment/model/DeploymentPackageConfiguration.java @@ -14,7 +14,6 @@ import lombok.Setter; import lombok.ToString; - /** * Class to represent a single package along with its dependencies that comes in the deployment configuration. */ @@ -46,8 +45,8 @@ public class DeploymentPackageConfiguration { /** * Constructor for no update configuration update. Used for testing * - * @param packageName name of package - * @param rootComponent if it is root + * @param packageName name of package + * @param rootComponent if it is root * @param resolvedVersion resolved version */ public DeploymentPackageConfiguration(String packageName, boolean rootComponent, String resolvedVersion) { @@ -59,10 +58,10 @@ public DeploymentPackageConfiguration(String packageName, boolean rootComponent, /** * Constructor for no legacy configuration. * - * @param packageName name of package - * @param rootComponent if it is root + * @param packageName name of package + * @param rootComponent if it is root * @param resolvedVersion resolved version - * @param configurationUpdateOperation configuration update + * @param configurationUpdateOperation configuration update */ public DeploymentPackageConfiguration(String packageName, boolean rootComponent, String resolvedVersion, ConfigurationUpdateOperation configurationUpdateOperation) { @@ -72,9 +71,9 @@ public DeploymentPackageConfiguration(String packageName, boolean rootComponent, this.configurationUpdateOperation = configurationUpdateOperation; } - /** * Constructor. Non provided fields are null. + * * @param packageName packageName */ public DeploymentPackageConfiguration(String packageName) { diff --git a/src/main/java/com/aws/greengrass/deployment/model/DeploymentResult.java b/src/main/java/com/aws/greengrass/deployment/model/DeploymentResult.java index 84e117588a..1436235f47 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/DeploymentResult.java +++ b/src/main/java/com/aws/greengrass/deployment/model/DeploymentResult.java @@ -20,11 +20,6 @@ public class DeploymentResult { Throwable failureCause; public enum DeploymentStatus { - SUCCESSFUL, - FAILED_NO_STATE_CHANGE, - FAILED_ROLLBACK_NOT_REQUESTED, - FAILED_ROLLBACK_COMPLETE, - FAILED_UNABLE_TO_ROLLBACK, - REJECTED + SUCCESSFUL, FAILED_NO_STATE_CHANGE, FAILED_ROLLBACK_NOT_REQUESTED, FAILED_ROLLBACK_COMPLETE, FAILED_UNABLE_TO_ROLLBACK, REJECTED } } diff --git a/src/main/java/com/aws/greengrass/deployment/model/DeploymentTaskMetadata.java b/src/main/java/com/aws/greengrass/deployment/model/DeploymentTaskMetadata.java index b00860f7fe..ef926db39c 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/DeploymentTaskMetadata.java +++ b/src/main/java/com/aws/greengrass/deployment/model/DeploymentTaskMetadata.java @@ -17,13 +17,16 @@ @AllArgsConstructor public class DeploymentTaskMetadata { // TODO: [P41179644] clean up duplicate information - @NonNull @Getter + @NonNull + @Getter private Deployment deployment; - @NonNull @Getter + @NonNull + @Getter private DeploymentTask deploymentTask; @NonNull private Future deploymentResultFuture; - @NonNull @Getter + @NonNull + @Getter private AtomicInteger deploymentAttemptCount; @Synchronized diff --git a/src/main/java/com/aws/greengrass/deployment/model/FailureHandlingPolicy.java b/src/main/java/com/aws/greengrass/deployment/model/FailureHandlingPolicy.java index 130de8ae4a..c614ed213d 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/FailureHandlingPolicy.java +++ b/src/main/java/com/aws/greengrass/deployment/model/FailureHandlingPolicy.java @@ -6,8 +6,7 @@ package com.aws.greengrass.deployment.model; public enum FailureHandlingPolicy { - ROLLBACK("ROLLBACK"), - DO_NOTHING("DO_NOTHING"); + ROLLBACK("ROLLBACK"), DO_NOTHING("DO_NOTHING"); private final String failureHandlingPolicy; diff --git a/src/main/java/com/aws/greengrass/deployment/model/LocalOverrideRequest.java b/src/main/java/com/aws/greengrass/deployment/model/LocalOverrideRequest.java index c913f9255c..ea5b593191 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/LocalOverrideRequest.java +++ b/src/main/java/com/aws/greengrass/deployment/model/LocalOverrideRequest.java @@ -25,10 +25,10 @@ @Getter @Builder public class LocalOverrideRequest { - String requestId; // UUID + String requestId; // UUID long requestTimestamp; - Map componentsToMerge; // name to version + Map componentsToMerge; // name to version List componentsToRemove; // remove just need name String groupName; List requiredCapabilities; diff --git a/src/main/java/com/aws/greengrass/deployment/model/RunWith.java b/src/main/java/com/aws/greengrass/deployment/model/RunWith.java index d3abdfcd40..025e04deff 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/RunWith.java +++ b/src/main/java/com/aws/greengrass/deployment/model/RunWith.java @@ -33,7 +33,7 @@ public class RunWith { /** * Construct a new instance. * - * @param posixUser posix user value. + * @param posixUser posix user value. * @param windowsUser windows user value. * @param systemResourceLimits system resource limits. */ diff --git a/src/main/java/com/aws/greengrass/deployment/model/S3EndpointType.java b/src/main/java/com/aws/greengrass/deployment/model/S3EndpointType.java index 99224c52eb..df62f1f3fb 100644 --- a/src/main/java/com/aws/greengrass/deployment/model/S3EndpointType.java +++ b/src/main/java/com/aws/greengrass/deployment/model/S3EndpointType.java @@ -6,5 +6,5 @@ package com.aws.greengrass.deployment.model; public enum S3EndpointType { - GLOBAL,REGIONAL,DUALSTACK + GLOBAL, REGIONAL, DUALSTACK } diff --git a/src/main/java/com/aws/greengrass/easysetup/DeviceProvisioningHelper.java b/src/main/java/com/aws/greengrass/easysetup/DeviceProvisioningHelper.java index 98730a49d8..476e009852 100644 --- a/src/main/java/com/aws/greengrass/easysetup/DeviceProvisioningHelper.java +++ b/src/main/java/com/aws/greengrass/easysetup/DeviceProvisioningHelper.java @@ -84,21 +84,12 @@ public class DeviceProvisioningHelper { private static final String GG_TOKEN_EXCHANGE_ROLE_ACCESS_POLICY_SUFFIX = "Access"; private static final String GG_TOKEN_EXCHANGE_ROLE_ACCESS_POLICY_DOCUMENT = - "{\n" + " \"Version\": \"2012-10-17\",\n" - + " \"Statement\": [\n" - + " {\n" - + " \"Effect\": \"Allow\",\n" - + " \"Action\": [\n" - + " \"logs:CreateLogGroup\",\n" - + " \"logs:CreateLogStream\",\n" - + " \"logs:PutLogEvents\",\n" - + " \"logs:DescribeLogStreams\",\n" - + " \"s3:GetBucketLocation\"\n" - + " ],\n" - + " \"Resource\": \"*\"\n" - + " }\n" - + " ]\n" - + "}"; + "{\n" + " \"Version\": \"2012-10-17\",\n" + " \"Statement\": [\n" + " {\n" + + " \"Effect\": \"Allow\",\n" + " \"Action\": [\n" + + " \"logs:CreateLogGroup\",\n" + " \"logs:CreateLogStream\",\n" + + " \"logs:PutLogEvents\",\n" + " \"logs:DescribeLogStreams\",\n" + + " \"s3:GetBucketLocation\"\n" + " ],\n" + + " \"Resource\": \"*\"\n" + " }\n" + " ]\n" + "}"; private static final String IOT_ROLE_POLICY_NAME_PREFIX = "GreengrassTESCertificatePolicy"; private static final String GREENGRASS_CLI_COMPONENT_NAME = "aws.greengrass.Cli"; private static final String INITIAL_DEPLOYMENT_NAME_FORMAT = "Deployment for %s"; @@ -108,11 +99,9 @@ public class DeviceProvisioningHelper { private static final String E2E_TESTS_POLICY_NAME_PREFIX = "E2ETestsIotPolicy"; private static final String E2E_TESTS_THING_NAME_PREFIX = "E2ETestsIotThing"; - private final Map tesServiceEndpoints = ImmutableMap.of( - EnvironmentStage.PROD, "credentials.iot.amazonaws.com", - EnvironmentStage.GAMMA, "credentials.iot.test.amazonaws.com", - EnvironmentStage.BETA, "credentials.iot.test.amazonaws.com" - ); + private final Map tesServiceEndpoints = + ImmutableMap.of(EnvironmentStage.PROD, "credentials.iot.amazonaws.com", EnvironmentStage.GAMMA, + "credentials.iot.test.amazonaws.com", EnvironmentStage.BETA, "credentials.iot.test.amazonaws.com"); private final PrintStream outStream; private final IotClient iotClient; @@ -126,22 +115,23 @@ public class DeviceProvisioningHelper { /** * Constructor for a desired region and stage. * - * @param awsRegion aws region - * @param outStream stream used to provide customer feedback + * @param awsRegion aws region + * @param outStream stream used to provide customer feedback * @param environmentStage {@link EnvironmentStage} - * @throws URISyntaxException when Iot endpoint is malformed + * @throws URISyntaxException when Iot endpoint is malformed * @throws InvalidEnvironmentStageException when the environmentStage passes is invalid */ public DeviceProvisioningHelper(String awsRegion, String environmentStage, PrintStream outStream) throws URISyntaxException, InvalidEnvironmentStageException { this.outStream = outStream; - this.envStage = StringUtils.isEmpty(environmentStage) ? EnvironmentStage.PROD + this.envStage = StringUtils.isEmpty(environmentStage) + ? EnvironmentStage.PROD : EnvironmentStage.fromString(environmentStage); this.iotClient = IotSdkClientFactory.getIotClient(awsRegion, envStage); this.iamClient = IamSdkClientFactory.getIamClient(awsRegion); this.stsClient = StsSdkClientFactory.getStsClient(awsRegion); - this.greengrassClient = GreengrassV2Client.builder().endpointOverride( - URI.create(RegionUtils.getGreengrassControlPlaneEndpoint(awsRegion, this.envStage))) + this.greengrassClient = GreengrassV2Client.builder() + .endpointOverride(URI.create(RegionUtils.getGreengrassControlPlaneEndpoint(awsRegion, this.envStage))) .region(Region.of(awsRegion)) .build(); } @@ -149,14 +139,14 @@ public DeviceProvisioningHelper(String awsRegion, String environmentStage, Print /** * Constructor for unit tests. * - * @param outStream stream to provide customer feedback - * @param iotClient iot client - * @param iamClient iam client - * @param stsClient sts client + * @param outStream stream to provide customer feedback + * @param iotClient iot client + * @param iamClient iam client + * @param stsClient sts client * @param greengrassClient Greengrass client */ - DeviceProvisioningHelper(PrintStream outStream, IotClient iotClient, IamClient iamClient, - StsClient stsClient, GreengrassV2Client greengrassClient) { + DeviceProvisioningHelper(PrintStream outStream, IotClient iotClient, IamClient iamClient, StsClient stsClient, + GreengrassV2Client greengrassClient) { this.outStream = outStream; this.iotClient = iotClient; this.iamClient = iamClient; @@ -177,40 +167,42 @@ public ThingInfo createThingForE2ETests() { /** * Create a thing with provided configuration. * - * @param client iotClient to use + * @param client iotClient to use * @param policyName policyName - * @param thingName thingName - * @param iotDataEndpoint iotDataEndpoint - * @param iotCredEndpoint iotCredEndpoint + * @param thingName thingName + * @param iotDataEndpoint iotDataEndpoint + * @param iotCredEndpoint iotCredEndpoint * @return created thing info */ @SuppressWarnings("PMD.UseObjectForClearerAPI") - public ThingInfo createThing(IotClient client, String policyName, String thingName, - String iotDataEndpoint, String iotCredEndpoint) { + public ThingInfo createThing(IotClient client, String policyName, String thingName, String iotDataEndpoint, + String iotCredEndpoint) { // Find or create IoT policy try { client.getPolicy(GetPolicyRequest.builder().policyName(policyName).build()); outStream.printf("Found IoT policy \"%s\", reusing it%n", policyName); } catch (ResourceNotFoundException e) { outStream.printf("Creating new IoT policy \"%s\"%n", policyName); - client.createPolicy(CreatePolicyRequest.builder().policyName(policyName).policyDocument( - "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n" + client.createPolicy(CreatePolicyRequest.builder() + .policyName(policyName) + .policyDocument("{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n" + " \"Effect\": \"Allow\",\n \"Action\": [\n" + " \"iot:Connect\",\n \"iot:Publish\",\n" + " \"iot:Subscribe\",\n \"iot:Receive\",\n" - + " \"greengrass:*\"\n],\n" - + " \"Resource\": \"*\"\n }\n ]\n}") + + " \"greengrass:*\"\n],\n" + " \"Resource\": \"*\"\n }\n ]\n}") .build()); } // handle endpoints if (Utils.isEmpty(iotDataEndpoint)) { - iotDataEndpoint = client.describeEndpoint(DescribeEndpointRequest.builder() - .endpointType("iot:Data-ATS").build()).endpointAddress(); + iotDataEndpoint = + client.describeEndpoint(DescribeEndpointRequest.builder().endpointType("iot:Data-ATS").build()) + .endpointAddress(); } if (Utils.isEmpty(iotCredEndpoint)) { - iotCredEndpoint = client.describeEndpoint(DescribeEndpointRequest.builder() - .endpointType("iot:CredentialProvider").build()).endpointAddress(); + iotCredEndpoint = client + .describeEndpoint(DescribeEndpointRequest.builder().endpointType("iot:CredentialProvider").build()) + .endpointAddress(); } // Create cert @@ -227,9 +219,10 @@ public ThingInfo createThing(IotClient client, String policyName, String thingNa outStream.printf("Creating IoT Thing \"%s\"...%n", thingName); String thingArn = client.createThing(CreateThingRequest.builder().thingName(thingName).build()).thingArn(); outStream.println("Attaching certificate to IoT thing..."); - client.attachThingPrincipal( - AttachThingPrincipalRequest.builder().thingName(thingName).principal(keyResponse.certificateArn()) - .build()); + client.attachThingPrincipal(AttachThingPrincipalRequest.builder() + .thingName(thingName) + .principal(keyResponse.certificateArn()) + .build()); return new ThingInfo(thingArn, thingName, keyResponse.certificateArn(), keyResponse.certificateId(), keyResponse.certificatePem(), keyResponse.keyPair(), iotDataEndpoint, iotCredEndpoint); @@ -238,17 +231,20 @@ public ThingInfo createThing(IotClient client, String policyName, String thingNa /** * Clean up an existing thing from AWS account using the provided client. * - * @param client iotClient to use - * @param thing thing info + * @param client iotClient to use + * @param thing thing info * @param deletePolicies true if iot policies should be deleted */ public void cleanThing(IotClient client, ThingInfo thing, boolean deletePolicies) { - client.detachThingPrincipal( - DetachThingPrincipalRequest.builder().thingName(thing.thingName).principal(thing.certificateArn) - .build()); + client.detachThingPrincipal(DetachThingPrincipalRequest.builder() + .thingName(thing.thingName) + .principal(thing.certificateArn) + .build()); client.deleteThing(DeleteThingRequest.builder().thingName(thing.thingName).build()); - client.updateCertificate(UpdateCertificateRequest.builder().certificateId(thing.certificateId) - .newStatus(CertificateStatus.INACTIVE).build()); + client.updateCertificate(UpdateCertificateRequest.builder() + .certificateId(thing.certificateId) + .newStatus(CertificateStatus.INACTIVE) + .build()); for (Policy p : client .listAttachedPolicies(ListAttachedPoliciesRequest.builder().target(thing.certificateArn).build()) .policies()) { @@ -265,17 +261,16 @@ public void cleanThing(IotClient client, ThingInfo thing, boolean deletePolicies /** * Update the kernel config with iot thing info, in specific CA, private Key and cert path. * - * @param kernel Kernel instance - * @param thing thing info - * @param awsRegion aws region + * @param kernel Kernel instance + * @param thing thing info + * @param awsRegion aws region * @param roleAliasName role alias for using IoT credentials endpoint * @param userCertPath the path of certificates which users specify - * @throws IOException Exception while reading root CA from file + * @throws IOException Exception while reading root CA from file * @throws DeviceConfigurationException when the configuration parameters are not valid */ public void updateKernelConfigWithIotConfiguration(Kernel kernel, ThingInfo thing, String awsRegion, - String roleAliasName, String userCertPath) - throws IOException, DeviceConfigurationException { + String roleAliasName, String userCertPath) throws IOException, DeviceConfigurationException { Path certPath = kernel.getNucleusPaths().rootPath(); if (!Utils.isEmpty(userCertPath)) { @@ -301,9 +296,9 @@ public void updateKernelConfigWithIotConfiguration(Kernel kernel, ThingInfo thin Path certFilePath = certPath.resolve("thingCert.crt"); Files.write(certFilePath, thing.certificatePem.getBytes(StandardCharsets.UTF_8)); - new DeviceConfiguration(kernel.getConfig(), kernel.getKernelCommandLine(), - thing.thingName, thing.dataEndpoint, thing.credEndpoint, privKeyFilePath.toString(), - certFilePath.toString(), caFilePath.toString(), awsRegion, roleAliasName); + new DeviceConfiguration(kernel.getConfig(), kernel.getKernelCommandLine(), thing.thingName, thing.dataEndpoint, + thing.credEndpoint, privKeyFilePath.toString(), certFilePath.toString(), caFilePath.toString(), + awsRegion, roleAliasName); // Make sure tlog persists the device configuration kernel.getContext().waitForPublishQueueToClear(); outStream.println("Created device configuration"); @@ -312,8 +307,8 @@ public void updateKernelConfigWithIotConfiguration(Kernel kernel, ThingInfo thin /** * Create IoT role for using TES. * - * @param roleName rolaName - * @param roleAliasName roleAlias name + * @param roleName rolaName + * @param roleAliasName roleAlias name * @param certificateArn certificate arn for the IoT thing */ public void setupIoTRoleForTes(String roleName, String roleAliasName, String certificateArn) { @@ -333,12 +328,15 @@ public void setupIoTRoleForTes(String roleName, String roleAliasName, String cer roleArn = iamClient.getRole(getRoleRequest).role().arn(); } catch (NoSuchEntityException | ResourceNotFoundException rnfe) { outStream.printf("TES role \"%s\" does not exist, creating role...%n", roleName); - CreateRoleRequest createRoleRequest = CreateRoleRequest.builder().roleName(roleName).description( - "Role for Greengrass IoT things to interact with AWS services using token exchange service") + CreateRoleRequest createRoleRequest = CreateRoleRequest.builder() + .roleName(roleName) + .description( + "Role for Greengrass IoT things to interact with AWS services using token exchange service") .assumeRolePolicyDocument("{\n \"Version\": \"2012-10-17\",\n" + " \"Statement\": [\n {\n \"Effect\": \"Allow\",\n" + " \"Principal\": {\n \"Service\": \"" + tesServiceEndpoints.get(envStage) - + "\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}").build(); + + "\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}") + .build(); roleArn = iamClient.createRole(createRoleRequest).role().arn(); } @@ -354,10 +352,12 @@ public void setupIoTRoleForTes(String roleName, String roleAliasName, String cer } catch (ResourceNotFoundException e) { outStream.printf("IoT role policy \"%s\" for TES Role alias not exist, creating policy...%n", iotRolePolicyName); - CreatePolicyRequest createPolicyRequest = CreatePolicyRequest.builder().policyName(iotRolePolicyName) + CreatePolicyRequest createPolicyRequest = CreatePolicyRequest.builder() + .policyName(iotRolePolicyName) .policyDocument("{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": {\n" + "\t\t\"Effect\": \"Allow\",\n\t\t\"Action\": \"iot:AssumeRoleWithCertificate\",\n" - + "\t\t\"Resource\": \"" + roleAliasArn + "\"\n\t}\n}").build(); + + "\t\t\"Resource\": \"" + roleAliasArn + "\"\n\t}\n}") + .build(); iotClient.createPolicy(createPolicyRequest); } @@ -370,7 +370,7 @@ public void setupIoTRoleForTes(String roleName, String roleAliasName, String cer /** * Creates IAM policy using specified name and document. Attach the policy to given IAM role name. * - * @param roleName name of target role + * @param roleName name of target role * @param awsRegion aws region * @return ARN of created policy */ @@ -382,24 +382,27 @@ public Optional createAndAttachRolePolicy(String roleName, Region awsReg /** * Creates IAM policy using specified name and document. Attach the policy to given IAM role name. * - * @param roleName name of target role - * @param rolePolicyName name of policy to create and attach + * @param roleName name of target role + * @param rolePolicyName name of policy to create and attach * @param rolePolicyDocument document of policy to create and attach - * @param awsRegion aws region + * @param awsRegion aws region * @return ARN of created policy */ public Optional createAndAttachRolePolicy(String roleName, String rolePolicyName, String rolePolicyDocument, - Region awsRegion) { + Region awsRegion) { Optional tesRolePolicyArnOptional = getPolicyArn(rolePolicyName, awsRegion); if (tesRolePolicyArnOptional.isPresent()) { - outStream.printf("IAM policy named \"%s\" already exists. Please attach it to the IAM role if not " - + "already%n", rolePolicyName); + outStream.printf( + "IAM policy named \"%s\" already exists. Please attach it to the IAM role if not " + "already%n", + rolePolicyName); return tesRolePolicyArnOptional; } else { String tesRolePolicyArn; - CreatePolicyResponse createPolicyResponse = iamClient.createPolicy( - software.amazon.awssdk.services.iam.model.CreatePolicyRequest.builder().policyName(rolePolicyName) - .policyDocument(rolePolicyDocument).build()); + CreatePolicyResponse createPolicyResponse = + iamClient.createPolicy(software.amazon.awssdk.services.iam.model.CreatePolicyRequest.builder() + .policyName(rolePolicyName) + .policyDocument(rolePolicyDocument) + .build()); tesRolePolicyArn = createPolicyResponse.policy().arn(); outStream.printf("IAM role policy for TES \"%s\" created. This policy DOES NOT have S3 access, please " + "modify it with your private components' artifact buckets/objects as needed when you " @@ -416,8 +419,8 @@ private Optional getPolicyArn(String policyName, Region awsRegion) { try { // Check if a managed policy exists with the name return Optional.of(iamClient.getPolicy(software.amazon.awssdk.services.iam.model.GetPolicyRequest.builder() - .policyArn(String.format(MANAGED_IAM_POLICY_ARN_FORMAT, partition, policyName)).build()).policy() - .arn()); + .policyArn(String.format(MANAGED_IAM_POLICY_ARN_FORMAT, partition, policyName)) + .build()).policy().arn()); } catch (NoSuchEntityException mnf) { outStream.println("No managed IAM policy found, looking for user defined policy..."); } catch (IamException e) { @@ -433,8 +436,8 @@ private Optional getPolicyArn(String policyName, Region awsRegion) { // Check if a customer policy exists with the name try { return Optional.of(iamClient.getPolicy(software.amazon.awssdk.services.iam.model.GetPolicyRequest.builder() - .policyArn(String.format(IAM_POLICY_ARN_FORMAT, partition, getAccountId(), policyName)).build()) - .policy().arn()); + .policyArn(String.format(IAM_POLICY_ARN_FORMAT, partition, getAccountId(), policyName)) + .build()).policy().arn()); } catch (NoSuchEntityException cnf) { outStream.println("No IAM policy found, will attempt creating one..."); } catch (IamException e) { @@ -458,11 +461,10 @@ private String getAccountId() { } /** - * Add an existing Thing into a Thing Group which may or may not exist, - * creates thing group if it doesn't exist. + * Add an existing Thing into a Thing Group which may or may not exist, creates thing group if it doesn't exist. * - * @param iotClient client - * @param thingName thing name + * @param iotClient client + * @param thingName thing name * @param thingGroupName group to add the thing into */ public void addThingToGroup(IotClient iotClient, String thingName, String thingGroupName) { @@ -499,13 +501,16 @@ public void createInitialDeploymentIfNeeded(ThingInfo thingInfo, String thingGro return; } - CreateDeploymentRequest.Builder deploymentRequest = CreateDeploymentRequest.builder().deploymentPolicies( - DeploymentPolicies.builder().configurationValidationPolicy( - DeploymentConfigurationValidationPolicy.builder().timeoutInSeconds(60).build()) + CreateDeploymentRequest.Builder deploymentRequest = CreateDeploymentRequest.builder() + .deploymentPolicies(DeploymentPolicies.builder() + .configurationValidationPolicy( + DeploymentConfigurationValidationPolicy.builder().timeoutInSeconds(60).build()) .componentUpdatePolicy(DeploymentComponentUpdatePolicy.builder() .action(DeploymentComponentUpdatePolicyAction.NOTIFY_COMPONENTS) - .timeoutInSeconds(60).build()) - .failureHandlingPolicy(DeploymentFailureHandlingPolicy.DO_NOTHING).build()); + .timeoutInSeconds(60) + .build()) + .failureHandlingPolicy(DeploymentFailureHandlingPolicy.DO_NOTHING) + .build()); if (Utils.isNotEmpty(thingGroupName)) { outStream.println("Creating a deployment for Greengrass first party components to the thing group"); diff --git a/src/main/java/com/aws/greengrass/easysetup/GreengrassSetup.java b/src/main/java/com/aws/greengrass/easysetup/GreengrassSetup.java index 880052841c..2e6d6d0aa3 100644 --- a/src/main/java/com/aws/greengrass/easysetup/GreengrassSetup.java +++ b/src/main/java/com/aws/greengrass/easysetup/GreengrassSetup.java @@ -44,7 +44,6 @@ import static com.aws.greengrass.easysetup.DeviceProvisioningHelper.ThingInfo; import static com.aws.greengrass.lifecyclemanager.GreengrassService.SERVICES_NAMESPACE_TOPIC; - /** * Easy setup for getting started with Greengrass kernel on a device. */ @@ -52,18 +51,15 @@ public class GreengrassSetup { private static final String SHOW_HELP_RESPONSE = "DESCRIPTION\n" + "\tInstall the Greengrass Nucleus, (optional) install local development tools, and (optional)\n" + "\tregister your device as an AWS IoT thing. This creates device certificates, attaches a role\n" - + "\tto use the AWS IoT credentials provider, and creates a role that provides AWS credentials.\n" - + "\n" - + "OPTIONS\n" - + "\t--help, -h\t\t\t(Optional) Show this help information and then exit.\n" + + "\tto use the AWS IoT credentials provider, and creates a role that provides AWS credentials.\n" + "\n" + + "OPTIONS\n" + "\t--help, -h\t\t\t(Optional) Show this help information and then exit.\n" + "\t--version\t\t\t(Optional) Show the version of the AWS IoT Greengrass Core software, and then exit.\n" + "\t--aws-region, -ar\t\t\tThe AWS Region to use. The AWS IoT Greengrass Core software uses this Region\n" + "\t\t\t\t\t to retrieve or create the AWS resources that it requires\n" + "\t--root, -r\t\t\t(Optional) The path to the folder to use as the root for the AWS IoT Greengrass Core\n" + "\t\t\t\t\tsoftware. Defaults to ~/.greengrass.\n" + "\t--init-config, -init\t\t\t(Optional) The path to the configuration file that you use to run the AWS " - + "IoT Greengrass Core software.\n" - + "\t\t\t\t\tsoftware. Defaults to ~/.greengrass.\n" + + "IoT Greengrass Core software.\n" + "\t\t\t\t\tsoftware. Defaults to ~/.greengrass.\n" + "\t--provision, -p\t\t\t(Optional) Specify true or false. If true, the AWS IoT Greengrass Core software" + " registers this\n" + "\t\t\t\t\tdevice as an AWS IoT thing, and provisions the AWS resources that the software requires. The\n" @@ -101,8 +97,7 @@ public class GreengrassSetup { + "\t--setup-system-service, -ss\t(Optional) Specify true or false. If true, then the AWS IoT Greengrass " + "Core software sets\n" + "\t\t\t\t\titself up as a system service that runs when this device boots. The system service name is " - + "greengrass.\n" - + "\t\t\t\t\tDefaults to false.\n" + + "greengrass.\n" + "\t\t\t\t\tDefaults to false.\n" + "\t--component-default-user, -u\t(Optional) The name of ID of the system user and group that the AWS " + "IoT Greengrass Core\n" + "\t\t\t\t\tsoftware uses to run components. This argument accepts the user and group separated by a\n" @@ -124,8 +119,7 @@ public class GreengrassSetup { + "\n\t--start, -s\t\t\t(Optional) Specify true or false. If true, the AWS IoT Greengrass Core software " + "runs setup steps,\n" + "\t\t\t\t\t(optional) provisions resources, and starts the software. If false, the software runs only " - + "setup\n" - + "\t\t\t\t\tsteps and (optional) provisions resources. Defaults to true.\n" + + "setup\n" + "\t\t\t\t\tsteps and (optional) provisions resources. Defaults to true.\n" + "\n\t--trusted-plugin, -tp\t\t(Optional) Path of a plugin jar file. The plugin will be included as " + "trusted plugin in nucleus. Specify multiple times for including multiple plugins.\n" + "\n\t--cert-path\t\t\t(Optional) Path where certificates and keys are written " @@ -201,10 +195,10 @@ public class GreengrassSetup { private static final String DEFAULT_POSIX_USER = String.format("%s:%s", GGC_USER, GGC_GROUP); private static final Logger logger = LogManager.getLogger(GreengrassSetup.class); - private static final String TRUSTED_PLUGIN_PATH_NON_JAR_ERROR - = "The trusted plugin path should point to a jar file"; - private static final String TRUSTED_PLUGIN_JAR_DOES_NOT_EXIST - = "The trusted plugin jar file does not exist or is not accessible"; + private static final String TRUSTED_PLUGIN_PATH_NON_JAR_ERROR = + "The trusted plugin path should point to a jar file"; + private static final String TRUSTED_PLUGIN_JAR_DOES_NOT_EXIST = + "The trusted plugin jar file does not exist or is not accessible"; private final String[] setupArgs; private final List kernelArgs = new ArrayList<>(); @Setter @@ -248,15 +242,15 @@ public GreengrassSetup(PrintStream outStream, PrintStream errStream, String... s /** * Constructor for unit tests. * - * @param outStream writer to use to send text response to user - * @param errStream writer to use to send error response to user + * @param outStream writer to use to send text response to user + * @param errStream writer to use to send error response to user * @param deviceProvisioningHelper Prebuilt DeviceProvisioningHelper instance - * @param platform a platform to use - * @param kernel a kernel instance - * @param setupArgs CLI args for setup script + * @param platform a platform to use + * @param kernel a kernel instance + * @param setupArgs CLI args for setup script */ GreengrassSetup(PrintStream outStream, PrintStream errStream, DeviceProvisioningHelper deviceProvisioningHelper, - Platform platform, Kernel kernel, String... setupArgs) { + Platform platform, Kernel kernel, String... setupArgs) { this.setupArgs = setupArgs; this.outStream = outStream; this.errStream = errStream; @@ -271,8 +265,9 @@ public GreengrassSetup(PrintStream outStream, PrintStream errStream, String... s * @param args CLI args for setup script * @throws Exception error in setup */ - @SuppressWarnings( - {"PMD.NullAssignment", "PMD.AvoidCatchingThrowable", "PMD.DoNotCallSystemExit", "PMD.SystemPrintln"}) + @SuppressWarnings({ + "PMD.NullAssignment", "PMD.AvoidCatchingThrowable", "PMD.DoNotCallSystemExit", "PMD.SystemPrintln" + }) public static void main(String[] args) { GreengrassSetup greengrassSetup = new GreengrassSetup(System.out, System.err, args); try { @@ -286,8 +281,8 @@ public static void main(String[] args) { } } - void performSetup() throws IOException, DeviceConfigurationException, URISyntaxException, - InvalidEnvironmentStageException { + void performSetup() + throws IOException, DeviceConfigurationException, URISyntaxException, InvalidEnvironmentStageException { // Describe usage of the command if (showHelp) { outStream.println(SHOW_HELP_RESPONSE); @@ -296,15 +291,15 @@ void performSetup() throws IOException, DeviceConfigurationException, URISyntaxE if (showVersion) { // Use getVersionFromBuildMetadataFile so that we don't need to startup the Nucleus which is slow and will // start creating files and directories which may not be desired - outStream.println(String.format(SHOW_VERSION_RESPONSE, - DeviceConfiguration.getVersionFromBuildRecipeFile())); + outStream + .println(String.format(SHOW_VERSION_RESPONSE, DeviceConfiguration.getVersionFromBuildRecipeFile())); return; } if (kernel == null) { kernel = new Kernel(); } - kernel.parseArgs(kernelArgs.toArray(new String[]{})); + kernel.parseArgs(kernelArgs.toArray(new String[] {})); try { IotSdkClientFactory.EnvironmentStage.fromString(environmentStage); @@ -333,7 +328,9 @@ void performSetup() throws IOException, DeviceConfigurationException, URISyntaxE if (setupSystemService) { kernel.getContext().get(KernelLifecycle.class).softShutdown(30); - boolean ok = kernel.getContext().get(SystemServiceUtilsFactory.class).getInstance() + boolean ok = kernel.getContext() + .get(SystemServiceUtilsFactory.class) + .getInstance() .setupSystemService(kernel.getContext().get(KernelAlternatives.class), kernel.getNucleusPaths(), kernelStart); if (ok) { @@ -358,12 +355,12 @@ void performSetup() throws IOException, DeviceConfigurationException, URISyntaxE private void copyTrustedPlugins(Kernel kernel, List trustedPluginPaths) { Path trustedPluginPath; try { - trustedPluginPath = kernel.getContext().get(EZPlugins.class) + trustedPluginPath = kernel.getContext() + .get(EZPlugins.class) .withCacheDirectory(kernel.getNucleusPaths().pluginPath()) .getTrustedCacheDirectory(); } catch (IOException e) { - logger.atError().setCause(e) - .log("Caught exception while getting trusted plugins directory path"); + logger.atError().setCause(e).log("Caught exception while getting trusted plugins directory path"); throw new RuntimeException(e); } trustedPluginPaths.forEach(pluginPath -> { @@ -371,7 +368,9 @@ private void copyTrustedPlugins(Kernel kernel, List trustedPluginPaths) Files.copy(Paths.get(pluginPath), trustedPluginPath.resolve(Utils.namePart(pluginPath)), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - logger.atError().kv("pluginPath", pluginPath).setCause(e) + logger.atError() + .kv("pluginPath", pluginPath) + .setCause(e) .log("Caught exception while copying plugin jar to trusted plugins directory"); throw new RuntimeException(e); } @@ -381,102 +380,101 @@ private void copyTrustedPlugins(Kernel kernel, List trustedPluginPaths) void parseArgs() { loop: while (getArg() != null) { switch (arg.toLowerCase()) { - case HELP_ARG: - case HELP_ARG_SHORT: - this.showHelp = true; - break loop; - case VERSION_ARG: - case VERSION_ARG_SHORT: - this.showVersion = true; - break loop; - case KERNEL_CONFIG_ARG: - case KERNEL_CONFIG_ARG_SHORT: - case KERNEL_ROOT_ARG: - case KERNEL_ROOT_ARG_SHORT: - case KERNEL_INIT_CONFIG_ARG: - case KERNEL_INIT_CONFIG_ARG_SHORT: - kernelArgs.add(arg); - kernelArgs.add(getArg()); - break; - case THING_NAME_ARG: - case THING_NAME_ARG_SHORT: - this.thingName = getArg(); - break; - case THING_GROUP_NAME_ARG: - case THING_GROUP_NAME_ARG_SHORT: - this.thingGroupName = getArg(); - break; - case THING_POLICY_NAME_ARG: - case THING_POLICY_NAME_ARG_SHORT: - this.thingPolicyName = getArg(); - break; - case TES_ROLE_NAME_ARG: - case TES_ROLE_NAME_ARG_SHORT: - this.tesRoleName = getArg(); - break; - case TES_ROLE_ALIAS_NAME_ARG: - case TES_ROLE_ALIAS_NAME_ARG_SHORT: - this.tesRoleAliasName = getArg(); - break; - case AWS_REGION_ARG: - case AWS_REGION_ARG_SHORT: - kernelArgs.add(arg); - this.awsRegion = getArg(); - if (!Region.regions().contains(Region.of(awsRegion))) { - throw new RuntimeException(String.format("%s is invalid AWS region", awsRegion)); - } - kernelArgs.add(awsRegion); - break; - - case ENV_STAGE_ARG: - case ENV_STAGE_ARG_SHORT: - kernelArgs.add(arg); - this.environmentStage = getArg(); - kernelArgs.add(environmentStage.toLowerCase()); - break; - case PROVISION_THING_ARG: - case PROVISION_THING_ARG_SHORT: - this.needProvisioning = parseBooleanArg(); - break; - case SETUP_SYSTEM_SERVICE_ARG: - case SETUP_SYSTEM_SERVICE_ARG_SHORT: - this.setupSystemService = parseBooleanArg(); - break; - case KERNEL_START_ARG: - case KERNEL_START_ARG_SHORT: - this.kernelStart = parseBooleanArg(); - break; - case DEFAULT_USER_ARG: - case DEFAULT_USER_ARG_SHORT: - String argument = arg; - kernelArgs.add(argument); - this.defaultUser = Coerce.toString(getArg()); - if (Utils.isEmpty(defaultUser)) { - throw new RuntimeException(String.format("No user specified with %s", argument)); - } - kernelArgs.add(defaultUser); - break; - case DEPLOY_DEV_TOOLS_ARG: - case DEPLOY_DEV_TOOLS_ARG_SHORT: - this.deployDevTools = parseBooleanArg(); - break; - case TRUSTED_PLUGIN_ARG: - case TRUSTED_PLUGIN_ARG_SHORT: - String pluginJarPath = Coerce.toString(getArg()); - validatePluginJarPath(pluginJarPath); - if (trustedPluginPaths == null) { - trustedPluginPaths = new ArrayList<>(); - } - trustedPluginPaths.add(pluginJarPath); - break; - case CERT_PATH_ARG: - this.certPath = getArg(); - break; - default: - RuntimeException rte = - new RuntimeException(String.format("Undefined command line argument: %s", arg)); - logger.atError().setEventType("parse-args-error").setCause(rte).log(); - throw rte; + case HELP_ARG: + case HELP_ARG_SHORT: + this.showHelp = true; + break loop; + case VERSION_ARG: + case VERSION_ARG_SHORT: + this.showVersion = true; + break loop; + case KERNEL_CONFIG_ARG: + case KERNEL_CONFIG_ARG_SHORT: + case KERNEL_ROOT_ARG: + case KERNEL_ROOT_ARG_SHORT: + case KERNEL_INIT_CONFIG_ARG: + case KERNEL_INIT_CONFIG_ARG_SHORT: + kernelArgs.add(arg); + kernelArgs.add(getArg()); + break; + case THING_NAME_ARG: + case THING_NAME_ARG_SHORT: + this.thingName = getArg(); + break; + case THING_GROUP_NAME_ARG: + case THING_GROUP_NAME_ARG_SHORT: + this.thingGroupName = getArg(); + break; + case THING_POLICY_NAME_ARG: + case THING_POLICY_NAME_ARG_SHORT: + this.thingPolicyName = getArg(); + break; + case TES_ROLE_NAME_ARG: + case TES_ROLE_NAME_ARG_SHORT: + this.tesRoleName = getArg(); + break; + case TES_ROLE_ALIAS_NAME_ARG: + case TES_ROLE_ALIAS_NAME_ARG_SHORT: + this.tesRoleAliasName = getArg(); + break; + case AWS_REGION_ARG: + case AWS_REGION_ARG_SHORT: + kernelArgs.add(arg); + this.awsRegion = getArg(); + if (!Region.regions().contains(Region.of(awsRegion))) { + throw new RuntimeException(String.format("%s is invalid AWS region", awsRegion)); + } + kernelArgs.add(awsRegion); + break; + + case ENV_STAGE_ARG: + case ENV_STAGE_ARG_SHORT: + kernelArgs.add(arg); + this.environmentStage = getArg(); + kernelArgs.add(environmentStage.toLowerCase()); + break; + case PROVISION_THING_ARG: + case PROVISION_THING_ARG_SHORT: + this.needProvisioning = parseBooleanArg(); + break; + case SETUP_SYSTEM_SERVICE_ARG: + case SETUP_SYSTEM_SERVICE_ARG_SHORT: + this.setupSystemService = parseBooleanArg(); + break; + case KERNEL_START_ARG: + case KERNEL_START_ARG_SHORT: + this.kernelStart = parseBooleanArg(); + break; + case DEFAULT_USER_ARG: + case DEFAULT_USER_ARG_SHORT: + String argument = arg; + kernelArgs.add(argument); + this.defaultUser = Coerce.toString(getArg()); + if (Utils.isEmpty(defaultUser)) { + throw new RuntimeException(String.format("No user specified with %s", argument)); + } + kernelArgs.add(defaultUser); + break; + case DEPLOY_DEV_TOOLS_ARG: + case DEPLOY_DEV_TOOLS_ARG_SHORT: + this.deployDevTools = parseBooleanArg(); + break; + case TRUSTED_PLUGIN_ARG: + case TRUSTED_PLUGIN_ARG_SHORT: + String pluginJarPath = Coerce.toString(getArg()); + validatePluginJarPath(pluginJarPath); + if (trustedPluginPaths == null) { + trustedPluginPaths = new ArrayList<>(); + } + trustedPluginPaths.add(pluginJarPath); + break; + case CERT_PATH_ARG: + this.certPath = getArg(); + break; + default: + RuntimeException rte = new RuntimeException(String.format("Undefined command line argument: %s", arg)); + logger.atError().setEventType("parse-args-error").setCause(rte).log(); + throw rte; } } } @@ -525,10 +523,12 @@ void provision(Kernel kernel) throws IOException, DeviceConfigurationException { outStream.printf("Provisioning AWS IoT resources for the device with IoT Thing Name: [%s]...%n", thingName); // handle endpoints provided by external config - String iotDataEndpoint = Coerce.toString(kernel.getConfig().find(SERVICES_NAMESPACE_TOPIC, - DEFAULT_NUCLEUS_COMPONENT_NAME, CONFIGURATION_CONFIG_KEY, DEVICE_PARAM_IOT_DATA_ENDPOINT)); - String iotCredEndpoint = Coerce.toString(kernel.getConfig().find(SERVICES_NAMESPACE_TOPIC, - DEFAULT_NUCLEUS_COMPONENT_NAME, CONFIGURATION_CONFIG_KEY, DEVICE_PARAM_IOT_CRED_ENDPOINT)); + String iotDataEndpoint = Coerce.toString(kernel.getConfig() + .find(SERVICES_NAMESPACE_TOPIC, DEFAULT_NUCLEUS_COMPONENT_NAME, CONFIGURATION_CONFIG_KEY, + DEVICE_PARAM_IOT_DATA_ENDPOINT)); + String iotCredEndpoint = Coerce.toString(kernel.getConfig() + .find(SERVICES_NAMESPACE_TOPIC, DEFAULT_NUCLEUS_COMPONENT_NAME, CONFIGURATION_CONFIG_KEY, + DEVICE_PARAM_IOT_CRED_ENDPOINT)); final ThingInfo thingInfo = deviceProvisioningHelper.createThing(deviceProvisioningHelper.getIotClient(), thingPolicyName, thingName, iotDataEndpoint, iotCredEndpoint); @@ -537,8 +537,8 @@ void provision(Kernel kernel) throws IOException, DeviceConfigurationException { thingName); if (!Utils.isEmpty(thingGroupName)) { outStream.printf("Adding IoT Thing [%s] into Thing Group: [%s]...%n", thingName, thingGroupName); - deviceProvisioningHelper - .addThingToGroup(deviceProvisioningHelper.getIotClient(), thingName, thingGroupName); + deviceProvisioningHelper.addThingToGroup(deviceProvisioningHelper.getIotClient(), thingName, + thingGroupName); outStream.printf("Successfully added Thing into Thing Group: [%s]%n", thingGroupName); } outStream.printf("Setting up resources for %s ... %n", TokenExchangeService.TOKEN_EXCHANGE_SERVICE_TOPICS); @@ -556,8 +556,8 @@ void provision(Kernel kernel) throws IOException, DeviceConfigurationException { // Dump config since we've just provisioned so that the bootstrap config will enable us to // reach the cloud when needed. Must do this now because we normally would never overwrite the bootstrap // file, however we need to do it since we've only just learned about our endpoints, certs, etc. - kernel.writeEffectiveConfigAsTransactionLog(kernel.getNucleusPaths().configPath() - .resolve(Kernel.DEFAULT_BOOTSTRAP_CONFIG_TLOG_FILE)); + kernel.writeEffectiveConfigAsTransactionLog( + kernel.getNucleusPaths().configPath().resolve(Kernel.DEFAULT_BOOTSTRAP_CONFIG_TLOG_FILE)); } @SuppressWarnings("PMD.PreserveStackTrace") diff --git a/src/main/java/com/aws/greengrass/iot/IotCloudHelper.java b/src/main/java/com/aws/greengrass/iot/IotCloudHelper.java index 1a8f509792..1ae7c076bc 100644 --- a/src/main/java/com/aws/greengrass/iot/IotCloudHelper.java +++ b/src/main/java/com/aws/greengrass/iot/IotCloudHelper.java @@ -28,7 +28,6 @@ import java.util.HashSet; import javax.inject.Singleton; - @Singleton @NoArgsConstructor public class IotCloudHelper { @@ -42,17 +41,16 @@ public class IotCloudHelper { * Sends Http request to Iot Cloud. * * @param connManager underlying connection manager to use for sending requests - * @param thingName IoT Thing Name - * @param path Http url to query - * @param verb Http verb for the request - * @param body Http body for the request + * @param thingName IoT Thing Name + * @param path Http url to query + * @param verb Http verb for the request + * @param body Http body for the request * @return Http response corresponding to http request for path * @throws AWSIotException when unable to send the request successfully * @throws TLSAuthException when unable to configure the client with mTLS */ public IotCloudResponse sendHttpRequest(final IotConnectionManager connManager, String thingName, final String path, - final String verb, final byte[] body) - throws AWSIotException, TLSAuthException { + final String verb, final byte[] body) throws AWSIotException, TLSAuthException { URI uri = null; try { uri = connManager.getURI(); @@ -74,9 +72,11 @@ public IotCloudResponse sendHttpRequest(final IotConnectionManager connManager, innerRequestBuilder.appendHeader(HTTP_HEADER_THING_NAME, thingName); } - ExecutableHttpRequest request = connManager.getClient().prepareRequest(HttpExecuteRequest.builder() - .contentStreamProvider(body == null ? null : () -> new ByteArrayInputStream(body)) - .request(innerRequestBuilder.build()).build()); + ExecutableHttpRequest request = connManager.getClient() + .prepareRequest(HttpExecuteRequest.builder() + .contentStreamProvider(body == null ? null : () -> new ByteArrayInputStream(body)) + .request(innerRequestBuilder.build()) + .build()); BaseRetryableAccessor accessor = new BaseRetryableAccessor(); CrashableSupplier getHttpResponse = () -> getHttpResponse(request); @@ -89,8 +89,8 @@ private IotCloudResponse getHttpResponse(ExecutableHttpRequest request) throws A try { HttpExecuteResponse httpResponse = request.call(); response.setStatusCode(httpResponse.httpResponse().statusCode()); - try (AbortableInputStream bodyStream = httpResponse.responseBody() - .orElseThrow(() -> new AWSIotException("No response body"))) { + try (AbortableInputStream bodyStream = + httpResponse.responseBody().orElseThrow(() -> new AWSIotException("No response body"))) { response.setResponseBody(IoUtils.toByteArray(bodyStream)); } } catch (IOException e) { diff --git a/src/main/java/com/aws/greengrass/iot/IotConnectionManager.java b/src/main/java/com/aws/greengrass/iot/IotConnectionManager.java index e00e136967..649a72845f 100644 --- a/src/main/java/com/aws/greengrass/iot/IotConnectionManager.java +++ b/src/main/java/com/aws/greengrass/iot/IotConnectionManager.java @@ -44,6 +44,7 @@ public IotConnectionManager(final DeviceConfiguration deviceConfiguration) { /** * Get URI for connecting to AWS IoT. + * * @return URI to AWS IoT, based on device configuration * @throws DeviceConfigurationException When device is not configured to get credentials */ @@ -56,6 +57,7 @@ public URI getURI() throws DeviceConfigurationException { /** * Initializes and returns the SdkHttpClient. + * * @throws TLSAuthException when unable to initialize the SdkHttpClient */ public SdkHttpClient getClient() throws TLSAuthException { @@ -86,7 +88,6 @@ private SdkHttpClient initConnectionManager() throws TLSAuthException { return ClientConfigurationUtils.getConfiguredClientBuilder(deviceConfiguration).build(); } - /** * Clean up underlying connections and close gracefully. */ diff --git a/src/main/java/com/aws/greengrass/ipc/AuthenticationHandler.java b/src/main/java/com/aws/greengrass/ipc/AuthenticationHandler.java index fe8508ae87..c7cd25993e 100644 --- a/src/main/java/com/aws/greengrass/ipc/AuthenticationHandler.java +++ b/src/main/java/com/aws/greengrass/ipc/AuthenticationHandler.java @@ -56,13 +56,13 @@ public static void registerAuthenticationToken(GreengrassService s) { /** * Register an auth token for an external client which is not part of Greengrass. Only authenticated EG service can * register such a token. + * * @param requestingAuthToken Auth token of the requesting service * @param clientIdentifier The identifier to identify the client for which the token is being requested * @return Auth token. * @throws UnauthenticatedException thrown when the requestAuthToken is invalid */ - public String registerAuthenticationTokenForExternalClient(String requestingAuthToken, - String clientIdentifier) + public String registerAuthenticationTokenForExternalClient(String requestingAuthToken, String clientIdentifier) throws UnauthenticatedException { authenticateRequestsForExternalClient(requestingAuthToken); return generateAuthenticationToken(clientIdentifier); @@ -70,8 +70,8 @@ public String registerAuthenticationTokenForExternalClient(String requestingAuth private String generateAuthenticationToken(String clientIdentifier) { String authenticationToken = Utils.generateRandomString(16).toUpperCase(); - Topics tokenTopics = config.lookupTopics(GreengrassService.SERVICES_NAMESPACE_TOPIC, - AUTHENTICATION_TOKEN_LOOKUP_KEY); + Topics tokenTopics = + config.lookupTopics(GreengrassService.SERVICES_NAMESPACE_TOPIC, AUTHENTICATION_TOKEN_LOOKUP_KEY); tokenTopics.withParentNeedsToKnow(false); Topic tokenTopic = tokenTopics.createLeafChild(authenticationToken); @@ -90,7 +90,8 @@ private void authenticateRequestsForExternalClient(String requestingAuthToken) t String authenticatedService = doAuthentication(requestingAuthToken); // Making it available only for 1P service right now. if (!authenticatedService.startsWith("aws.greengrass")) { - logger.atError().kv("requestingServiceName", authenticatedService) + logger.atError() + .kv("requestingServiceName", authenticatedService) .log("Invalid requesting auth token for service to register/revoke external client token"); throw new UnauthenticatedException("Invalid requesting auth token for service"); } @@ -99,6 +100,7 @@ private void authenticateRequestsForExternalClient(String requestingAuthToken) t /** * Revoke an auth token for an external client which is not part of Greengrass. Only authenticated EG service can * revoke such a token. + * * @param requestingAuthToken Auth token of the requesting service * @param authTokenToRevoke The auth token to revoke * @return true if authTokenToRevoke existed and is now removed, false if authTokenToRevoke does not exist. @@ -111,8 +113,8 @@ public boolean revokeAuthenticationTokenForExternalClient(String requestingAuthT } private boolean revokeAuthenticationToken(String authTokenToRevoke) { - Topic tokenTopic = config.lookup(GreengrassService.SERVICES_NAMESPACE_TOPIC, - AUTHENTICATION_TOKEN_LOOKUP_KEY, authTokenToRevoke); + Topic tokenTopic = config.lookup(GreengrassService.SERVICES_NAMESPACE_TOPIC, AUTHENTICATION_TOKEN_LOOKUP_KEY, + authTokenToRevoke); if (tokenTopic == null) { return false; } @@ -122,6 +124,7 @@ private boolean revokeAuthenticationToken(String authTokenToRevoke) { /** * Lookup the provided authentication token to associate it with a service (or reject it). + * * @param authenticationToken token to be looked up. * @return service name to which the token is associated. * @throws UnauthenticatedException if token is invalid or unassociated. @@ -130,8 +133,8 @@ public String doAuthentication(String authenticationToken) throws Unauthenticate if (authenticationToken == null) { throw new UnauthenticatedException("Invalid authentication token"); } - Topic service = config.find(GreengrassService.SERVICES_NAMESPACE_TOPIC, - AUTHENTICATION_TOKEN_LOOKUP_KEY, authenticationToken); + Topic service = config.find(GreengrassService.SERVICES_NAMESPACE_TOPIC, AUTHENTICATION_TOKEN_LOOKUP_KEY, + authenticationToken); if (service == null) { throw new UnauthenticatedException("Authentication token not found"); } diff --git a/src/main/java/com/aws/greengrass/ipc/IPCEventStreamService.java b/src/main/java/com/aws/greengrass/ipc/IPCEventStreamService.java index e39cc8e0a1..b3ffeaeab6 100644 --- a/src/main/java/com/aws/greengrass/ipc/IPCEventStreamService.java +++ b/src/main/java/com/aws/greengrass/ipc/IPCEventStreamService.java @@ -66,11 +66,9 @@ public class IPCEventStreamService implements Startable, Closeable { private EventLoopGroup eventLoopGroup; @Inject - IPCEventStreamService(Kernel kernel, - DeviceConfiguration deviceConfiguration, - GreengrassCoreIPCService greengrassCoreIPCService, - Configuration config, - AuthenticationHandler authenticationHandler) { + IPCEventStreamService(Kernel kernel, DeviceConfiguration deviceConfiguration, + GreengrassCoreIPCService greengrassCoreIPCService, Configuration config, + AuthenticationHandler authenticationHandler) { this.kernel = kernel; this.deviceConfiguration = deviceConfiguration; this.greengrassCoreIPCService = greengrassCoreIPCService; @@ -78,7 +76,9 @@ public class IPCEventStreamService implements Startable, Closeable { this.authenticationHandler = authenticationHandler; } - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.ExceptionAsFlowControl"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.ExceptionAsFlowControl" + }) @Override public void startup() { Path rootPath = kernel.getNucleusPaths().rootPath(); @@ -86,12 +86,13 @@ public void startup() { Path ipcPath = Utils.isEmpty(ipcPathStr) ? null : Paths.get(ipcPathStr); try { - greengrassCoreIPCService.getAllOperations().forEach(operation -> - greengrassCoreIPCService.setOperationHandler(operation, - (context) -> new DefaultOperationHandler(GreengrassCoreIPCServiceModel.getInstance() - .getOperationModelContext(operation), context))); - greengrassCoreIPCService.setAuthenticationHandler((List

headers, byte[] bytes) -> - ipcAuthenticationHandler(bytes)); + greengrassCoreIPCService.getAllOperations() + .forEach(operation -> greengrassCoreIPCService.setOperationHandler(operation, + (context) -> new DefaultOperationHandler( + GreengrassCoreIPCServiceModel.getInstance().getOperationModelContext(operation), + context))); + greengrassCoreIPCService + .setAuthenticationHandler((List
headers, byte[] bytes) -> ipcAuthenticationHandler(bytes)); greengrassCoreIPCService.setAuthorizationHandler(this::ipcAuthorizationHandler); socketOptions = new SocketOptions(); @@ -110,8 +111,8 @@ public void startup() { // 1. Port number is ignored. RpcServer does not accept a null value so we are using a default value. // 2. The hostname parameter expects the socket filepath rpcServer = new RpcServer(eventLoopGroup, socketOptions, null, - Platform.getInstance().prepareIpcFilepathForRpcServer(rootPath, ipcPath), - DEFAULT_PORT_NUMBER, greengrassCoreIPCService); + Platform.getInstance().prepareIpcFilepathForRpcServer(rootPath, ipcPath), DEFAULT_PORT_NUMBER, + greengrassCoreIPCService); rpcServer.runServer(); } catch (RuntimeException e) { // Make sure to cleanup anything we created since we don't know where exactly we failed @@ -129,13 +130,15 @@ private Authorization ipcAuthorizationHandler(AuthenticationData authenticationD return Authorization.ACCEPT; } - @SuppressWarnings({"PMD.UnusedFormalParameter", "PMD.PreserveStackTrace"}) + @SuppressWarnings({ + "PMD.UnusedFormalParameter", "PMD.PreserveStackTrace" + }) private AuthenticationData ipcAuthenticationHandler(byte[] payload) { String authToken = null; try { - GreengrassEventStreamConnectMessage connectMessage = OBJECT_MAPPER.readValue(payload, - GreengrassEventStreamConnectMessage.class); + GreengrassEventStreamConnectMessage connectMessage = + OBJECT_MAPPER.readValue(payload, GreengrassEventStreamConnectMessage.class); authToken = connectMessage.getAuthToken(); } catch (IOException e) { String errorMessage = "Invalid auth token in connect message"; @@ -159,7 +162,9 @@ private AuthenticationData ipcAuthenticationHandler(byte[] payload) { } @Override - @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.AvoidCatchingGenericException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingThrowable", "PMD.AvoidCatchingGenericException" + }) public void close() { // GG_NEEDS_REVIEW: TODO: Future does not complete, wait on them when fixed. if (rpcServer != null) { diff --git a/src/main/java/com/aws/greengrass/ipc/common/DefaultOperationHandler.java b/src/main/java/com/aws/greengrass/ipc/common/DefaultOperationHandler.java index a11ad622e6..d90dc111b4 100644 --- a/src/main/java/com/aws/greengrass/ipc/common/DefaultOperationHandler.java +++ b/src/main/java/com/aws/greengrass/ipc/common/DefaultOperationHandler.java @@ -13,27 +13,27 @@ import software.amazon.awssdk.eventstreamrpc.OperationModelContext; import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; -public class DefaultOperationHandler extends OperationContinuationHandler { +public class DefaultOperationHandler + extends + OperationContinuationHandler { private static final Logger LOGGER = LogManager.getLogger(DefaultOperationHandler.class.getName()); private final OperationModelContext operationModelContext; public DefaultOperationHandler(final OperationModelContext modelContext, - final OperationContinuationHandlerContext context) { + final OperationContinuationHandlerContext context) { super(context); this.operationModelContext = modelContext; } @Override - public OperationModelContext getOperationModelContext() { + public OperationModelContext getOperationModelContext() { return operationModelContext; } /** - * Called when the underlying continuation is closed. Gives operations a chance to cleanup whatever - * resources may be on the other end of an open stream. Also invoked when an underlying ServerConnection - * is closed associated with the stream/continuation + * Called when the underlying continuation is closed. Gives operations a chance to cleanup whatever resources may be + * on the other end of an open stream. Also invoked when an underlying ServerConnection is closed associated with + * the stream/continuation */ @Override protected void onStreamClosed() { @@ -42,17 +42,15 @@ protected void onStreamClosed() { @Override public EventStreamJsonMessage handleRequest(EventStreamJsonMessage request) { - LOGGER.atDebug().log("Request received for unsupported operation {}", - operationModelContext.getOperationName()); - throw new ServiceError(String.format("Operation %s is not supported by Greengrass", - operationModelContext.getOperationName())); + LOGGER.atDebug().log("Request received for unsupported operation {}", operationModelContext.getOperationName()); + throw new ServiceError( + String.format("Operation %s is not supported by Greengrass", operationModelContext.getOperationName())); } @Override public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { - LOGGER.atDebug().log("Event received on stream for operation {}", - operationModelContext.getOperationName()); - throw new ServiceError(String.format("Operation %s is not supported by Greengrass", - operationModelContext.getOperationName())); + LOGGER.atDebug().log("Event received on stream for operation {}", operationModelContext.getOperationName()); + throw new ServiceError( + String.format("Operation %s is not supported by Greengrass", operationModelContext.getOperationName())); } } diff --git a/src/main/java/com/aws/greengrass/ipc/common/ExceptionUtil.java b/src/main/java/com/aws/greengrass/ipc/common/ExceptionUtil.java index 2ce5616eea..ab30422c16 100644 --- a/src/main/java/com/aws/greengrass/ipc/common/ExceptionUtil.java +++ b/src/main/java/com/aws/greengrass/ipc/common/ExceptionUtil.java @@ -25,9 +25,11 @@ private ExceptionUtil() { * @param Return type * @return return if the supplier does not throw * @throws GreengrassCoreIPCError when an exception occurs - * @throws ServiceError for any translated exception + * @throws ServiceError for any translated exception */ - @SuppressWarnings({"PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace"}) + @SuppressWarnings({ + "PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" + }) public static T translateExceptions(Supplier sup) { try { return sup.get(); diff --git a/src/main/java/com/aws/greengrass/ipc/common/IPCErrorStrings.java b/src/main/java/com/aws/greengrass/ipc/common/IPCErrorStrings.java index ea26414c3b..d2ffabff29 100644 --- a/src/main/java/com/aws/greengrass/ipc/common/IPCErrorStrings.java +++ b/src/main/java/com/aws/greengrass/ipc/common/IPCErrorStrings.java @@ -6,8 +6,8 @@ package com.aws.greengrass.ipc.common; public final class IPCErrorStrings { - public static final String DEPLOYMENTS_QUEUE_NOT_INITIALIZED = "Greengrass not setup to receive deployments. The " - + "deployments queue is not initialized"; + public static final String DEPLOYMENTS_QUEUE_NOT_INITIALIZED = + "Greengrass not setup to receive deployments. The " + "deployments queue is not initialized"; public static final String DEPLOYMENTS_QUEUE_FULL = "Deployments queue is full, Please try again later"; private IPCErrorStrings() { diff --git a/src/main/java/com/aws/greengrass/ipc/modules/ComponentMetricIPCService.java b/src/main/java/com/aws/greengrass/ipc/modules/ComponentMetricIPCService.java index 8e3751d079..67f50456c1 100644 --- a/src/main/java/com/aws/greengrass/ipc/modules/ComponentMetricIPCService.java +++ b/src/main/java/com/aws/greengrass/ipc/modules/ComponentMetricIPCService.java @@ -45,7 +45,7 @@ public void postInject() { @Override public void startup() { - greengrassCoreIPCService.setPutComponentMetricHandler( - context -> eventStreamAgent.getPutComponentMetricHandler(context)); + greengrassCoreIPCService + .setPutComponentMetricHandler(context -> eventStreamAgent.getPutComponentMetricHandler(context)); } } diff --git a/src/main/java/com/aws/greengrass/ipc/modules/ConfigStoreIPCService.java b/src/main/java/com/aws/greengrass/ipc/modules/ConfigStoreIPCService.java index 4205e51105..ebfa6d5f66 100644 --- a/src/main/java/com/aws/greengrass/ipc/modules/ConfigStoreIPCService.java +++ b/src/main/java/com/aws/greengrass/ipc/modules/ConfigStoreIPCService.java @@ -18,24 +18,25 @@ public class ConfigStoreIPCService implements Startable { /** * Constructor. + * * @param eventStreamAgent {@link ConfigStoreIPCEventStreamAgent} * @param greengrassCoreIPCService {@link GreengrassCoreIPCService} */ @Inject public ConfigStoreIPCService(ConfigStoreIPCEventStreamAgent eventStreamAgent, - GreengrassCoreIPCService greengrassCoreIPCService) { + GreengrassCoreIPCService greengrassCoreIPCService) { this.eventStreamAgent = eventStreamAgent; this.greengrassCoreIPCService = greengrassCoreIPCService; } @Override public void startup() { - greengrassCoreIPCService.setUpdateConfigurationHandler( - (context) -> eventStreamAgent.getUpdateConfigurationHandler(context)); + greengrassCoreIPCService + .setUpdateConfigurationHandler((context) -> eventStreamAgent.getUpdateConfigurationHandler(context)); greengrassCoreIPCService.setSendConfigurationValidityReportHandler( (context) -> eventStreamAgent.getSendConfigurationValidityReportHandler(context)); - greengrassCoreIPCService.setGetConfigurationHandler( - (context) -> eventStreamAgent.getGetConfigurationHandler(context)); + greengrassCoreIPCService + .setGetConfigurationHandler((context) -> eventStreamAgent.getGetConfigurationHandler(context)); greengrassCoreIPCService.setSubscribeToConfigurationUpdateHandler( (context) -> eventStreamAgent.getConfigurationUpdateHandler(context)); greengrassCoreIPCService.setSubscribeToValidateConfigurationUpdatesHandler( diff --git a/src/main/java/com/aws/greengrass/ipc/modules/LifecycleIPCService.java b/src/main/java/com/aws/greengrass/ipc/modules/LifecycleIPCService.java index 4c2fd0fc4f..f0b754dc31 100644 --- a/src/main/java/com/aws/greengrass/ipc/modules/LifecycleIPCService.java +++ b/src/main/java/com/aws/greengrass/ipc/modules/LifecycleIPCService.java @@ -5,7 +5,6 @@ package com.aws.greengrass.ipc.modules; - import com.aws.greengrass.authorization.AuthorizationHandler; import com.aws.greengrass.authorization.exceptions.AuthorizationException; import com.aws.greengrass.builtin.services.lifecycle.LifecycleIPCEventStreamAgent; @@ -53,15 +52,15 @@ public void postInject() { @Override public void startup() { - greengrassCoreIPCService.setUpdateStateHandler( - (context) -> eventStreamAgent.getUpdateStateOperationHandler(context)); + greengrassCoreIPCService + .setUpdateStateHandler((context) -> eventStreamAgent.getUpdateStateOperationHandler(context)); greengrassCoreIPCService.setSubscribeToComponentUpdatesHandler( (context) -> eventStreamAgent.getSubscribeToComponentUpdateHandler(context)); - greengrassCoreIPCService.setDeferComponentUpdateHandler( - (context) -> eventStreamAgent.getDeferComponentHandler(context)); - greengrassCoreIPCService.setPauseComponentHandler( - (context) -> eventStreamAgent.getPauseComponentHandler(context)); - greengrassCoreIPCService.setResumeComponentHandler( - (context) -> eventStreamAgent.getResumeComponentHandler(context)); + greengrassCoreIPCService + .setDeferComponentUpdateHandler((context) -> eventStreamAgent.getDeferComponentHandler(context)); + greengrassCoreIPCService + .setPauseComponentHandler((context) -> eventStreamAgent.getPauseComponentHandler(context)); + greengrassCoreIPCService + .setResumeComponentHandler((context) -> eventStreamAgent.getResumeComponentHandler(context)); } } diff --git a/src/main/java/com/aws/greengrass/ipc/modules/PubSubIPCService.java b/src/main/java/com/aws/greengrass/ipc/modules/PubSubIPCService.java index 82d026256d..f04510744c 100644 --- a/src/main/java/com/aws/greengrass/ipc/modules/PubSubIPCService.java +++ b/src/main/java/com/aws/greengrass/ipc/modules/PubSubIPCService.java @@ -46,9 +46,9 @@ public void postInject() { @Override public void startup() { - greengrassCoreIPCService.setSubscribeToTopicHandler( - context -> eventStreamAgent.getSubscribeToTopicHandler(context)); - greengrassCoreIPCService.setPublishToTopicHandler( - context -> eventStreamAgent.getPublishToTopicHandler(context)); + greengrassCoreIPCService + .setSubscribeToTopicHandler(context -> eventStreamAgent.getSubscribeToTopicHandler(context)); + greengrassCoreIPCService + .setPublishToTopicHandler(context -> eventStreamAgent.getPublishToTopicHandler(context)); } } diff --git a/src/main/java/com/aws/greengrass/jna/Kernel32Ex.java b/src/main/java/com/aws/greengrass/jna/Kernel32Ex.java index b3a20a966b..0dca83d2fd 100644 --- a/src/main/java/com/aws/greengrass/jna/Kernel32Ex.java +++ b/src/main/java/com/aws/greengrass/jna/Kernel32Ex.java @@ -11,6 +11,9 @@ public interface Kernel32Ex extends Library { Kernel32Ex INSTANCE = Native.load("kernel32", Kernel32Ex.class, W32APIOptions.DEFAULT_OPTIONS); - @SuppressWarnings({"checkstyle:MethodName", "PMD.MethodNamingConventions"}) + + @SuppressWarnings({ + "checkstyle:MethodName", "PMD.MethodNamingConventions" + }) boolean SetConsoleCtrlHandler(HandlerRoutine handlerRoutine, boolean add); } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/GenericExternalService.java b/src/main/java/com/aws/greengrass/lifecyclemanager/GenericExternalService.java index 65a1d72e20..c93bd95d79 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/GenericExternalService.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/GenericExternalService.java @@ -58,7 +58,7 @@ public class GenericExternalService extends GreengrassService { public static final String LIFECYCLE_RUN_NAMESPACE_TOPIC = "run"; - public static final int DEFAULT_BOOTSTRAP_TIMEOUT_SEC = 120; // 2 min + public static final int DEFAULT_BOOTSTRAP_TIMEOUT_SEC = 120; // 2 min protected static final String EXIT_CODE = "exitCode"; private static final String SKIP_COMMAND_REGEX = "(exists|onpath) +(.+)"; private static final Pattern SKIPCMD = Pattern.compile(SKIP_COMMAND_REGEX); @@ -117,8 +117,8 @@ protected GenericExternalService(Topics c, Topics privateSpace, Platform platfor c.subscribe((what, child) -> { // When the service is removed via a deployment this topic itself will be removed // When first initialized, the child will be null - if (WhatHappened.removed.equals(what) || child == null - || WhatHappened.timestampUpdated.equals(what) || WhatHappened.interiorAdded.equals(what)) { + if (WhatHappened.removed.equals(what) || child == null || WhatHappened.timestampUpdated.equals(what) + || WhatHappened.interiorAdded.equals(what)) { return; } @@ -133,7 +133,8 @@ protected GenericExternalService(Topics c, Topics privateSpace, Platform platfor // Reinstall for changes to the install script or if the package version changed, or runWith user changed if (child.childOf(Lifecycle.LIFECYCLE_INSTALL_NAMESPACE_TOPIC) || child.childOf(VERSION_CONFIG_KEY) || (child.childOf(RUN_WITH_NAMESPACE_TOPIC) && !child.childOf(SYSTEM_RESOURCE_LIMITS_TOPICS))) { - logger.atInfo("service-config-change").kv(CONFIG_NODE, child.getFullName()) + logger.atInfo("service-config-change") + .kv(CONFIG_NODE, child.getFullName()) .log("Requesting reinstallation for component"); requestReinstall(); return; @@ -144,12 +145,14 @@ protected GenericExternalService(Topics c, Topics privateSpace, Platform platfor // If we're currently broken, restart will not be able to take us out of BROKEN. // Instead, we must reinstall to get out of BROKEN, so requestReinstall here. if (State.BROKEN.equals(getState())) { - logger.atInfo("service-config-change").kv(CONFIG_NODE, child.getFullName()) + logger.atInfo("service-config-change") + .kv(CONFIG_NODE, child.getFullName()) .log("Configuration changed, and current state is BROKEN. " + "Requesting reinstallation for component"); requestReinstall(); } else { - logger.atInfo("service-config-change").kv(CONFIG_NODE, child.getFullName()) + logger.atInfo("service-config-change") + .kv(CONFIG_NODE, child.getFullName()) .log("Requesting restart for component"); requestRestart(); } @@ -176,7 +179,7 @@ private void updateSystemResourceLimits() { * Check if the case-insensitive lifecycle key is defined in the service lifecycle configuration map. * * @param newServiceLifecycle service lifecycle configuration map - * @param lifecycleKey case-insensitive lifecycle key + * @param lifecycleKey case-insensitive lifecycle key * @return key in the map that matches the lifecycle key; empty string if no match */ public static String serviceLifecycleDefined(Map newServiceLifecycle, String lifecycleKey) { @@ -211,11 +214,12 @@ public void postInject() { * * @return exit code of process * @throws InterruptedException when the command execution is interrupted. - * @throws TimeoutException when the command execution times out. + * @throws TimeoutException when the command execution times out. */ @Override - @SuppressFBWarnings(value = {"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", "NP_LOAD_OF_KNOWN_NULL_VALUE"}, - justification = "Known false-positives") + @SuppressFBWarnings(value = { + "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", "NP_LOAD_OF_KNOWN_NULL_VALUE" + }, justification = "Known false-positives") public int bootstrap() throws InterruptedException, TimeoutException { try (LockScope ls = LockScope.lock(lock)) { // this is redundant because all lifecycle processes should have been before calling this method. @@ -242,8 +246,8 @@ public int bootstrap() throws InterruptedException, TimeoutException { } // timeout handling - int timeoutInSec = Coerce.toInt( - config.findOrDefault(DEFAULT_BOOTSTRAP_TIMEOUT_SEC, SERVICE_LIFECYCLE_NAMESPACE_TOPIC, + int timeoutInSec = Coerce + .toInt(config.findOrDefault(DEFAULT_BOOTSTRAP_TIMEOUT_SEC, SERVICE_LIFECYCLE_NAMESPACE_TOPIC, Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC, Lifecycle.TIMEOUT_NAMESPACE_TOPIC)); boolean completedInTime = timeoutLatch.await(timeoutInSec, TimeUnit.SECONDS); if (!completedInTime) { @@ -252,7 +256,8 @@ public int bootstrap() throws InterruptedException, TimeoutException { } } catch (IOException e) { - logger.atError("bootstrap-process-close-error").setCause(e) + logger.atError("bootstrap-process-close-error") + .setCause(e) .log("Error closing process at bootstrap step."); // No need to return special error code here because the exit code is handled by atomicExitCode. } @@ -271,9 +276,8 @@ private boolean isPrivilegeRequired(String lifecycleName) { * workflow. * * @param newServiceConfig new service config for the update - * @return true if the service - * 1. has a bootstrap step defined, 2. component version changes, or bootstrap step changes. - * false otherwise + * @return true if the service 1. has a bootstrap step defined, 2. component version changes, or bootstrap step + * changes. false otherwise */ @Override public boolean isBootstrapRequired(Map newServiceConfig) { @@ -283,8 +287,8 @@ public boolean isBootstrapRequired(Map newServiceConfig) { } Map newServiceLifecycle = (Map) newServiceConfig.get(SERVICE_LIFECYCLE_NAMESPACE_TOPIC); - String lifecycleKey = serviceLifecycleDefined(newServiceLifecycle, - Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC); + String lifecycleKey = + serviceLifecycleDefined(newServiceLifecycle, Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC); if (lifecycleKey.isEmpty()) { logger.atDebug().log("Bootstrap is not required: service lifecycle bootstrap not found"); return false; @@ -294,13 +298,15 @@ public boolean isBootstrapRequired(Map newServiceConfig) { logger.atDebug().log("Bootstrap is required: service version changed"); return true; } - Node serviceOldBootstrap = getConfig().findNode(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, - Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC); - boolean bootstrapStepChanged = serviceOldBootstrap == null || !serviceLifecycleBootstrapEquals( - serviceOldBootstrap.toPOJO(), newServiceLifecycle.get(lifecycleKey)); + Node serviceOldBootstrap = + getConfig().findNode(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC); + boolean bootstrapStepChanged = + serviceOldBootstrap == null || !serviceLifecycleBootstrapEquals(serviceOldBootstrap.toPOJO(), + newServiceLifecycle.get(lifecycleKey)); if (bootstrapStepChanged) { - logger.atDebug().kv("before", - (Supplier) () -> serviceOldBootstrap == null ? null : serviceOldBootstrap.toPOJO()) + logger.atDebug() + .kv("before", + (Supplier) () -> serviceOldBootstrap == null ? null : serviceOldBootstrap.toPOJO()) .kv("after", newServiceLifecycle.get(lifecycleKey)) .log("Bootstrap is required: bootstrap step changed"); } else { @@ -473,7 +479,8 @@ private void resume(boolean restartOnFail, boolean retryOnFail) throws ServiceEx if (retryOnFail && retryAttempts > 0) { logger.atInfo().setCause(e).log("Error resuming component, retrying"); } else { - logger.atError().setCause(e) + logger.atError() + .setCause(e) .log("Error resuming component and all retried exhausted, " + "restarting"); if (restartOnFail) { // Reset tracking flag @@ -491,6 +498,7 @@ private void resume(boolean restartOnFail, boolean retryOnFail) throws ServiceEx /** * Check if component is paused. + * * @return true if paused */ public boolean isPaused() { @@ -550,7 +558,8 @@ private void handleRunScript() throws InterruptedException { reportState(State.ERRORED, ComponentStatusCode.RUN_TIMEOUT); processToClose.close(); } catch (IOException e) { - logger.atError("service-close-error").setCause(e) + logger.atError("service-close-error") + .setCause(e) .log("Error closing service after run timed out"); } } @@ -571,7 +580,8 @@ protected void shutdown() { } catch (ServiceException e) { // Reset tracking flag paused.set(false); - logger.atError().setCause(e) + logger.atError() + .setCause(e) .log("Could not resume service before shutdown, process will be killed"); } } @@ -603,7 +613,8 @@ protected void shutdown() { /** * Stop all the lifecycle processes. * - *

public for integ test use only. + *

+ * public for integ test use only. */ public void stopAllLifecycleProcesses() { try (LockScope ls = LockScope.lock(lock)) { @@ -655,7 +666,8 @@ public void handleError() throws InterruptedException { * Computer user, group, and shell that will be used to run the service. This should be used throughout the * lifecycle. * - *

This information can change with a deployment, but service *must* execute the lifecycle steps with the same + *

+ * This information can change with a deployment, but service *must* execute the lifecycle steps with the same * user/group/shell that was configured when it started. */ protected Optional computeRunWithConfiguration() { @@ -679,10 +691,8 @@ protected boolean updateComponentPathOwner() { ownershipHandler.updateOwner(id, runWith); return true; } catch (IOException e) { - LogEventBuilder logEvent = logger.atError() - .setEventType("update-artifact-owner") - .setCause(e) - .kv("user", runWith.getUser()); + LogEventBuilder logEvent = + logger.atError().setEventType("update-artifact-owner").setCause(e).kv("user", runWith.getUser()); if (runWith.getGroup() != null) { logEvent.kv("group", runWith.getGroup()); } @@ -691,18 +701,17 @@ protected boolean updateComponentPathOwner() { } } - protected RunResult run(String name, IntConsumer background, List trackingList) - throws InterruptedException { + protected RunResult run(String name, IntConsumer background, List trackingList) throws InterruptedException { return run(name, background, trackingList, true); } /** * Run one of the commands defined in the config on the command line. * - * @param name name of the command to run ("run", "install", "startup", "bootstrap"). - * @param background IntConsumer to and run the command as background process and receive the exit code. If - * null, the command will run as a foreground process and blocks indefinitely. - * @param trackingList List used to track running processes. + * @param name name of the command to run ("run", "install", "startup", "bootstrap"). + * @param background IntConsumer to and run the command as background process and receive the exit code. If null, + * the command will run as a foreground process and blocks indefinitely. + * @param trackingList List used to track running processes. * @param runImmediately True if the command should be run immediately, false to construct without running * @return the status of the run and the Exec. */ @@ -714,8 +723,8 @@ protected RunResult run(String name, IntConsumer background, List tracking } if (n instanceof Topic) { - return run(name, (Topic) n, Coerce.toString(n), background, - trackingList, isPrivilegeRequired(name), runImmediately); + return run(name, (Topic) n, Coerce.toString(n), background, trackingList, isPrivilegeRequired(name), + runImmediately); } if (n instanceof Topics) { return run(name, (Topics) n, background, trackingList, isPrivilegeRequired(name), runImmediately); @@ -725,12 +734,13 @@ protected RunResult run(String name, IntConsumer background, List tracking @SuppressWarnings("PMD.CloseResource") protected RunResult run(String name, Topic t, String cmd, IntConsumer background, List trackingList, - boolean requiresPrivilege, boolean runImmediately) throws InterruptedException { + boolean requiresPrivilege, boolean runImmediately) throws InterruptedException { if (runWith == null) { Optional opt = computeRunWithConfiguration(); if (!opt.isPresent()) { - logger.atError().log("Could not determine user/group to run with. Ensure that {} is set for {}", - DeviceConfiguration.RUN_WITH_TOPIC, deviceConfiguration.getNucleusComponentName()); + logger.atError() + .log("Could not determine user/group to run with. Ensure that {} is set for {}", + DeviceConfiguration.RUN_WITH_TOPIC, deviceConfiguration.getNucleusComponentName()); return new RunResult(RunStatus.Errored, null, ComponentStatusCode.getCodeMissingRunWithForState(name)); } @@ -773,8 +783,9 @@ protected RunResult run(String name, Topic t, String cmd, IntConsumer background if (finalExec.isRunning()) { trackingList.add(finalExec); } - return shellRunner.successful(finalExec, t.getFullName(), - background, this) ? RunStatus.OK : RunStatus.Errored; + return shellRunner.successful(finalExec, t.getFullName(), background, this) + ? RunStatus.OK + : RunStatus.Errored; }; if (runImmediately) { @@ -786,8 +797,7 @@ protected RunResult run(String name, Topic t, String cmd, IntConsumer background } protected RunResult run(String name, Topics t, IntConsumer background, List trackingList, - boolean requiresPrivilege, boolean runImmediately) - throws InterruptedException { + boolean requiresPrivilege, boolean runImmediately) throws InterruptedException { try { if (shouldSkip(t)) { logger.atDebug().setEventType("generic-service-skipped").addKeyValue("script", t.getFullName()).log(); @@ -802,7 +812,9 @@ protected RunResult run(String name, Topics t, IntConsumer background, List close(boolean waitForDependers) { } } // removing listeners on dependencies after the dependers have exited - dependencies.forEach((service, dependencyInfo) -> - getContext().removeGlobalStateChangeListener(dependencyInfo.stateListener)); + dependencies.forEach((service, dependencyInfo) -> getContext() + .removeGlobalStateChangeListener(dependencyInfo.stateListener)); externalDependenciesTopic.remove(externalDependenciesTopicWatcher); requestStop(); @@ -482,13 +474,12 @@ protected CompletableFuture close(boolean waitForDependers) { * Add a dependency. * * @param dependencyService the service to add as a dependency. - * @param dependencyType type of the dependency. - * @param isDefault True if the dependency is added without explicit declaration in 'dependencies' Topic. + * @param dependencyType type of the dependency. + * @param isDefault True if the dependency is added without explicit declaration in 'dependencies' Topic. * @throws InputValidationException if the provided arguments are invalid. */ public void addOrUpdateDependency(GreengrassService dependencyService, DependencyType dependencyType, - boolean isDefault) - throws InputValidationException { + boolean isDefault) throws InputValidationException { if (dependencyService == null || dependencyType == null) { throw new InputValidationException("One or more parameters was null"); } @@ -511,13 +502,15 @@ public void addOrUpdateDependency(GreengrassService dependencyService, Dependenc } private GlobalStateChangeListener createDependencyListener(GreengrassService dependencyService, - DependencyType dependencyType) { + DependencyType dependencyType) { return (service, oldState, newState) -> { - if (service.equals(dependencyService) && (State.STARTING.equals(getState()) || State.RUNNING.equals( - getState())) && !dependencyReady(dependencyService, dependencyType)) { + if (service.equals(dependencyService) + && (State.STARTING.equals(getState()) || State.RUNNING.equals(getState())) + && !dependencyReady(dependencyService, dependencyType)) { requestRestart(); - logger.atInfo("service-restart").log("Restarting service because dependency {} was in a bad state", - dependencyService.getName()); + logger.atInfo("service-restart") + .log("Restarting service because dependency {} was in a bad state", + dependencyService.getName()); } synchronized (dependencyReadyLock) { if (dependencyReady()) { @@ -529,6 +522,7 @@ private GlobalStateChangeListener createDependencyListener(GreengrassService dep /** * Get all hard dependers. + * * @return a List of services which are hard dependers of current service. */ public List getHardDependers() { @@ -585,9 +579,11 @@ private boolean dependersExited(List dependers) { } protected boolean dependencyReady() { - List ret = - dependencies.entrySet().stream().filter(e -> !dependencyReady(e.getKey(), e.getValue().dependencyType)) - .map(Map.Entry::getKey).collect(Collectors.toList()); + List ret = dependencies.entrySet() + .stream() + .filter(e -> !dependencyReady(e.getKey(), e.getValue().dependencyType)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); if (!ret.isEmpty()) { logger.atDebug("continue-waiting-for-dependencies").kv("waitingFor", ret).log(); } @@ -717,9 +713,11 @@ private void setupDependencies(Collection dependencyList) Map oldDependencies = new HashMap<>(getDependencies()); Map keptDependencies = getDependencyTypeMap(dependencyList); - Set removedDependencies = dependencies.entrySet().stream() + Set removedDependencies = dependencies.entrySet() + .stream() .filter(e -> !keptDependencies.containsKey(e.getKey()) && !e.getValue().isDefaultDependency) - .map(Map.Entry::getKey).collect(Collectors.toSet()); + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); if (!removedDependencies.isEmpty()) { logger.atDebug("removing-unused-dependencies").kv("removedDependencies", removedDependencies).log(); @@ -751,7 +749,6 @@ private void setupDependencies(Collection dependencyList) } } - @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -781,7 +778,8 @@ protected void putDependenciesIntoSet(Set deps) { // GG_NEEDS_REVIEW: TODO: return the entire dependency info public Map getDependencies() { - return dependencies.entrySet().stream() + return dependencies.entrySet() + .stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().dependencyType)); } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/Kernel.java b/src/main/java/com/aws/greengrass/lifecyclemanager/Kernel.java index 867d983997..f342fa875a 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/Kernel.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/Kernel.java @@ -140,10 +140,9 @@ public class Kernel { protected static final ObjectMapper CONFIG_YAML_WRITER = YAMLMapper.builder().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET).build(); - private static final List SUPPORTED_CAPABILITIES = - Arrays.asList(DeploymentCapability.LARGE_CONFIGURATION.toString(), - DeploymentCapability.LINUX_RESOURCE_LIMITS.toString(), - DeploymentCapability.SUB_DEPLOYMENTS.toString()); + private static final List SUPPORTED_CAPABILITIES = Arrays.asList( + DeploymentCapability.LARGE_CONFIGURATION.toString(), DeploymentCapability.LINUX_RESOURCE_LIMITS.toString(), + DeploymentCapability.SUB_DEPLOYMENTS.toString()); @Getter private final Context context; @@ -219,7 +218,8 @@ public static String findServiceForNode(Node node) { @SuppressWarnings("PMD.MissingBreakInSwitch") public Kernel launch() { try { - Platform.getInstance().getRunWithGenerator() + Platform.getInstance() + .getRunWithGenerator() .validateDefaultConfiguration(context.get(DeviceConfiguration.class)); } catch (DeviceConfigurationException e) { RuntimeException rte = new RuntimeException(e); @@ -231,79 +231,81 @@ public Kernel launch() { KernelAlternatives kernelAlts = context.get(KernelAlternatives.class); switch (deploymentStageAtLaunch) { - case BOOTSTRAP: - logger.atInfo().kv("deploymentStage", deploymentStageAtLaunch).log("Resume deployment"); + case BOOTSTRAP: + logger.atInfo().kv("deploymentStage", deploymentStageAtLaunch).log("Resume deployment"); + try { + Path bootstrapTaskFilePath = deploymentDirectoryManager.getBootstrapTaskFilePath(); + executeBootstrapTasksAndShutdown(bootstrapManager, bootstrapTaskFilePath); + } catch (ServiceUpdateException | IOException e) { + logger.atError().log("Deployment bootstrap failed", e); try { - Path bootstrapTaskFilePath = deploymentDirectoryManager.getBootstrapTaskFilePath(); - executeBootstrapTasksAndShutdown(bootstrapManager, bootstrapTaskFilePath); - } catch (ServiceUpdateException | IOException e) { - logger.atError().log("Deployment bootstrap failed", e); - try { - // Bootstrapping for target deployment failed, so check if bootstrap-on-rollback is needed - boolean bootstrapOnRollbackRequired = kernelAlts.prepareBootstrapOnRollbackIfNeeded( - this.context, deploymentDirectoryManager, bootstrapManager); - // Save deployment error information - Deployment deployment = deploymentDirectoryManager.readDeploymentMetadata(); - deployment.setDeploymentStage( - bootstrapOnRollbackRequired ? ROLLBACK_BOOTSTRAP : KERNEL_ROLLBACK); - Pair, List> errorReport = - DeploymentErrorCodeUtils.generateErrorReportFromExceptionStack(e); - deployment.setErrorStack(errorReport.getLeft()); - deployment.setErrorTypes(errorReport.getRight()); - deployment.setStageDetails(Utils.generateFailureMessage(e)); - deploymentDirectoryManager.writeDeploymentMetadata(deployment); - } catch (IOException ioException) { - logger.atError().setCause(ioException).log("Could not read deployment metadata, " - + "file is either missing or corrupted"); - } - try { - kernelAlts.prepareRollback(); - shutdown(30, REQUEST_RESTART); - } catch (IOException ioException) { - logger.atError().setCause(ioException).log("Could not prepare rollback"); - kernelLifecycle.launch(); - } + // Bootstrapping for target deployment failed, so check if bootstrap-on-rollback is needed + boolean bootstrapOnRollbackRequired = kernelAlts.prepareBootstrapOnRollbackIfNeeded(this.context, + deploymentDirectoryManager, bootstrapManager); + // Save deployment error information + Deployment deployment = deploymentDirectoryManager.readDeploymentMetadata(); + deployment.setDeploymentStage(bootstrapOnRollbackRequired ? ROLLBACK_BOOTSTRAP : KERNEL_ROLLBACK); + Pair, List> errorReport = + DeploymentErrorCodeUtils.generateErrorReportFromExceptionStack(e); + deployment.setErrorStack(errorReport.getLeft()); + deployment.setErrorTypes(errorReport.getRight()); + deployment.setStageDetails(Utils.generateFailureMessage(e)); + deploymentDirectoryManager.writeDeploymentMetadata(deployment); + } catch (IOException ioException) { + logger.atError() + .setCause(ioException) + .log("Could not read deployment metadata, " + "file is either missing or corrupted"); } - break; - case ROLLBACK_BOOTSTRAP: - logger.atInfo().kv("deploymentStage", deploymentStageAtLaunch).log("Resume deployment"); - Path bootstrapTaskFilePath; try { - bootstrapTaskFilePath = deploymentDirectoryManager.getRollbackBootstrapTaskFilePath(); - executeBootstrapTasksAndShutdown(bootstrapManager, bootstrapTaskFilePath); - } catch (ServiceUpdateException | IOException e) { - logger.atError().log("Rollback bootstrapping failed", e); - DeploymentQueue deploymentQueue = new DeploymentQueue(); - context.put(DeploymentQueue.class, deploymentQueue); - try { - // Deployment error info should already have been saved during the target deployment failure. - Deployment deployment = deploymentDirectoryManager.readDeploymentMetadata(); - deployment.setDeploymentStage(deploymentStageAtLaunch); - deploymentQueue.offer(deployment); - } catch (IOException ioException) { - logger.atError().setCause(ioException) - .log("Failed to load information for the ongoing deployment. Proceed as default"); - } + kernelAlts.prepareRollback(); + shutdown(30, REQUEST_RESTART); + } catch (IOException ioException) { + logger.atError().setCause(ioException).log("Could not prepare rollback"); kernelLifecycle.launch(); } - break; - case KERNEL_ACTIVATION: - case KERNEL_ROLLBACK: - logger.atInfo().kv("deploymentStage", deploymentStageAtLaunch).log("Resume deployment"); + } + break; + case ROLLBACK_BOOTSTRAP: + logger.atInfo().kv("deploymentStage", deploymentStageAtLaunch).log("Resume deployment"); + Path bootstrapTaskFilePath; + try { + bootstrapTaskFilePath = deploymentDirectoryManager.getRollbackBootstrapTaskFilePath(); + executeBootstrapTasksAndShutdown(bootstrapManager, bootstrapTaskFilePath); + } catch (ServiceUpdateException | IOException e) { + logger.atError().log("Rollback bootstrapping failed", e); DeploymentQueue deploymentQueue = new DeploymentQueue(); context.put(DeploymentQueue.class, deploymentQueue); try { + // Deployment error info should already have been saved during the target deployment failure. Deployment deployment = deploymentDirectoryManager.readDeploymentMetadata(); deployment.setDeploymentStage(deploymentStageAtLaunch); deploymentQueue.offer(deployment); - } catch (IOException e) { - logger.atError().setCause(e) + } catch (IOException ioException) { + logger.atError() + .setCause(ioException) .log("Failed to load information for the ongoing deployment. Proceed as default"); } - // fall through to launch kernel - default: kernelLifecycle.launch(); - break; + } + break; + case KERNEL_ACTIVATION: + case KERNEL_ROLLBACK: + logger.atInfo().kv("deploymentStage", deploymentStageAtLaunch).log("Resume deployment"); + DeploymentQueue deploymentQueue = new DeploymentQueue(); + context.put(DeploymentQueue.class, deploymentQueue); + try { + Deployment deployment = deploymentDirectoryManager.readDeploymentMetadata(); + deployment.setDeploymentStage(deploymentStageAtLaunch); + deploymentQueue.offer(deployment); + } catch (IOException e) { + logger.atError() + .setCause(e) + .log("Failed to load information for the ongoing deployment. Proceed as default"); + } + // fall through to launch kernel + default: + kernelLifecycle.launch(); + break; } return this; } @@ -328,7 +330,7 @@ public void shutdown(int timeoutSeconds) { * Shutdown Kernel within the timeout and exit the process with the given code. * * @param timeoutSeconds Timeout in seconds - * @param exitCode exit code + * @param exitCode exit code */ public void shutdown(int timeoutSeconds, int exitCode) { kernelLifecycle.shutdown(timeoutSeconds, exitCode); @@ -368,16 +370,15 @@ public Collection orderedDependencies() { final HashSet pendingDependencyServices = new LinkedHashSet<>(); getMain().putDependenciesIntoSet(pendingDependencyServices); - final LinkedHashSet dependencyFoundServices = - new DependencyOrder().computeOrderedDependencies(pendingDependencyServices, - s -> s.getDependencies().keySet()); + final LinkedHashSet dependencyFoundServices = new DependencyOrder() + .computeOrderedDependencies(pendingDependencyServices, s -> s.getDependencies().keySet()); return cachedOD = dependencyFoundServices; } } /** - * When a config file gets read, it gets woven together from fragments from multiple sources. This writes a fresh + * When a config file gets read, it gets woven together from fragments from multiple sources. This writes a fresh * copy of the config file, as it is, after the weaving-together process. */ public void writeEffectiveConfig() { @@ -388,7 +389,7 @@ public void writeEffectiveConfig() { } /** - * When a config file gets read, it gets woven together from fragments from multiple sources. This writes a fresh + * When a config file gets read, it gets woven together from fragments from multiple sources. This writes a fresh * copy of the config file, as it is, after the weaving-together process. * * @param p Path to write the effective config into @@ -443,8 +444,8 @@ public Topics findServiceTopic(String serviceName) { * @throws ServiceLoadException if service cannot load */ public GreengrassService locate(String name) throws ServiceLoadException { - return context.getValue(GreengrassService.class, name).computeObjectIfEmpty(v -> - createGreengrassServiceInstance(v, name, this::locate)); + return context.getValue(GreengrassService.class, name) + .computeObjectIfEmpty(v -> createGreengrassServiceInstance(v, name, this::locate)); } /** @@ -473,16 +474,19 @@ private void executeBootstrapTasksAndShutdown(BootstrapManager bootstrapManager, } // If exitCode is 0, which happens when all bootstrap tasks are completed, restart in new launch // directories and verify handover is complete. As a result, exit code 0 is treated as 100 here. - logger.atInfo().log((exitCode == REQUEST_REBOOT ? "device reboot" : "Nucleus restart") - + " requested to complete bootstrap task"); + logger.atInfo() + .log((exitCode == REQUEST_REBOOT ? "device reboot" : "Nucleus restart") + + " requested to complete bootstrap task"); shutdown(30, exitCode == REQUEST_REBOOT ? REQUEST_REBOOT : REQUEST_RESTART); } - @SuppressWarnings( - {"UseSpecificCatch", "PMD.AvoidCatchingThrowable", "PMD.AvoidDeeplyNestedIfStmts", "PMD.ConfusingTernary"}) - private GreengrassService createGreengrassServiceInstance(Context.Value v, String name, CrashableFunction locateFunction) throws ServiceLoadException { + @SuppressWarnings({ + "UseSpecificCatch", "PMD.AvoidCatchingThrowable", "PMD.AvoidDeeplyNestedIfStmts", "PMD.ConfusingTernary" + }) + private GreengrassService createGreengrassServiceInstance(Context.Value v, String name, + CrashableFunction locateFunction) + throws ServiceLoadException { Topics serviceRootTopics = findServiceTopic(name); Class clazz = null; @@ -517,8 +521,8 @@ private GreengrassService createGreengrassServiceInstance(Context.Value v, Strin .get(Coerce.toString(componentTypeTopic).toLowerCase()); // If the mapping didn't exist and the component type is "plugin", then load the service from a // plugin - if (className == null && Coerce.toString(componentTypeTopic) - .equalsIgnoreCase(PLUGIN_SERVICE_TYPE_NAME)) { + if (className == null + && Coerce.toString(componentTypeTopic).equalsIgnoreCase(PLUGIN_SERVICE_TYPE_NAME)) { clazz = locateExternalPlugin(name, serviceRootTopics); } } @@ -537,7 +541,8 @@ private GreengrassService createGreengrassServiceInstance(Context.Value v, Strin if (clazz == null) { Map> si = context.getIfExists(Map.class, CONTEXT_SERVICE_IMPLEMENTERS); if (si != null) { - logger.atInfo().kv(GreengrassService.SERVICE_NAME_KEY, name) + logger.atInfo() + .kv(GreengrassService.SERVICE_NAME_KEY, name) .log("Attempt to load service from plugins"); clazz = si.get(name); } @@ -561,7 +566,7 @@ private GreengrassService createGreengrassServiceInstance(Context.Value v, Strin // Force plugins and built-in services to be singletons if (clazz.getAnnotation(Singleton.class) != null || PluginService.class.isAssignableFrom(clazz) - || clazz.getAnnotation(ImplementsService.class) != null) { + || clazz.getAnnotation(ImplementsService.class) != null) { context.put(ret.getClass(), v); } if (clazz.getAnnotation(ImplementsService.class) != null) { @@ -569,12 +574,10 @@ private GreengrassService createGreengrassServiceInstance(Context.Value v, Strin .withNewerValue(0L, clazz.getAnnotation(ImplementsService.class).version()); } - logger.atDebug("service-loaded").kv(GreengrassService.SERVICE_NAME_KEY, ret.getName()) - .log(); + logger.atDebug("service-loaded").kv(GreengrassService.SERVICE_NAME_KEY, ret.getName()).log(); return ret; } catch (Throwable ex) { - throw new ServiceLoadException("Can't create Greengrass Service instance " + clazz.getSimpleName(), - ex); + throw new ServiceLoadException("Can't create Greengrass Service instance " + clazz.getSimpleName(), ex); } } @@ -592,13 +595,14 @@ private GreengrassService createGreengrassServiceInstance(Context.Value v, Strin return ret; } - @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.CloseResource"}) + @SuppressWarnings({ + "PMD.AvoidCatchingThrowable", "PMD.CloseResource" + }) private Class locateExternalPlugin(String name, Topics serviceRootTopics) throws ServiceLoadException { ComponentIdentifier componentId = ComponentIdentifier.fromServiceTopics(serviceRootTopics); Path pluginJar; try { - pluginJar = nucleusPaths.artifactPath(componentId) - .resolve(componentId.getName() + JAR_FILE_EXTENSION); + pluginJar = nucleusPaths.artifactPath(componentId).resolve(componentId.getName() + JAR_FILE_EXTENSION); } catch (IOException e) { throw new ServiceLoadException(e); } @@ -610,7 +614,8 @@ private Class locateExternalPlugin(String name, Topics serviceRootTopics) thr Topic storedDigest = config.find(SERVICES_NAMESPACE_TOPIC, MAIN_SERVICE_NAME, GreengrassService.RUNTIME_STORE_NAMESPACE_TOPIC, SERVICE_DIGEST_TOPIC_KEY, componentId.toString()); if (storedDigest == null || storedDigest.getOnce() == null) { - logger.atError("plugin-load-error").kv(GreengrassService.SERVICE_NAME_KEY, name) + logger.atError("plugin-load-error") + .kv(GreengrassService.SERVICE_NAME_KEY, name) .log("Local external plugin is not supported by this greengrass version"); throw new CustomPluginNotSupportedException("Locally deployed plugin components are not supported. " + "Plugins must be deployed via a cloud-based deployment."); @@ -619,12 +624,15 @@ private Class locateExternalPlugin(String name, Topics serviceRootTopics) thr try { if (!componentStore.validateComponentRecipeDigest(componentId, Coerce.toString(storedDigest))) { - logger.atError("plugin-load-error").kv(GreengrassService.SERVICE_NAME_KEY, name) + logger.atError("plugin-load-error") + .kv(GreengrassService.SERVICE_NAME_KEY, name) .log("Plugin recipe was modified after it was downloaded from cloud"); throw new ServiceLoadException("Plugin recipe has been modified after it was downloaded"); } } catch (PackageLoadingException e) { - logger.atError("plugin-load-error").setCause(e).kv(GreengrassService.SERVICE_NAME_KEY, name) + logger.atError("plugin-load-error") + .setCause(e) + .kv(GreengrassService.SERVICE_NAME_KEY, name) .log("Unable to calculate local plugin recipe digest"); throw new ServiceLoadException("Unable to calculate local plugin recipe digest", e); } @@ -638,9 +646,10 @@ private Class locateExternalPlugin(String name, Topics serviceRootTopics) thr ImplementsService serviceImplementation = c.getAnnotation(ImplementsService.class); if (serviceImplementation.name().equals(name)) { if (classReference.get() != null) { - logger.atWarn().log("Multiple classes implementing service found in {} " + logger.atWarn() + .log("Multiple classes implementing service found in {} " + "for component {}. Using the first one found: {}", pluginJar, name, - classReference.get()); + classReference.get()); return; } classReference.set(c); @@ -657,7 +666,6 @@ private Class locateExternalPlugin(String name, Topics serviceRootTopics) thr return clazz; } - /** * Get running custom root components, excluding the kernel's built-in services. * @@ -696,40 +704,46 @@ public Kernel parseArgs(String... args) { String configFileName = ""; switch (stage) { - case KERNEL_ACTIVATION: - case BOOTSTRAP: - try { - Path configPath = deploymentDirectoryManager.getTargetConfigFilePath(); - if (!Files.exists(configPath)) { - logger.atError().kv(DEPLOYMENT_STAGE_LOG_KEY, stage).kv("targetConfigFile", configPath) - .log("Detected ongoing deployment, but target configuration file not found"); - break; - } - configFileName = configPath.toString(); - deploymentStageAtLaunch = stage; - } catch (IOException e) { - logger.atError().kv(DEPLOYMENT_STAGE_LOG_KEY, stage) - .log("Detected ongoing deployment, but failed to load target configuration file", e); + case KERNEL_ACTIVATION: + case BOOTSTRAP: + try { + Path configPath = deploymentDirectoryManager.getTargetConfigFilePath(); + if (!Files.exists(configPath)) { + logger.atError() + .kv(DEPLOYMENT_STAGE_LOG_KEY, stage) + .kv("targetConfigFile", configPath) + .log("Detected ongoing deployment, but target configuration file not found"); + break; } - break; - case ROLLBACK_BOOTSTRAP: - case KERNEL_ROLLBACK: - try { - Path configPath = deploymentDirectoryManager.getSnapshotFilePath(); - if (!Files.exists(configPath)) { - logger.atError().kv(DEPLOYMENT_STAGE_LOG_KEY, stage).kv("rollbackConfigFile", configPath) - .log("Detected ongoing deployment, but rollback configuration not found"); - break; - } - configFileName = configPath.toString(); - deploymentStageAtLaunch = stage; - } catch (IOException e) { - logger.atError().kv(DEPLOYMENT_STAGE_LOG_KEY, stage) - .log("Detected ongoing deployment, but failed to load rollback configuration file", e); + configFileName = configPath.toString(); + deploymentStageAtLaunch = stage; + } catch (IOException e) { + logger.atError() + .kv(DEPLOYMENT_STAGE_LOG_KEY, stage) + .log("Detected ongoing deployment, but failed to load target configuration file", e); + } + break; + case ROLLBACK_BOOTSTRAP: + case KERNEL_ROLLBACK: + try { + Path configPath = deploymentDirectoryManager.getSnapshotFilePath(); + if (!Files.exists(configPath)) { + logger.atError() + .kv(DEPLOYMENT_STAGE_LOG_KEY, stage) + .kv("rollbackConfigFile", configPath) + .log("Detected ongoing deployment, but rollback configuration not found"); + break; } - break; - default: - logger.atInfo().log("No ongoing deployment detected. Proceed as default"); + configFileName = configPath.toString(); + deploymentStageAtLaunch = stage; + } catch (IOException e) { + logger.atError() + .kv(DEPLOYMENT_STAGE_LOG_KEY, stage) + .log("Detected ongoing deployment, but failed to load rollback configuration file", e); + } + break; + default: + logger.atInfo().log("No ongoing deployment detected. Proceed as default"); } if (Utils.isEmpty(configFileName)) { kernelLifecycle.initConfigAndTlog(); @@ -781,8 +795,8 @@ void initializeNucleusFromRecipe(String nucleusComponentName) { logger.atError().log("Unable to set up Nucleus from build recipe file", e); } - initializeNucleusVersion(nucleusComponentName, componentVersion == null - ? DeviceConfiguration.FALLBACK_VERSION : componentVersion.toString()); + initializeNucleusVersion(nucleusComponentName, + componentVersion == null ? DeviceConfiguration.FALLBACK_VERSION : componentVersion.toString()); } void persistInitialLaunchParams(KernelAlternatives kernelAlts, String nucleusComponentName) { @@ -792,8 +806,11 @@ void persistInitialLaunchParams(KernelAlternatives kernelAlts, String nucleusCom } // Persist initial Nucleus launch parameters try { - String jvmOptions = ManagementFactory.getRuntimeMXBean().getInputArguments() - .stream().sorted().filter(s -> !s.startsWith(DeviceConfiguration.JVM_OPTION_ROOT_PATH)) + String jvmOptions = ManagementFactory.getRuntimeMXBean() + .getInputArguments() + .stream() + .sorted() + .filter(s -> !s.startsWith(DeviceConfiguration.JVM_OPTION_ROOT_PATH)) // if windows, we wrap each JVM option with double quotes to preserve special characters in input; // not providing this option on linux because it would break the loader script. .map(s -> PlatformResolver.isWindows ? "\"" + s + "\"" : s) @@ -816,8 +833,8 @@ void initializeNucleusLifecycleConfig(String nucleusComponentName, ComponentReci if (nucleusDependencies == null) { nucleusDependencies = Collections.emptyMap(); } - config.lookup(DEFAULT_VALUE_TIMESTAMP, SERVICES_NAMESPACE_TOPIC, - nucleusComponentName, SERVICE_DEPENDENCIES_NAMESPACE_TOPIC) + config.lookup(DEFAULT_VALUE_TIMESTAMP, SERVICES_NAMESPACE_TOPIC, nucleusComponentName, + SERVICE_DEPENDENCIES_NAMESPACE_TOPIC) .dflt(kernelConfigResolver.generateServiceDependencies(nucleusDependencies)); Topics nucleusLifecycle = config.lookupTopics(DEFAULT_VALUE_TIMESTAMP, SERVICES_NAMESPACE_TOPIC, @@ -833,8 +850,7 @@ void initializeNucleusLifecycleConfig(String nucleusComponentName, ComponentReci try { Object interpolatedLifecycle = kernelConfigResolver.interpolate(componentRecipe.getLifecycle(), new ComponentIdentifier(nucleusComponentName, componentRecipe.getVersion()), - nucleusDependencies.keySet(), - config.lookupTopics(SERVICES_NAMESPACE_TOPIC).toPOJO()); + nucleusDependencies.keySet(), config.lookupTopics(SERVICES_NAMESPACE_TOPIC).toPOJO()); nucleusLifecycle.replaceAndWait((Map) interpolatedLifecycle); logger.atInfo().log("Nucleus lifecycle has been initialized successfully"); } catch (IOException e) { @@ -842,9 +858,8 @@ void initializeNucleusLifecycleConfig(String nucleusComponentName, ComponentReci } } - void initializeComponentStore(KernelAlternatives kernelAlts, String nucleusComponentName, - Semver componentVersion, Path recipePath, - Path unpackDir) throws IOException, PackageLoadingException { + void initializeComponentStore(KernelAlternatives kernelAlts, String nucleusComponentName, Semver componentVersion, + Path recipePath, Path unpackDir) throws IOException, PackageLoadingException { // Copy recipe to component store ComponentStore componentStore = context.get(ComponentStore.class); ComponentIdentifier componentIdentifier = new ComponentIdentifier(nucleusComponentName, componentVersion); @@ -854,16 +869,22 @@ void initializeComponentStore(KernelAlternatives kernelAlts, String nucleusCompo } // Copy unpacked artifacts to component store - Path destinationArtifactPath = context.get(NucleusPaths.class).unarchiveArtifactPath( - componentIdentifier, DEFAULT_NUCLEUS_COMPONENT_NAME.toLowerCase(Locale.ROOT)); + Path destinationArtifactPath = context.get(NucleusPaths.class) + .unarchiveArtifactPath(componentIdentifier, DEFAULT_NUCLEUS_COMPONENT_NAME.toLowerCase(Locale.ROOT)); if (Files.isSameFile(unpackDir, destinationArtifactPath)) { logger.atDebug().log("Nucleus artifacts have already been loaded to component store"); return; } copyUnpackedNucleusArtifacts(unpackDir, destinationArtifactPath); - Permissions.setArtifactPermission(destinationArtifactPath, FileSystemPermission.builder() - .ownerRead(true).ownerExecute(true).groupRead(true).groupExecute(true) - .otherRead(true).otherExecute(true).build()); + Permissions.setArtifactPermission(destinationArtifactPath, + FileSystemPermission.builder() + .ownerRead(true) + .ownerExecute(true) + .groupRead(true) + .groupExecute(true) + .otherRead(true) + .otherExecute(true) + .build()); // Relink the alts init path to point to the artifact since we've just installed. This will allow the // customer to delete their unzipped Nucleus distribution. This will not change the "current" symlink // so that if current points to something other than init, we won't be messing with that. @@ -887,8 +908,7 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th return FileVisitResult.CONTINUE; } - @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", - justification = "Spotbugs false positive") + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Spotbugs false positive") @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativeFile = src.relativize(file); @@ -904,8 +924,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } void initializeNucleusVersion(String nucleusComponentName, String nucleusComponentVersion) { - config.lookup(SERVICES_NAMESPACE_TOPIC, nucleusComponentName, - VERSION_CONFIG_KEY).dflt(nucleusComponentVersion); + config.lookup(SERVICES_NAMESPACE_TOPIC, nucleusComponentName, VERSION_CONFIG_KEY).dflt(nucleusComponentVersion); config.lookup(SETENV_CONFIG_NAMESPACE, GGC_VERSION_ENV).overrideValue(nucleusComponentVersion); } @@ -918,17 +937,18 @@ public List getSupportedCapabilities() { } /** - * Finds all auto startable services with auto startable dependencies. - * This method performs a breadth-first search, starting from the target services and traversing through - * all hard dependencies and exclude non auto startable services from. + * Finds all auto startable services with auto startable dependencies. This method performs a breadth-first search, + * starting from the target services and traversing through all hard dependencies and exclude non auto startable + * services from. * - * @return a set of all services that only contains auto startable services and their dependencies are all - * auto startable services + * @return a set of all services that only contains auto startable services and their dependencies are all auto + * startable services */ public Set findAutoStartableServicesToTrack() { // Find all non auto startable services Set nonAutoStartableServices = orderedDependencies().stream() - .filter(service -> !service.shouldAutoStart()).collect(Collectors.toSet()); + .filter(service -> !service.shouldAutoStart()) + .collect(Collectors.toSet()); Set nonAutoStartableDependers = findDependers(nonAutoStartableServices); @@ -939,9 +959,9 @@ public Set findAutoStartableServicesToTrack() { } /** - * Finds all services which are dependers of initial services, directly or indirectly - * This method performs a breadth-first search, starting from the initial services and traversing through - * all hard dependencies. + * Finds all services which are dependers of initial services, directly or indirectly This method performs a + * breadth-first search, starting from the initial services and traversing through all hard dependencies. + * * @param initialServices the set of services that we want to find dependers * @return a set of all services that depend on the target services, including the initial services */ diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java index 7cc81b16c7..4cc58e71d5 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java @@ -133,7 +133,8 @@ public Path getLoaderPath() { } private Path getLoaderPathFromLaunchDir(Path path) { - return path.resolve(KERNEL_DISTRIBUTION_DIR).resolve(KERNEL_BIN_DIR) + return path.resolve(KERNEL_DISTRIBUTION_DIR) + .resolve(KERNEL_BIN_DIR) .resolve(Platform.getInstance().loaderFilename()); } @@ -239,8 +240,8 @@ public void setupInitLaunchDirIfAbsent() throws IOException { * Unconditionally relink alts/init to the provided path and alts/current to alts/init. * * @param pathToNucleusDistro path to the unzipped Nucleus distribution - * @param linkCurrentToInit relink the current path to the init path, false if current should be left alone and - * only init should be relinked. + * @param linkCurrentToInit relink the current path to the init path, false if current should be left alone and only + * init should be relinked. * @throws IOException on I/O error */ public void relinkInitLaunchDir(Path pathToNucleusDistro, boolean linkCurrentToInit) throws IOException { @@ -256,18 +257,14 @@ public void relinkInitLaunchDir(Path pathToNucleusDistro, boolean linkCurrentToI } if (!isLaunchDirSetup()) { - throw new IOException("Failed to setup initial launch directory. Expecting loader script at: " - + getLoaderPath()); + throw new IOException( + "Failed to setup initial launch directory. Expecting loader script at: " + getLoaderPath()); } } /** - * Locate launch directory of Kernel, assuming unpack directory tree as below. - * ├── bin - * │ ├── greengrass.service.template - * │ └── loader - * └── lib - * └── Greengrass.jar + * Locate launch directory of Kernel, assuming unpack directory tree as below. ├── bin │ ├── + * greengrass.service.template │ └── loader └── lib └── Greengrass.jar * * @return Path of the unpack directory * @throws IOException if directory structure does not match the expectation @@ -279,9 +276,10 @@ public static Path locateCurrentKernelUnpackDir() throws IOException, URISyntaxE Path parentDir; try { parentDir = new File(KernelAlternatives.class.getProtectionDomain().getCodeSource().getLocation().toURI()) - .toPath().getParent(); - if (parentDir == null || !Files.exists(parentDir) || parentDir.getFileName() != null && !KERNEL_LIB_DIR - .equals(parentDir.getFileName().toString())) { + .toPath() + .getParent(); + if (parentDir == null || !Files.exists(parentDir) + || parentDir.getFileName() != null && !KERNEL_LIB_DIR.equals(parentDir.getFileName().toString())) { throw new IOException("Unable to locate the unpack directory of Nucleus Jar file"); } } catch (IllegalArgumentException e) { @@ -304,7 +302,7 @@ public static Path locateCurrentKernelUnpackDir() throws IOException, URISyntaxE * @return DeploymentStage */ public Deployment.DeploymentStage determineDeploymentStage(BootstrapManager bootstrapManager, - DeploymentDirectoryManager deploymentDirectoryManager) { + DeploymentDirectoryManager deploymentDirectoryManager) { if (getOldDir().toFile().exists()) { try { Path persistedBootstrapTasks = deploymentDirectoryManager.getBootstrapTaskFilePath(); @@ -345,9 +343,11 @@ public void activationSucceeds() throws IOException { Path launchDirToCleanUp = Files.readSymbolicLink(getOldDir()).toAbsolutePath(); Files.delete(getOldDir()); if (Files.isSameFile(launchDirToCleanUp, getCurrentDir())) { - logger.atInfo().kv("oldDir", launchDirToCleanUp).log("Skipping launch directory cleanup after kernel " - + "update due to matching directory names. Likely the same deployment was executed twice on the " - + "device"); + logger.atInfo() + .kv("oldDir", launchDirToCleanUp) + .log("Skipping launch directory cleanup after kernel " + + "update due to matching directory names. Likely the same deployment was executed twice on the " + + "device"); return; } logger.atDebug().kv("oldDir", launchDirToCleanUp).log("Cleaning up previous kernel launch directory"); @@ -408,14 +408,14 @@ public void prepareBootstrap(String deploymentId) throws IOException { } /** - * Cleans up loader logs dumped in loader.log by acquiring a lock on the file first as - * Windows FS does not allow a brute force truncate. + * Cleans up loader logs dumped in loader.log by acquiring a lock on the file first as Windows FS does not allow a + * brute force truncate. */ @SuppressWarnings("PMD.AvoidFileStream") protected void cleanupLoaderLogs() { logger.atDebug().kv("logs-path", getLoaderLogsPath().toAbsolutePath()).log("Cleaning up Nucleus logs"); try (FileOutputStream fos = new FileOutputStream(getLoaderLogsPath().toAbsolutePath().toString()); - FileChannel channel = fos.getChannel()) { + FileChannel channel = fos.getChannel()) { // Try to acquire a lock FileLock lock = channel.tryLock(); @@ -466,8 +466,7 @@ public void cleanupLaunchDirectoryLinks() { * @return true if bootstrapping is required during rollback, otherwise false */ public boolean prepareBootstrapOnRollbackIfNeeded(Context context, - DeploymentDirectoryManager deploymentDirectoryManager, - BootstrapManager bootstrapManager) { + DeploymentDirectoryManager deploymentDirectoryManager, BootstrapManager bootstrapManager) { Configuration rollbackConfig = new Configuration(context); try { rollbackConfig.read(deploymentDirectoryManager.getSnapshotFilePath()); @@ -480,8 +479,8 @@ public boolean prepareBootstrapOnRollbackIfNeeded(Context context, // Check if we need to execute component bootstrap steps during the rollback deployment. final Set componentsToExclude = getComponentsToExcludeFromBootstrapOnRollback(bootstrapManager, rollbackConfig); - bootstrapOnRollbackRequired = bootstrapManager.isBootstrapRequired(rollbackConfig.toPOJO(), - componentsToExclude); + bootstrapOnRollbackRequired = + bootstrapManager.isBootstrapRequired(rollbackConfig.toPOJO(), componentsToExclude); } catch (ServiceUpdateException | ComponentConfigurationValidationException exc) { logger.atError().log("Rollback config invalid or could not be parsed", exc); return false; @@ -502,8 +501,9 @@ public boolean prepareBootstrapOnRollbackIfNeeded(Context context, return false; } } else { - logger.atInfo().log("No component with a pending rollback bootstrap task found: " - + "No rollback deployment exists or rollback deployment has no bootstrap tasks"); + logger.atInfo() + .log("No component with a pending rollback bootstrap task found: " + + "No rollback deployment exists or rollback deployment has no bootstrap tasks"); // Bootstrap-on-rollback is not required, so ensure that the task file is deleted. try { bootstrapManager.deleteBootstrapTaskList(rollbackBootstrapTaskFilePath); @@ -516,15 +516,17 @@ public boolean prepareBootstrapOnRollbackIfNeeded(Context context, } private Set getComponentsToExcludeFromBootstrapOnRollback(BootstrapManager bootstrapManager, - Configuration rollbackConfig) { + Configuration rollbackConfig) { // Exclude components with bootstrap steps that did not execute during the target deployment. final Set componentsToExclude = bootstrapManager.getUnstartedTasks(); - logger.atDebug().kv("components", componentsToExclude) + logger.atDebug() + .kv("components", componentsToExclude) .log("These components did not bootstrap during the target deployment. " + "They will be excluded from bootstrap-on-rollback."); // Exclude components that are not explicitly configured to bootstrap-on-rollback Set unconfiguredComponents = getComponentsNotConfiguredToBootstrapOnRollback(rollbackConfig); - logger.atDebug().kv("components", unconfiguredComponents) + logger.atDebug() + .kv("components", unconfiguredComponents) .log("These components are not configured to execute bootstrap steps during rollback. " + "They will be excluded from bootstrap-on-rollback."); componentsToExclude.addAll(unconfiguredComponents); @@ -540,9 +542,9 @@ private Set getComponentsNotConfiguredToBootstrapOnRollback(Configuratio services.forEach((service) -> { String serviceName = service.getName(); if (service instanceof Topics) { - boolean bootstrapOnRollback = Coerce.toBoolean(((Topics) service).findOrDefault(false, - SERVICE_LIFECYCLE_NAMESPACE_TOPIC, LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC, - BOOTSTRAP_ON_ROLLBACK_CONFIG_KEY)); + boolean bootstrapOnRollback = + Coerce.toBoolean(((Topics) service).findOrDefault(false, SERVICE_LIFECYCLE_NAMESPACE_TOPIC, + LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC, BOOTSTRAP_ON_ROLLBACK_CONFIG_KEY)); if (!bootstrapOnRollback) { componentsNotConfiguredToBootstrapOnRollback.add(serviceName); } @@ -555,8 +557,9 @@ private void cleanupLaunchDirectoryLink(Path link) { try { Files.deleteIfExists(link); } catch (IOException e) { - logger.atWarn().kv("link", link).log( - "Failed to clean up launch directory link from previous deployments", e); + logger.atWarn() + .kv("link", link) + .log("Failed to clean up launch directory link from previous deployments", e); } } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelCommandLine.java b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelCommandLine.java index 5bf9afbb14..c9ac18c551 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelCommandLine.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelCommandLine.java @@ -88,42 +88,41 @@ public void parseArgs(String... args) { while (getArg() != null) { switch (arg.toLowerCase()) { - case "--config": - case "-i": - String configArg = getArg(); - Objects.requireNonNull(configArg, "-i or --config requires an argument"); - providedConfigPathName = deTilde(configArg); - break; - case "--init-config": - case "-init": - String initArg = getArg(); - Objects.requireNonNull(initArg, "-init or --init-config requires an argument"); - providedInitialConfigPath = deTilde(initArg); - break; - case "--root": - case "-r": - rootAbsolutePath = getArg(); - Objects.requireNonNull(rootAbsolutePath, "-r or --root requires an argument"); - break; - case "--aws-region": - case "-ar": - awsRegionFromCmdLine = getArg(); - break; - case "--env-stage": - case "-es": - envStageFromCmdLine = getArg(); - break; - case "--component-default-user": - case "-u": - String user = getArg(); - Objects.requireNonNull(user, "-u or --component-default-user requires an argument"); - defaultUserFromCmdLine = user; - break; - default: - RuntimeException rte = - new RuntimeException(String.format("Undefined command line argument: %s", arg)); - logger.atError().setEventType("parse-args-error").setCause(rte).log(); - throw rte; + case "--config": + case "-i": + String configArg = getArg(); + Objects.requireNonNull(configArg, "-i or --config requires an argument"); + providedConfigPathName = deTilde(configArg); + break; + case "--init-config": + case "-init": + String initArg = getArg(); + Objects.requireNonNull(initArg, "-init or --init-config requires an argument"); + providedInitialConfigPath = deTilde(initArg); + break; + case "--root": + case "-r": + rootAbsolutePath = getArg(); + Objects.requireNonNull(rootAbsolutePath, "-r or --root requires an argument"); + break; + case "--aws-region": + case "-ar": + awsRegionFromCmdLine = getArg(); + break; + case "--env-stage": + case "-es": + envStageFromCmdLine = getArg(); + break; + case "--component-default-user": + case "-u": + String user = getArg(); + Objects.requireNonNull(user, "-u or --component-default-user requires an argument"); + defaultUserFromCmdLine = user; + break; + default: + RuntimeException rte = new RuntimeException(String.format("Undefined command line argument: %s", arg)); + logger.atError().setEventType("parse-args-error").setCause(rte).log(); + throw rte; } } @@ -132,19 +131,21 @@ public void parseArgs(String... args) { if (Utils.isEmpty(rootAbsolutePath) && Utils.isNotEmpty(providedInitialConfigPath) && Files.exists(Paths.get(providedInitialConfigPath))) { try { - rootAbsolutePath = Coerce.toString( - new Configuration(kernel.getContext()).read(providedInitialConfigPath) + rootAbsolutePath = + Coerce.toString(new Configuration(kernel.getContext()).read(providedInitialConfigPath) .lookup("system", "rootpath")); } catch (IOException ignored) { // Any reading exception in initial config will be raised up later. For now we will continue. } } if (Utils.isEmpty(rootAbsolutePath)) { - rootAbsolutePath = "~/.greengrass"; // Default to hidden subdirectory of home. + rootAbsolutePath = "~/.greengrass"; // Default to hidden subdirectory of home. } rootAbsolutePath = deTilde(rootAbsolutePath); - kernel.getConfig().lookup("system", "rootpath").dflt(rootAbsolutePath) + kernel.getConfig() + .lookup("system", "rootpath") + .dflt(rootAbsolutePath) .subscribe((whatHappened, topic) -> initPaths(Coerce.toString(topic))); bootstrapManager = new BootstrapManager(kernel); kernel.getContext().put(BootstrapManager.class, bootstrapManager); @@ -175,12 +176,12 @@ private void initPaths(String rootAbsolutePath) { try { // Set root path first, so that deTilde works on the subsequent calls nucleusPaths.setRootPath(Paths.get(rootAbsolutePath).toAbsolutePath()); - //set root path for the telemetry logger + // set root path for the telemetry logger TelemetryConfig.getInstance().setRoot(Paths.get(deTilde(ROOT_DIR_PREFIX))); LogManager.setRoot(Paths.get(deTilde(ROOT_DIR_PREFIX))); nucleusPaths.setTelemetryPath(TelemetryConfig.getInstance().getStoreDirectory()); - String storeDirectory = LogManager.getRootLogConfiguration().getStoreDirectory().toAbsolutePath() - .toString(); + String storeDirectory = + LogManager.getRootLogConfiguration().getStoreDirectory().toAbsolutePath().toString(); NucleusPaths.setLoggerPath(Paths.get(storeDirectory)); nucleusPaths.initPaths(Paths.get(rootAbsolutePath).toAbsolutePath(), Paths.get(deTilde(workPathName)).toAbsolutePath(), diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelLifecycle.java b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelLifecycle.java index 377a5622b5..7f0e9ce273 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelLifecycle.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelLifecycle.java @@ -87,32 +87,31 @@ public class KernelLifecycle { private static final int EXECUTOR_SERVICE_SHUTDOWN_TIMEOUT_SECONDS = 5; // Enum for provision policy will exist in common library package // This will be done as part of re-provisioning - // TODO: Use the enum from common library when available + // TODO: Use the enum from common library when available private static final String DEFAULT_PROVISIONING_POLICY = "PROVISION_IF_NOT_PROVISIONED"; private static final String SYSTEM_SHUTDOWN_EVENT = "system-shutdown"; private static final int MAX_PROVISIONING_PLUGIN_RETRY_ATTEMPTS = 3; - public static final String MULTIPLE_PROVISIONING_PLUGINS_FOUND_EXCEPTION = "Multiple provisioning plugins found " - + "[%s]. Greengrass expects only one provisioning plugin"; + public static final String MULTIPLE_PROVISIONING_PLUGINS_FOUND_EXCEPTION = + "Multiple provisioning plugins found " + "[%s]. Greengrass expects only one provisioning plugin"; public static final String UPDATED_PROVISIONING_MESSAGE = "Updated provisioning configuration"; - private static final List> BUILTIN_SERVICES = - Arrays.asList(DockerApplicationManagerService.class, UpdateSystemPolicyService.class, - DeploymentService.class, FleetStatusService.class, TelemetryAgent.class, - TokenExchangeService.class); + private static final List> BUILTIN_SERVICES = Arrays.asList( + DockerApplicationManagerService.class, UpdateSystemPolicyService.class, DeploymentService.class, + FleetStatusService.class, TelemetryAgent.class, TokenExchangeService.class); private final Kernel kernel; private final KernelCommandLine kernelCommandLine; private final Map> serviceImplementors = new HashMap<>(); private final NucleusPaths nucleusPaths; - @Setter (AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) private ProvisioningConfigUpdateHelper provisioningConfigUpdateHelper; - @Setter (AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) private ProvisioningPluginFactory provisioningPluginFactory; // setter for unit testing @Setter(AccessLevel.PACKAGE) - private List> startables = Arrays.asList(IPCEventStreamService.class, - AuthorizationService.class, ConfigStoreIPCService.class, LifecycleIPCService.class, - PubSubIPCService.class, ComponentMetricIPCService.class); + private List> startables = + Arrays.asList(IPCEventStreamService.class, AuthorizationService.class, ConfigStoreIPCService.class, + LifecycleIPCService.class, PubSubIPCService.class, ComponentMetricIPCService.class); @Setter(AccessLevel.PACKAGE) private List> postPluginStartables = Collections.singletonList(MqttProxyIPCService.class); @@ -141,10 +140,11 @@ public KernelLifecycle(Kernel kernel, KernelCommandLine kernelCommandLine, Nucle * Startup the Kernel and all services. */ public void launch() { - logger.atInfo("system-start").kv("version", - kernel.getContext().get(DeviceConfiguration.class).getNucleusVersion()) + logger.atInfo("system-start") + .kv("version", kernel.getContext().get(DeviceConfiguration.class).getNucleusVersion()) .kv("rootPath", nucleusPaths.rootPath()) - .kv("configPath", nucleusPaths.configPath()).log("Launch Nucleus"); + .kv("configPath", nucleusPaths.configPath()) + .log("Launch Nucleus"); // Startup builtin non-services. This is blocking, so it will wait for them to be running. // This guarantees that IPC, for example, is running before any user code @@ -155,7 +155,7 @@ public void launch() { final List provisioningPlugins = findProvisioningPlugins(); // Must be called before everything else so that these are available to be // referenced by main/dependencies of main - final Queue autostart = findBuiltInServicesAndPlugins(); //NOPMD + final Queue autostart = findBuiltInServicesAndPlugins(); // NOPMD loadPlugins(); // Start MqttProxyIPCService after plugins are loaded, as it requires @@ -171,8 +171,8 @@ public void launch() { // Multiple provisioning plugins may need plugin ordering. We do not support plugin ordering right now // There is also no compelling use case right now for multiple provisioning plugins. if (provisioningPlugins.size() > 1) { - String errorString = String.format(MULTIPLE_PROVISIONING_PLUGINS_FOUND_EXCEPTION, - provisioningPlugins.toString()); + String errorString = + String.format(MULTIPLE_PROVISIONING_PLUGINS_FOUND_EXCEPTION, provisioningPlugins.toString()); throw new RuntimeException(errorString); } executeProvisioningPlugin(provisioningPlugins.get(0)); @@ -201,8 +201,10 @@ public void launch() { ((FleetStatusService) fleetStatusService).triggerFleetStatusUpdateAtKernelLaunch(); } } catch (ServiceLoadException e) { - logger.atError().setCause(e).log("Failed to send status update at kernel launch because kernel was " - + "unable to locate FleetStatusService"); + logger.atError() + .setCause(e) + .log("Failed to send status update at kernel launch because kernel was " + + "unable to locate FleetStatusService"); } } @@ -217,27 +219,28 @@ private void executeProvisioningPlugin(DeviceIdentityInterface provisioningPlugi executorService.execute(() -> { String pluginName = provisioningPlugin.name(); logger.atInfo().log("Running provisioning plugin: " + pluginName); - Topics pluginConfig = kernel.getConfig() - .findTopics(SERVICES_NAMESPACE_TOPIC, pluginName, CONFIGURATION_CONFIG_KEY); + Topics pluginConfig = + kernel.getConfig().findTopics(SERVICES_NAMESPACE_TOPIC, pluginName, CONFIGURATION_CONFIG_KEY); ProvisionConfiguration provisionConfiguration = null; try { provisionConfiguration = RetryUtils.runWithRetry(retryConfig, - () -> provisioningPlugin.updateIdentityConfiguration(new ProvisionContext( - DEFAULT_PROVISIONING_POLICY, pluginConfig == null - ? Collections.emptyMap() : pluginConfig.toPOJO())), + () -> provisioningPlugin + .updateIdentityConfiguration(new ProvisionContext(DEFAULT_PROVISIONING_POLICY, + pluginConfig == null ? Collections.emptyMap() : pluginConfig.toPOJO())), "Running provisioning plugin", logger); } catch (Exception e) { - logger.atError().setCause(e).log("Caught exception while running provisioning plugin. " - + "Moving on to run Greengrass without provisioning"); + logger.atError() + .setCause(e) + .log("Caught exception while running provisioning plugin. " + + "Moving on to run Greengrass without provisioning"); return; } - provisioningConfigUpdateHelper.updateSystemConfiguration(provisionConfiguration - .getSystemConfiguration(), UpdateBehaviorTree.UpdateBehavior.MERGE); - provisioningConfigUpdateHelper.updateNucleusConfiguration(provisionConfiguration - .getNucleusConfiguration(), UpdateBehaviorTree.UpdateBehavior.MERGE); - logger.atDebug().kv("PluginName", pluginName) - .log(UPDATED_PROVISIONING_MESSAGE); + provisioningConfigUpdateHelper.updateSystemConfiguration(provisionConfiguration.getSystemConfiguration(), + UpdateBehaviorTree.UpdateBehavior.MERGE); + provisioningConfigUpdateHelper.updateNucleusConfiguration(provisionConfiguration.getNucleusConfiguration(), + UpdateBehaviorTree.UpdateBehavior.MERGE); + logger.atDebug().kv("PluginName", pluginName).log(UPDATED_PROVISIONING_MESSAGE); }); } @@ -255,7 +258,9 @@ private List findProvisioningPlugins() { provisioningPluginNames.add(c.getName()); } } catch (InstantiationException | IllegalAccessException e) { - logger.atError().kv("Plugin", c.getName()).setCause(e) + logger.atError() + .kv("Plugin", c.getName()) + .setCause(e) .log("Error instantiating a provisioning plugin"); } }); @@ -268,9 +273,11 @@ private List findProvisioningPlugins() { void initConfigAndTlog(String configFilePath) { String configFileInput = kernelCommandLine.getProvidedConfigPathName(); if (!Utils.isEmpty(configFileInput)) { - logger.atWarn().kv("configFileInput", configFileInput).kv("configOverride", configFilePath) + logger.atWarn() + .kv("configFileInput", configFileInput) + .kv("configOverride", configFilePath) .log("Detected ongoing deployment. Ignore the config file from input and use " - + "config file override"); + + "config file override"); } kernelCommandLine.setProvidedConfigPathName(configFilePath); initConfigAndTlog(); @@ -292,9 +299,8 @@ void initConfigAndTlog() { // config.tlog is valid if any incomplete tlog truncation is handled correctly and the tlog content // is validated - boolean transactionTlogValid = - handleIncompleteTlogTruncation(transactionLogPath) && ConfigurationReader.validateTlog( - transactionLogPath); + boolean transactionTlogValid = handleIncompleteTlogTruncation(transactionLogPath) + && ConfigurationReader.validateTlog(transactionLogPath); // if config.tlog is valid, read the tlog first because the yaml config file may not be up to date if (transactionTlogValid) { @@ -335,7 +341,8 @@ void initConfigAndTlog() { // hook tlog to config so that changes over time are persisted to the tlog tlog = ConfigurationWriter.logTransactionsTo(kernel.getConfig(), transactionLogPath) - .flushImmediately(true).withAutoTruncate(kernel.getContext()); + .flushImmediately(true) + .withAutoTruncate(kernel.getContext()); } catch (IOException ioe) { logger.atError().setEventType("nucleus-read-config-error").setCause(ioe).log(); throw new RuntimeException(ioe); @@ -346,8 +353,9 @@ void initConfigAndTlog() { * Check if last tlog truncation was interrupted and undo its effect * * @param transactionLogPath path to config.tlog - * @return true if last tlog truncation was complete or if we are able to undo its effect; - * false only if there was an IO error while undoing its effect (renaming the old tlog file) + * + * @return true if last tlog truncation was complete or if we are able to undo its effect; false only if there was + * an IO error while undoing its effect (renaming the old tlog file) */ private boolean handleIncompleteTlogTruncation(Path transactionLogPath) { Path oldTlogPath = ConfigurationWriter.getOldTlogPath(transactionLogPath); @@ -357,14 +365,17 @@ private boolean handleIncompleteTlogTruncation(Path transactionLogPath) { if (Files.exists(oldTlogPath)) { // we don't need to validate the content of old tlog here, since the existence of old tlog itself signals // that the content in config.tlog at the moment is unusable - logger.atWarn().log("Config tlog truncation was interrupted by last nucleus shutdown and an old version " - + "of config.tlog exists. Undoing the effect of incomplete truncation by moving {} back to {}", - oldTlogPath, transactionLogPath); + logger.atWarn() + .log("Config tlog truncation was interrupted by last nucleus shutdown and an old version " + + "of config.tlog exists. Undoing the effect of incomplete truncation by moving {} back to {}", + oldTlogPath, transactionLogPath); try { Files.move(oldTlogPath, transactionLogPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - logger.atError().setCause(e).log("An IO error occurred while moving the old tlog file. Will " - + "attempt to load from backup configs"); + logger.atError() + .setCause(e) + .log("An IO error occurred while moving the old tlog file. Will " + + "attempt to load from backup configs"); return false; } } @@ -380,30 +391,31 @@ private boolean handleIncompleteTlogTruncation(Path transactionLogPath) { } /* - * Read configs from backup tlog files. - * the fallback order is config.tlog~ -> bootstrap.tlog -> bootstrap.tlog~ + * Read configs from backup tlog files. the fallback order is config.tlog~ -> bootstrap.tlog -> bootstrap.tlog~ * * @param transactionLogPath path to main config tlog - * @param bootstrapTlogPath path to bootstrap config tlog - * @throws IOException IO error while reading file + * + * @param bootstrapTlogPath path to bootstrap config tlog + * + * @throws IOException IO error while reading file */ private void readConfigFromBackUpTLog(Path transactionLogPath, Path bootstrapTlogPath) throws IOException { - List tlogBackupPathsInOrder = - Arrays.asList(CommitableFile.getBackupFile(transactionLogPath), // config.tlog~ - bootstrapTlogPath, // bootstrap.tlog - CommitableFile.getBackupFile(bootstrapTlogPath) // bootstrap.tlog~ - ); + List tlogBackupPathsInOrder = Arrays.asList(CommitableFile.getBackupFile(transactionLogPath), // config.tlog~ + bootstrapTlogPath, // bootstrap.tlog + CommitableFile.getBackupFile(bootstrapTlogPath) // bootstrap.tlog~ + ); for (Path tlogBackupPath : tlogBackupPathsInOrder) { if (ConfigurationReader.validateTlog(tlogBackupPath)) { - logger.atError().log("Transaction log {} is invalid, will attempt to load configuration from {}", - transactionLogPath, tlogBackupPath); + logger.atError() + .log("Transaction log {} is invalid, will attempt to load configuration from {}", + transactionLogPath, tlogBackupPath); kernel.getConfig().read(tlogBackupPath); return; } } - logger.atWarn().log("Transaction log {} is invalid and no usable backup transaction log exists. Either an " - + "initial Nucleus setup is ongoing or all config tlogs were corrupted", - transactionLogPath); + logger.atWarn() + .log("Transaction log {} is invalid and no usable backup transaction log exists. Either an " + + "initial Nucleus setup is ongoing or all config tlogs were corrupted", transactionLogPath); } @SuppressWarnings("PMD.CloseResource") @@ -414,8 +426,9 @@ private Queue findBuiltInServicesAndPlugins() { pim.withCacheDirectory(nucleusPaths.pluginPath()); pim.annotated(ImplementsService.class, cl -> { if (!GreengrassService.class.isAssignableFrom(cl)) { - logger.atError().log("{} needs to be a subclass of GreengrassService " - + "in order to use ImplementsService", cl); + logger.atError() + .log("{} needs to be a subclass of GreengrassService " + + "in order to use ImplementsService", cl); return; } ImplementsService is = cl.getAnnotation(ImplementsService.class); @@ -462,7 +475,9 @@ private void loadPlugins() { * Make all services startup in order. */ public void startupAllServices() { - kernel.orderedDependencies().stream().filter(GreengrassService::shouldAutoStart) + kernel.orderedDependencies() + .stream() + .filter(GreengrassService::shouldAutoStart) .forEach(GreengrassService::requestStart); } @@ -482,7 +497,8 @@ public void stopAllServices(int timeoutSeconds) { arr[i] = d[i].close(); arr[i].whenComplete((v, t) -> { if (t != null) { - logger.atError("service-shutdown-error", t).kv(GreengrassService.SERVICE_NAME_KEY, serviceName) + logger.atError("service-shutdown-error", t) + .kv(GreengrassService.SERVICE_NAME_KEY, serviceName) .log(); } }); @@ -501,15 +517,17 @@ public void stopAllServices(int timeoutSeconds) { } combinedFuture.get(timeoutSeconds, TimeUnit.SECONDS); } catch (ExecutionException | InterruptedException | TimeoutException e) { - List unclosedServices = - IntStream.range(0, arr.length).filter((i) -> !arr[i].isDone() || arr[i].isCompletedExceptionally()) - .mapToObj((i) -> d[i].getName()).collect(Collectors.toList()); + List unclosedServices = IntStream.range(0, arr.length) + .filter((i) -> !arr[i].isDone() || arr[i].isCompletedExceptionally()) + .mapToObj((i) -> d[i].getName()) + .collect(Collectors.toList()); logger.atError("services-shutdown-errored", e).kv("unclosedServices", unclosedServices).log(); } } /** * Shutdown transaction log and all services with given timeout. + * * @param timeoutSeconds Timeout in seconds */ public void softShutdown(int timeoutSeconds) { @@ -574,8 +592,9 @@ public void shutdown(int timeoutSeconds) { scheduledExecutorService.awaitTermination(executorServiceShutdownTimeoutSecond, TimeUnit.SECONDS); logger.atInfo("executor-service-shutdown-complete") .kv("executor-terminated", executorTerminated) - .kv("scheduled-executor-terminated", scheduledExecutorTerminated).log(); - //Stop the telemetry logger context after each test so we can delete the telemetry log files that are + .kv("scheduled-executor-terminated", scheduledExecutorTerminated) + .log(); + // Stop the telemetry logger context after each test so we can delete the telemetry log files that are // created during the test. TelemetryConfig.getInstance().closeContext(); logger.atInfo("context-shutdown-initiated").log(); @@ -597,11 +616,12 @@ GreengrassService getMain() { /** * Check if all services has reached to terminal state: RUNNING, FINISHED or BROKEN. + * * @return true if all services in terminal states */ public boolean allServicesInTerminalState() { - List servicesToTrack = kernel.findAutoStartableServicesToTrack() - .stream().collect(Collectors.toList()); + List servicesToTrack = + kernel.findAutoStartableServicesToTrack().stream().collect(Collectors.toList()); return servicesToTrack.stream().allMatch(service -> { State state = service.getState(); // service is broken diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelMetricsEmitter.java b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelMetricsEmitter.java index 99505d3894..8a7b1004c5 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelMetricsEmitter.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelMetricsEmitter.java @@ -52,6 +52,7 @@ public void emitMetrics() { /** * Retrieve kernel component state metrics. + * * @return a list of {@link Metric} */ @Override diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/Lifecycle.java b/src/main/java/com/aws/greengrass/lifecyclemanager/Lifecycle.java index d23d70190c..354b008e18 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/Lifecycle.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/Lifecycle.java @@ -52,8 +52,7 @@ import java.util.function.Predicate; import javax.annotation.Nonnull; -@SuppressFBWarnings(value = "JLM_JSR166_UTILCONCURRENT_MONITORENTER", - justification = "We're synchronizing on the desired state list which is fine") +@SuppressFBWarnings(value = "JLM_JSR166_UTILCONCURRENT_MONITORENTER", justification = "We're synchronizing on the desired state list which is fine") public class Lifecycle { public static final String LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC = "bootstrap"; public static final String LIFECYCLE_INSTALL_NAMESPACE_TOPIC = "install"; @@ -80,23 +79,22 @@ public class Lifecycle { private static final long DEFAULT_ERROR_RESET_TIME_IN_SEC = Duration.ofHours(1).getSeconds(); /* - * State generation is a value representing how many times the service has been in the NEW/STARTING state. - * It is to used determine if an action should be taken when that action would be run asynchronously. - * It is not sufficient to check if the state is what you want it to be, because the service may have - * restarted again by the time you are performing this check. Therefore, the generation is used to know - * that the service is still in the same state as you want it to be. + * State generation is a value representing how many times the service has been in the NEW/STARTING state. It is to + * used determine if an action should be taken when that action would be run asynchronously. It is not sufficient to + * check if the state is what you want it to be, because the service may have restarted again by the time you are + * performing this check. Therefore, the generation is used to know that the service is still in the same state as + * you want it to be. * - * For example, if we want to move the service to errored if installed takes too long, then we setup a callback - * to move it to errored if the state is installed. But this won't necessarily be correct because the service - * could have restarted in the mean time, so this old callback should not move it to errored since it's view - * of the world is outdated. If the callback checks both the state and the generation then it is assured to - * properly move the service into errored only when the callback's view of the world is correct. + * For example, if we want to move the service to errored if installed takes too long, then we setup a callback to + * move it to errored if the state is installed. But this won't necessarily be correct because the service could + * have restarted in the mean time, so this old callback should not move it to errored since it's view of the world + * is outdated. If the callback checks both the state and the generation then it is assured to properly move the + * service into errored only when the callback's view of the world is correct. */ @Getter(AccessLevel.PACKAGE) private final AtomicLong stateGeneration = new AtomicLong(); private final GreengrassService greengrassService; - // lastReportedState stores the last reported state (not necessarily processed) private final AtomicReference lastReportedState = new AtomicReference<>(); // stoppingFromStartupError stores whether service is stopping due to an error from startup @@ -130,12 +128,12 @@ public class Lifecycle { static { ALLOWED_STATE_TRANSITION_FOR_REPORTING.put(State.NEW, Collections.singletonList(State.ERRORED)); - ALLOWED_STATE_TRANSITION_FOR_REPORTING - .put(State.STARTING, new HashSet<>(Arrays.asList(State.RUNNING, State.ERRORED, State.FINISHED))); - ALLOWED_STATE_TRANSITION_FOR_REPORTING - .put(State.RUNNING, new HashSet<>(Arrays.asList(State.ERRORED, State.FINISHED))); - ALLOWED_STATE_TRANSITION_FOR_REPORTING - .put(State.STOPPING, new HashSet<>(Arrays.asList(State.ERRORED, State.FINISHED))); + ALLOWED_STATE_TRANSITION_FOR_REPORTING.put(State.STARTING, + new HashSet<>(Arrays.asList(State.RUNNING, State.ERRORED, State.FINISHED))); + ALLOWED_STATE_TRANSITION_FOR_REPORTING.put(State.RUNNING, + new HashSet<>(Arrays.asList(State.ERRORED, State.FINISHED))); + ALLOWED_STATE_TRANSITION_FOR_REPORTING.put(State.STOPPING, + new HashSet<>(Arrays.asList(State.ERRORED, State.FINISHED))); } private final Lock lock = LockFactory.newReentrantLock(this); @@ -146,8 +144,8 @@ public class Lifecycle { * Constructor for lifecycle. * * @param greengrassService service that this is the lifecycle for - * @param logger service's logger - * @param topics config namespace for storing the state topic + * @param logger service's logger + * @param topics config namespace for storing the state topic */ public Lifecycle(GreengrassService greengrassService, Logger logger, Topics topics) { this.greengrassService = greengrassService; @@ -179,13 +177,13 @@ void reportState(State newState, ComponentStatusCode statusCode, Integer exitCod reportState(newState, statusCode, exitCode, null); } - void reportState(State newState, ComponentStatusCode statusCode, Integer exitCode, - String statusReason) { + void reportState(State newState, ComponentStatusCode statusCode, Integer exitCode, String statusReason) { try (LockScope ls = LockScope.lock(lock)) { Collection allowedStatesForReporting = ALLOWED_STATE_TRANSITION_FOR_REPORTING.get(getLastReportedState()); if (allowedStatesForReporting == null || !allowedStatesForReporting.contains(newState)) { - logger.atWarn(INVALID_STATE_ERROR_EVENT).kv(NEW_STATE_METRIC_NAME, newState) + logger.atWarn(INVALID_STATE_ERROR_EVENT) + .kv(NEW_STATE_METRIC_NAME, newState) .log("Invalid reported state"); return; } @@ -244,18 +242,23 @@ private void internalReportState(State newState, ComponentStatusCode statusCode, } if (stateToErroredCount.get(currentState) != null && stateToErroredCount.get(currentState).size() >= MAXIMUM_CONTINUAL_ERROR) { - enqueueStateEvent(StateTransitionEvent.builder().newState(State.BROKEN).statusCode(statusCode) - .statusReason(statusReason).build()); + enqueueStateEvent(StateTransitionEvent.builder() + .newState(State.BROKEN) + .statusCode(statusCode) + .statusReason(statusReason) + .build()); } else { - enqueueStateEvent(StateTransitionEvent.builder().newState(newState).statusCode(statusCode) - .statusReason(statusReason).build()); + enqueueStateEvent(StateTransitionEvent.builder() + .newState(newState) + .statusCode(statusCode) + .statusReason(statusReason) + .build()); } } } /** - * Returns true if either the current or the very last reported state (if any) - * is equal to the provided state. + * Returns true if either the current or the very last reported state (if any) is equal to the provided state. * * @param state state to check against */ @@ -277,7 +280,7 @@ protected ComponentStatusDetails getStatusDetails() { .build(); } - protected Topic getStateTopic() { + protected Topic getStateTopic() { return stateTopic; } @@ -364,33 +367,33 @@ private void startStateTransition() throws InterruptedException { } switch (current) { - case BROKEN: - handleCurrentStateBroken(desiredState, prevState); - break; - case NEW: - handleCurrentStateNew(desiredState); - break; - case INSTALLED: - handleCurrentStateInstalledAsync(desiredState, asyncFinishAction); - break; - case STARTING: - handleCurrentStateStartingAsync(desiredState, asyncFinishAction); - break; - case RUNNING: - handleCurrentStateRunning(desiredState); - break; - case STOPPING: - handleCurrentStateStopping(); - break; - case FINISHED: - handleCurrentStateFinished(desiredState); - break; - case ERRORED: - handleCurrentStateErrored(desiredState, prevState); - break; - default: - logger.atError(INVALID_STATE_ERROR_EVENT).log("Unrecognized current state"); - break; + case BROKEN: + handleCurrentStateBroken(desiredState, prevState); + break; + case NEW: + handleCurrentStateNew(desiredState); + break; + case INSTALLED: + handleCurrentStateInstalledAsync(desiredState, asyncFinishAction); + break; + case STARTING: + handleCurrentStateStartingAsync(desiredState, asyncFinishAction); + break; + case RUNNING: + handleCurrentStateRunning(desiredState); + break; + case STOPPING: + handleCurrentStateStopping(); + break; + case FINISHED: + handleCurrentStateFinished(desiredState); + break; + case ERRORED: + handleCurrentStateErrored(desiredState, prevState); + break; + default: + logger.atError(INVALID_STATE_ERROR_EVENT).log("Unrecognized current state"); + break; } boolean canFinish = false; @@ -428,9 +431,8 @@ private void startStateTransition() throws InterruptedException { } /** - * !!WARNING!! - * This method is package-private for unit testing purposes, but it must NEVER be called - * from anything but the lifecycle thread in this class. + * !!WARNING!! This method is package-private for unit testing purposes, but it must NEVER be called from anything + * but the lifecycle thread in this class. * * @param current current state to transition out of * @param stateTransitionEvent new state to transition into @@ -452,35 +454,35 @@ void setState(State current, StateTransitionEvent stateTransitionEvent) { private void handleCurrentStateBroken(Optional desiredState, State previousState) throws InterruptedException { switch (previousState) { - case STARTING: - case RUNNING: - case ERRORED: // shouldn't happen. Try to stop the service anyways. - logger.atInfo("Stopping service in BROKEN state"); - Future shutdownFuture = greengrassService.getContext().get(ExecutorService.class).submit(() -> { - try { - greengrassService.shutdown(); - } catch (InterruptedException i) { - logger.atWarn("service-shutdown-interrupted").log("Service interrupted while running shutdown"); - } catch (Throwable i) { - logger.atError("service-shutdown-error").setCause(i).log(); - } - }); - + case STARTING: + case RUNNING: + case ERRORED: // shouldn't happen. Try to stop the service anyways. + logger.atInfo("Stopping service in BROKEN state"); + Future shutdownFuture = greengrassService.getContext().get(ExecutorService.class).submit(() -> { try { - Integer timeout = getTimeoutConfigValue( - LIFECYCLE_SHUTDOWN_NAMESPACE_TOPIC, DEFAULT_SHUTDOWN_STAGE_TIMEOUT_IN_SEC); - shutdownFuture.get(timeout, TimeUnit.SECONDS); - } catch (ExecutionException e) { - logger.atError("service-shutdown-error").setCause(e).log(); - } catch (TimeoutException te) { - logger.atWarn("service-shutdown-timeout").log(); - shutdownFuture.cancel(true); - } finally { - stopBackingTask(); + greengrassService.shutdown(); + } catch (InterruptedException i) { + logger.atWarn("service-shutdown-interrupted").log("Service interrupted while running shutdown"); + } catch (Throwable i) { + logger.atError("service-shutdown-error").setCause(i).log(); } - break; - default: - // do nothing + }); + + try { + Integer timeout = getTimeoutConfigValue(LIFECYCLE_SHUTDOWN_NAMESPACE_TOPIC, + DEFAULT_SHUTDOWN_STAGE_TIMEOUT_IN_SEC); + shutdownFuture.get(timeout, TimeUnit.SECONDS); + } catch (ExecutionException e) { + logger.atError("service-shutdown-error").setCause(e).log(); + } catch (TimeoutException te) { + logger.atWarn("service-shutdown-timeout").log(); + shutdownFuture.cancel(true); + } finally { + stopBackingTask(); + } + break; + default: + // do nothing } if (!desiredState.isPresent()) { return; @@ -517,8 +519,8 @@ private void handleCurrentStateNew(Optional desiredState) throws Interrup } }, LIFECYCLE_INSTALL_NAMESPACE_TOPIC); - Integer installTimeOut = getTimeoutConfigValue( - LIFECYCLE_INSTALL_NAMESPACE_TOPIC, DEFAULT_INSTALL_STAGE_TIMEOUT_IN_SEC); + Integer installTimeOut = + getTimeoutConfigValue(LIFECYCLE_INSTALL_NAMESPACE_TOPIC, DEFAULT_INSTALL_STAGE_TIMEOUT_IN_SEC); try { backingTask.get().get(installTimeOut, TimeUnit.SECONDS); @@ -534,9 +536,8 @@ private void handleCurrentStateNew(Optional desiredState) throws Interrup } } - private void handleCurrentStateInstalledAsync(Optional desiredState, - AtomicReference> asyncFinishAction) { + AtomicReference> asyncFinishAction) { if (!desiredState.isPresent()) { return; } @@ -563,7 +564,7 @@ private void handleCurrentStateInstalledAsync(Optional desiredState, } private void handleCurrentStateStartingAsync(Optional desiredState, - AtomicReference> asyncFinishAction) { + AtomicReference> asyncFinishAction) { if (!desiredState.isPresent()) { return; } @@ -580,17 +581,18 @@ private void handleCurrentStateStartingAsync(Optional desiredState, } } - @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.AvoidGettingFutureWithoutTimeout"}) + @SuppressWarnings({ + "PMD.AvoidCatchingThrowable", "PMD.AvoidGettingFutureWithoutTimeout" + }) private void handleStateTransitionStartingToRunningAsync(AtomicReference> asyncFinishAction) { long currentStateGeneration = stateGeneration.incrementAndGet(); - Integer timeout = getTimeoutConfigValue( - LIFECYCLE_STARTUP_NAMESPACE_TOPIC, DEFAULT_STARTUP_STAGE_TIMEOUT_IN_SEC); - Future schedule = - greengrassService.getContext().get(ScheduledExecutorService.class).schedule(() -> { - if (getState().equals(State.STARTING) && currentStateGeneration == getStateGeneration().get()) { - greengrassService.serviceErrored(ComponentStatusCode.STARTUP_TIMEOUT, "startup timeout"); - } - }, timeout, TimeUnit.SECONDS); + Integer timeout = + getTimeoutConfigValue(LIFECYCLE_STARTUP_NAMESPACE_TOPIC, DEFAULT_STARTUP_STAGE_TIMEOUT_IN_SEC); + Future schedule = greengrassService.getContext().get(ScheduledExecutorService.class).schedule(() -> { + if (getState().equals(State.STARTING) && currentStateGeneration == getStateGeneration().get()) { + greengrassService.serviceErrored(ComponentStatusCode.STARTUP_TIMEOUT, "startup timeout"); + } + }, timeout, TimeUnit.SECONDS); replaceBackingTask(() -> { try { @@ -625,7 +627,6 @@ private void handleStateTransitionStartingToRunningAsync(AtomicReference desiredState) { if (!desiredState.isPresent()) { return; @@ -649,8 +650,8 @@ private void handleCurrentStateStopping() throws InterruptedException { }); try { - Integer timeout = getTimeoutConfigValue( - LIFECYCLE_SHUTDOWN_NAMESPACE_TOPIC, DEFAULT_SHUTDOWN_STAGE_TIMEOUT_IN_SEC); + Integer timeout = + getTimeoutConfigValue(LIFECYCLE_SHUTDOWN_NAMESPACE_TOPIC, DEFAULT_SHUTDOWN_STAGE_TIMEOUT_IN_SEC); shutdownFuture.get(timeout, TimeUnit.SECONDS); stoppingFromStartupError.set(false); if (!State.ERRORED.equals(lastReportedState.get())) { @@ -691,35 +692,33 @@ private void handleCurrentStateErrored(Optional desiredState, State prevS } switch (prevState) { - // For both starting and running, make sure we stop first before retrying - case STARTING: - stoppingFromStartupError.set(true); - internalReportState(State.STOPPING); - break; - case RUNNING: - internalReportState(State.STOPPING); - break; - case NEW: // error in installing. - internalReportState(State.NEW); - break; - case STOPPING: - // not handled; - // reset stoppingFromStartupError since the last stopping lifecycle errored - stoppingFromStartupError.set(false); - desiredState = peekOrRemoveFirstDesiredState(State.FINISHED); - serviceTerminatedMoveToDesiredState(desiredState.orElse(State.FINISHED)); - break; - default: - logger.atError(INVALID_STATE_ERROR_EVENT).kv("previousState", prevState) - .log("Unexpected previous state"); - internalReportState(State.FINISHED); - break; + // For both starting and running, make sure we stop first before retrying + case STARTING: + stoppingFromStartupError.set(true); + internalReportState(State.STOPPING); + break; + case RUNNING: + internalReportState(State.STOPPING); + break; + case NEW: // error in installing. + internalReportState(State.NEW); + break; + case STOPPING: + // not handled; + // reset stoppingFromStartupError since the last stopping lifecycle errored + stoppingFromStartupError.set(false); + desiredState = peekOrRemoveFirstDesiredState(State.FINISHED); + serviceTerminatedMoveToDesiredState(desiredState.orElse(State.FINISHED)); + break; + default: + logger.atError(INVALID_STATE_ERROR_EVENT).kv("previousState", prevState).log("Unexpected previous state"); + internalReportState(State.FINISHED); + break; } } /** - * Given the service is terminated, move to desired state. - * Only use in service lifecycle thread. + * Given the service is terminated, move to desired state. Only use in service lifecycle thread. * * @param desiredState the desiredState to go, not null */ @@ -730,20 +729,19 @@ private void serviceTerminatedMoveToDesiredState(@Nonnull State desiredState) { return; } switch (desiredState) { - case NEW: - internalReportState(State.NEW); - break; - case INSTALLED: - case RUNNING: - internalReportState(State.INSTALLED); - break; - case FINISHED: - internalReportState(State.FINISHED); - break; - default: - // not allowed to set desired state to STOPPING, ERRORED, BROKEN - logger.atError(INVALID_STATE_ERROR_EVENT).kv("desiredState", desiredState) - .log("Unexpected desired state"); + case NEW: + internalReportState(State.NEW); + break; + case INSTALLED: + case RUNNING: + internalReportState(State.INSTALLED); + break; + case FINISHED: + internalReportState(State.FINISHED); + break; + default: + // not allowed to set desired state to STOPPING, ERRORED, BROKEN + logger.atError(INVALID_STATE_ERROR_EVENT).kv("desiredState", desiredState).log("Unexpected desired state"); } } @@ -801,9 +799,9 @@ void initLifecycleThread() { } } } finally { - Thread.currentThread() - .setName(threadName); // reset thread name so that if the thread is recycled it - // will not falsely claim to be a lifecycle thread. + // reset thread name so that if the thread is recycled + // it will not falsely claim to be a lifecycle thread. + Thread.currentThread().setName(threadName); } }); } @@ -811,6 +809,7 @@ void initLifecycleThread() { /** * Return the lifecycle thread future. + * * @return the lifecycle thread future. */ public Future getLifecycleThread() { @@ -829,8 +828,7 @@ void setClosed(boolean b) { final void requestStart() { // It's ok to start service again if the lifecycle thread is in the middle of closing if (isClosed.compareAndSet(true, false)) { - logger.atWarn("service-shutdown-in-progress") - .log("Requesting service to start while it is closing"); + logger.atWarn("service-shutdown-in-progress").log("Requesting service to start while it is closing"); } try (LockScope ls = LockScope.lock(desiredStateLock)) { if (desiredStateList.isEmpty() || desiredStateList.equals(Collections.singletonList(State.FINISHED))) { @@ -854,8 +852,7 @@ final void requestStart() { final void requestReinstall() { // It's ok to reinstall service again if the lifecycle thread is in the middle of closing if (isClosed.compareAndSet(true, false)) { - logger.atWarn("service-shutdown-in-progress") - .log("Requesting service to reinstall while it is closing"); + logger.atWarn("service-shutdown-in-progress").log("Requesting service to reinstall while it is closing"); } try (LockScope ls = LockScope.lock(desiredStateLock)) { setDesiredState(State.NEW, State.RUNNING); @@ -910,13 +907,15 @@ final void requestStop() { } private Integer getTimeoutConfigValue(String nameSpace, Integer defaultValue) { - return Coerce.toInt(greengrassService.getConfig().findOrDefault(defaultValue, - GreengrassService.SERVICE_LIFECYCLE_NAMESPACE_TOPIC, nameSpace, TIMEOUT_NAMESPACE_TOPIC)); + return Coerce.toInt(greengrassService.getConfig() + .findOrDefault(defaultValue, GreengrassService.SERVICE_LIFECYCLE_NAMESPACE_TOPIC, nameSpace, + TIMEOUT_NAMESPACE_TOPIC)); } private int getErrorResetTime() { - return Coerce.toInt(greengrassService.getConfig().findOrDefault(DEFAULT_ERROR_RESET_TIME_IN_SEC, - GreengrassService.SERVICE_LIFECYCLE_NAMESPACE_TOPIC, ERROR_RESET_TIME_TOPIC)); + return Coerce.toInt(greengrassService.getConfig() + .findOrDefault(DEFAULT_ERROR_RESET_TIME_IN_SEC, GreengrassService.SERVICE_LIFECYCLE_NAMESPACE_TOPIC, + ERROR_RESET_TIME_TOPIC)); } static class StateEvent { diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/LogManagerHelper.java b/src/main/java/com/aws/greengrass/lifecyclemanager/LogManagerHelper.java index c332a4bdfa..6ce9c26392 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/LogManagerHelper.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/LogManagerHelper.java @@ -35,7 +35,7 @@ public static Logger getComponentLogger(GreengrassService service) { * Get the logger for a particular component. The logs will be added to the log file name provided in the method * signature if the logs are configured to be written to the disk. * - * @param name The name of the component + * @param name The name of the component * @param fileName The name of the log file. * @return a logger with configuration to log to a log file with the same name. */ diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/Periodicity.java b/src/main/java/com/aws/greengrass/lifecyclemanager/Periodicity.java index e81f17e21b..31dd216445 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/Periodicity.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/Periodicity.java @@ -31,8 +31,7 @@ */ public final class Periodicity { /** - * Just using raw milliseconds: finer precision isn't realistic at this - * point. + * Just using raw milliseconds: finer precision isn't realistic at this point. */ private final Topic interval; private final Topic phase; @@ -88,8 +87,11 @@ public static Periodicity of(GreengrassService s) { } return ret; } catch (NumberFormatException t) { - s.logger.atError("service-invalid-config").setCause(t).kv("parameter", Utils.deepToString(n)) - .kv(GreengrassService.SERVICE_NAME_KEY, s.getName()).log("Unparseable periodic parameter"); + s.logger.atError("service-invalid-config") + .setCause(t) + .kv("parameter", Utils.deepToString(n)) + .kv(GreengrassService.SERVICE_NAME_KEY, s.getName()) + .log("Unparseable periodic parameter"); s.serviceErrored(t); } return null; @@ -100,41 +102,41 @@ static long parseInterval(String v) { CharBuffer p = CharBuffer.wrap(v); long n = parseLong(p); String u = p.toString().trim().toLowerCase(); - // TimeUnit tu; Bugger: doesn't support weeks + // TimeUnit tu; Bugger: doesn't support weeks long tu; switch (u) { - case "ms": - case "millis": - case "milliseconds": - tu = 1; - break; - case "": - case "s": - case "seconds": - case "second": - tu = 1000; - break; - default: - case "m": - case "minutes": - case "minute": - tu = 1000 * 60; - break; - case "h": - case "hours": - case "hour": - tu = 1000 * 60 * 60; - break; - case "d": - case "days": - case "day": - tu = 1000 * 60 * 60 * 24; - break; - case "w": - case "weeks": - case "week": - tu = 1000 * 60 * 60 * 24 * 7; - break; + case "ms": + case "millis": + case "milliseconds": + tu = 1; + break; + case "": + case "s": + case "seconds": + case "second": + tu = 1000; + break; + default: + case "m": + case "minutes": + case "minute": + tu = 1000 * 60; + break; + case "h": + case "hours": + case "hour": + tu = 1000 * 60 * 60; + break; + case "d": + case "days": + case "day": + tu = 1000 * 60 * 60 * 24; + break; + case "w": + case "weeks": + case "week": + tu = 1000 * 60 * 60 * 24 * 7; + break; } return n * tu; } @@ -148,7 +150,7 @@ private void start(ScheduledExecutorService ses, Runnable r) { long now = System.currentTimeMillis(); long timeIntervalMillis = parseInterval(Coerce.toString(interval)); long phase = parseInterval(Coerce.toString(this.phase)); - float fuzzFactor; // The fraction of the interval to "fuzz" the start time + float fuzzFactor; // The fraction of the interval to "fuzz" the start time try { fuzzFactor = Float.parseFloat(Coerce.toString(fuzz)); if (fuzzFactor < 0) { @@ -158,8 +160,11 @@ private void start(ScheduledExecutorService ses, Runnable r) { fuzzFactor = 1; } } catch (NumberFormatException t) { - service.logger.atWarn().addKeyValue("factor", Coerce.toString(fuzz)).setCause(t) - .addKeyValue("default", DEFAULT_FUZZ_FACTOR).log("Error parsing fuzz factor. Using default"); + service.logger.atWarn() + .addKeyValue("factor", Coerce.toString(fuzz)) + .setCause(t) + .addKeyValue("default", DEFAULT_FUZZ_FACTOR) + .log("Error parsing fuzz factor. Using default"); fuzzFactor = DEFAULT_FUZZ_FACTOR; } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/RunWithPathOwnershipHandler.java b/src/main/java/com/aws/greengrass/lifecyclemanager/RunWithPathOwnershipHandler.java index 7bffd43439..247e7b64c5 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/RunWithPathOwnershipHandler.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/RunWithPathOwnershipHandler.java @@ -54,20 +54,18 @@ public RunWithPathOwnershipHandler(NucleusPaths paths, Platform platform) { * Update the owner of the artifacts and work path in the component on the local filesystem. The user and group of * from the RunWith parameter are used. * - * @param id the component to update. + * @param id the component to update. * @param runWith the user/group that should own the files. * @throws IOException if an error occurs while updating. This can occur if the user running the kernel does not - * have the correct permissions or capabilities to change file ownership to another user. + * have the correct permissions or capabilities to change file ownership to another user. */ public void updateOwner(ComponentIdentifier id, RunWith runWith) throws IOException { Path artifacts = nucleusPaths.artifactPath(id); Path unarchived = nucleusPaths.unarchiveArtifactPath(id); Path workPath = nucleusPaths.workPath(id.getName()); - FileSystemPermission permission = FileSystemPermission.builder() - .ownerUser(runWith.getUser()) - .ownerGroup(runWith.getGroup()) - .build(); + FileSystemPermission permission = + FileSystemPermission.builder().ownerUser(runWith.getUser()).ownerGroup(runWith.getGroup()).build(); // change ownership of files within the artifact dirs, but don't change the artifact dir itself as that would // make it writable to the user @@ -79,7 +77,7 @@ public void updateOwner(ComponentIdentifier id, RunWith runWith) throws IOExcept } @SuppressWarnings("PMD.ForLoopCanBeForeach") - void setPermissions(Path p, FileSystemPermission permission, boolean applyToRoot) throws IOException { + void setPermissions(Path p, FileSystemPermission permission, boolean applyToRoot) throws IOException { if (Files.notExists(p)) { return; } @@ -87,7 +85,7 @@ void setPermissions(Path p, FileSystemPermission permission, boolean applyToRoo platform.setPermissions(permission, p, Recurse, SetOwner); } else { try (Stream files = Files.list(p)) { - for (Iterator it = files.iterator(); it.hasNext(); ) { + for (Iterator it = files.iterator(); it.hasNext();) { platform.setPermissions(permission, it.next(), Recurse, SetOwner); } } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/ShellRunner.java b/src/main/java/com/aws/greengrass/lifecyclemanager/ShellRunner.java index 55c71549ee..a34c21235e 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/ShellRunner.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/ShellRunner.java @@ -48,24 +48,21 @@ public Exec setup(String note, String command, GreengrassService onBehalfOf) thr if (rootCaPath == null) { rootCaPath = ""; } - Exec exec = Platform.getInstance().createNewProcessRunner() - .withShell(command) - .withOut(s -> { - String ss = s.toString().trim(); - logger.atInfo().setEventType("stdout").kv(SCRIPT_NAME_KEY, note).log(ss); - }) - .withErr(s -> { - String ss = s.toString().trim(); - logger.atWarn().setEventType("stderr").kv(SCRIPT_NAME_KEY, note).log(ss); - }) + Exec exec = Platform.getInstance().createNewProcessRunner().withShell(command).withOut(s -> { + String ss = s.toString().trim(); + logger.atInfo().setEventType("stdout").kv(SCRIPT_NAME_KEY, note).log(ss); + }).withErr(s -> { + String ss = s.toString().trim(); + logger.atWarn().setEventType("stderr").kv(SCRIPT_NAME_KEY, note).log(ss); + }) .setenv("SVCUID", - String.valueOf(onBehalfOf.getPrivateConfig().findLeafChild(SERVICE_UNIQUE_ID_KEY) - .getOnce())) + String.valueOf( + onBehalfOf.getPrivateConfig().findLeafChild(SERVICE_UNIQUE_ID_KEY).getOnce())) // Tes needs to inject identity separately as required by AWS SDK's which expect this env // variable to be present for sending credential request to a server .setenv(TES_AUTH_HEADER, - String.valueOf(onBehalfOf.getPrivateConfig().findLeafChild(SERVICE_UNIQUE_ID_KEY) - .getOnce())) + String.valueOf( + onBehalfOf.getPrivateConfig().findLeafChild(SERVICE_UNIQUE_ID_KEY).getOnce())) .setenv(GG_ROOT_CA_PATH, rootCaPath) .cd(cwd.toFile().getAbsoluteFile()) .logger(logger); @@ -102,15 +99,16 @@ public boolean successful(Exec e, String note, IntConsumer background, Greengras try { if (background == null) { if (!e.successful(true)) { - logger.atWarn("shell-runner-error").kv(SCRIPT_NAME_KEY, note) - .kv("command", e.toString()).log(); + logger.atWarn("shell-runner-error").kv(SCRIPT_NAME_KEY, note).kv("command", e.toString()).log(); return false; } } else { e.background(background); } } catch (IOException ex) { - logger.atError("shell-runner-error").kv(SCRIPT_NAME_KEY, note).kv("command", e.toString()) + logger.atError("shell-runner-error") + .kv(SCRIPT_NAME_KEY, note) + .kv("command", e.toString()) .log("Error while running component lifecycle script", ex); return false; } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/UnloadableService.java b/src/main/java/com/aws/greengrass/lifecyclemanager/UnloadableService.java index dd5192e179..3ba5c965a7 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/UnloadableService.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/UnloadableService.java @@ -70,8 +70,10 @@ public boolean isBootstrapRequired(Map newServiceConfig) { return true; } try { - Path pluginJar = config.getContext().get(NucleusPaths.class).artifactPath(new ComponentIdentifier(getName(), - new Semver(newVersion))).resolve(getName() + JAR_FILE_EXTENSION); + Path pluginJar = config.getContext() + .get(NucleusPaths.class) + .artifactPath(new ComponentIdentifier(getName(), new Semver(newVersion))) + .resolve(getName() + JAR_FILE_EXTENSION); if (!pluginJar.toFile().exists() || !pluginJar.toFile().isFile()) { logger.atInfo().kv("pluginJar", pluginJar).log("Bootstrap is not required: plugin JAR not found"); @@ -90,8 +92,8 @@ public boolean isBootstrapRequired(Map newServiceConfig) { } /** - * Moves the service to finished state and shuts down lifecycle thread. - * Since the service has loading exceptions, don't expect depending services to exit before itself. + * Moves the service to finished state and shuts down lifecycle thread. Since the service has loading exceptions, + * don't expect depending services to exit before itself. * * @return future completes when the lifecycle thread shuts down. */ diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/UpdateSystemPolicyService.java b/src/main/java/com/aws/greengrass/lifecyclemanager/UpdateSystemPolicyService.java index 1c2234fee6..814bb52990 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/UpdateSystemPolicyService.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/UpdateSystemPolicyService.java @@ -34,16 +34,16 @@ import javax.inject.Singleton; /** - * Handles requests to update the system's configuration during disruptable times. - * (or anything else that's disruptive and shouldn't be done until the system - * is in a "disruptable" state). + * Handles requests to update the system's configuration during disruptable times. (or anything else that's disruptive + * and shouldn't be done until the system is in a "disruptable" state). * - *

It maintains a list of actions that will be executed when the - * system is next "disruptable". This is typically code that is going to install an update. + *

+ * It maintains a list of actions that will be executed when the system is next "disruptable". This is typically code + * that is going to install an update. * - *

If the update service is periodic, update actions will only be processed at that time. - * Otherwise, it the update will be processed immediately, assuming that all disruptability - * checks pass. + *

+ * If the update service is periodic, update actions will only be processed at that time. Otherwise, it the update will + * be processed immediately, assuming that all disruptability checks pass. */ @ImplementsService(name = "UpdateSystemPolicyService", autostart = true) @Singleton @@ -74,9 +74,9 @@ public UpdateSystemPolicyService(Topics topics) { /** * Add an update action to be performed when the system is in a "disruptable" state. * - * @param tag used both as a printable description and a de-duplication key. eg. If the action is - * installing a new config file, the tag should probably be the URL of the config. If a key is - * duplicated by subsequent actions, they are suppressed. + * @param tag used both as a printable description and a de-duplication key. eg. If the action is installing a new + * config file, the tag should probably be the URL of the config. If a key is duplicated by subsequent + * actions, they are suppressed. * @param updateAction Update action to be performed. */ public void addUpdateAction(String tag, UpdateAction updateAction) { @@ -102,13 +102,16 @@ protected void runUpdateActions(String deploymentId) { todo.getValue().getAction().run(); logger.atDebug().setEventType("service-update-action").addKeyValue("action", todo.getKey()).log(); } catch (Throwable t) { - logger.atError().setEventType("service-update-action-error").addKeyValue("action", todo.getKey()) - .setCause(t).log(); + logger.atError() + .setEventType("service-update-action-error") + .addKeyValue("action", todo.getKey()) + .setCause(t) + .log(); } } pendingActions.clear(); - lifecycleIPCAgent.sendPostComponentUpdateEvent( - new PostComponentUpdateEvent().withDeploymentId(deploymentId)); + lifecycleIPCAgent + .sendPostComponentUpdateEvent(new PostComponentUpdateEvent().withDeploymentId(deploymentId)); actionInProgress.set(null); } } @@ -116,9 +119,9 @@ protected void runUpdateActions(String deploymentId) { /** * Discard a pending action if update actions are not already running. * - * @param tag tag to identify an update action - * @return true if all update actions are pending and requested action could be discarded, - * false if update actions were already in progress + * @param tag tag to identify an update action + * @return true if all update actions are pending and requested action could be discarded, false if update actions + * were already in progress */ public boolean discardPendingUpdateAction(String tag) { if (tag.equals(actionInProgress.get())) { @@ -134,7 +137,9 @@ public boolean discardPendingUpdateAction(String tag) { return true; } - @SuppressWarnings({"SleepWhileInLoop"}) + @SuppressWarnings({ + "SleepWhileInLoop" + }) @Override protected void startup() throws InterruptedException { // startup() is invoked on it's own thread @@ -147,7 +152,9 @@ protected void startup() throws InterruptedException { continue; } } - logger.atDebug().setEventType("service-update-pending").addKeyValue("numOfUpdates", pendingActions.size()) + logger.atDebug() + .setEventType("service-update-pending") + .addKeyValue("numOfUpdates", pendingActions.size()) .log(); boolean ggcRestarting = false; @@ -179,16 +186,15 @@ protected void startup() throws InterruptedException { logger.atInfo().setEventType("service-update-finish").log(); }).get(); } catch (ExecutionException e) { - logger.atError().setEventType("service-update-error") - .log("Run update actions errored", e); + logger.atError().setEventType("service-update-error").log("Run update actions errored", e); } } } } /* - If multiple updates are present, get the max time-out. As of now, kernel does not process multiple - deployments at the same time and pendingActions will have only one action to run at a time. + * If multiple updates are present, get the max time-out. As of now, kernel does not process multiple deployments at + * the same time and pendingActions will have only one action to run at a time. */ private long getMaxTimeoutInMillis() { Optional maxTimeoutInSec = @@ -197,8 +203,7 @@ private long getMaxTimeoutInMillis() { } private long getTimeToReCheck(long timeout, String deploymentId, - List> deferRequestFutures) - throws InterruptedException { + List> deferRequestFutures) throws InterruptedException { final long currentTimeMillis = clock.millis(); long maxTimeToReCheck = currentTimeMillis; while ((clock.millis() - currentTimeMillis) < timeout && !deferRequestFutures.isEmpty()) { @@ -212,9 +217,9 @@ private long getTimeToReCheck(long timeout, String deploymentId, long timeToRecheck = currentTimeMillis + deferRequest.getRecheckAfterMs(); if (timeToRecheck > maxTimeToReCheck) { maxTimeToReCheck = timeToRecheck; - logger.atInfo().setEventType("service-update-deferred") - .log("deferred for {} millis with message {}", - deferRequest.getRecheckAfterMs(), + logger.atInfo() + .setEventType("service-update-deferred") + .log("deferred for {} millis with message {}", deferRequest.getRecheckAfterMs(), deferRequest.getMessage()); } } else { diff --git a/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqtt5Client.java b/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqtt5Client.java index 28288edef9..e57559d503 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqtt5Client.java +++ b/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqtt5Client.java @@ -78,7 +78,8 @@ class AwsIotMqtt5Client implements IndividualMqttClient { private Mqtt5Client client = null; private static final Random RANDOM = new Random(); - private final Logger logger = LogManager.getLogger(AwsIotMqtt5Client.class).createChild() + private final Logger logger = LogManager.getLogger(AwsIotMqtt5Client.class) + .createChild() .dfltKv(MqttClient.CLIENT_ID_KEY, (Supplier) this::getClientId); private final ExecutorService executorService; @@ -114,87 +115,89 @@ class AwsIotMqtt5Client implements IndividualMqttClient { @Getter(AccessLevel.PACKAGE) private final Mqtt5ClientOptions.LifecycleEvents connectionEventCallback = new Mqtt5ClientOptions.LifecycleEvents() { - @Override - public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { - logger.atDebug().log("Attempting to connect to AWS IoT Core"); - } - - @Override - public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { - boolean sessionPresent = onConnectionSuccessReturn.getConnAckPacket().getSessionPresent(); + @Override + public void onAttemptingConnect(Mqtt5Client client, + OnAttemptingConnectReturn onAttemptingConnectReturn) { + logger.atDebug().log("Attempting to connect to AWS IoT Core"); + } - if (hasConnectedOnce.compareAndSet(false, true)) { - logger.atInfo().kv("sessionPresent", sessionPresent).log("Successfully connected to AWS IoT Core"); - callbackEventManager.runOnInitialConnect(sessionPresent); - } else { - logger.atInfo().kv("sessionPresent", sessionPresent).log("Connection resumed"); - callbackEventManager.runOnConnectionResumed(sessionPresent); - } - connectFuture.complete(client); - resubscribe(sessionPresent); - } + @Override + public void onConnectionSuccess(Mqtt5Client client, + OnConnectionSuccessReturn onConnectionSuccessReturn) { + boolean sessionPresent = onConnectionSuccessReturn.getConnAckPacket().getSessionPresent(); + + if (hasConnectedOnce.compareAndSet(false, true)) { + logger.atInfo() + .kv("sessionPresent", sessionPresent) + .log("Successfully connected to AWS IoT Core"); + callbackEventManager.runOnInitialConnect(sessionPresent); + } else { + logger.atInfo().kv("sessionPresent", sessionPresent).log("Connection resumed"); + callbackEventManager.runOnConnectionResumed(sessionPresent); + } + connectFuture.complete(client); + resubscribe(sessionPresent); + } - @Override - @SuppressWarnings("PMD.DoNotLogWithoutLogging") - public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { - int errorCode = onConnectionFailureReturn.getErrorCode(); - ConnAckPacket packet = onConnectionFailureReturn.getConnAckPacket(); - LogEventBuilder l = logger.atError().kv("error", CRT.awsErrorString(errorCode)); - if (packet != null) { - l.kv("reasonCode", packet.getReasonCode().name()) - .kv("reason", packet.getReasonString()); - } - l.log("Failed to connect to AWS IoT Core"); - } + @Override + @SuppressWarnings("PMD.DoNotLogWithoutLogging") + public void onConnectionFailure(Mqtt5Client client, + OnConnectionFailureReturn onConnectionFailureReturn) { + int errorCode = onConnectionFailureReturn.getErrorCode(); + ConnAckPacket packet = onConnectionFailureReturn.getConnAckPacket(); + LogEventBuilder l = logger.atError().kv("error", CRT.awsErrorString(errorCode)); + if (packet != null) { + l.kv("reasonCode", packet.getReasonCode().name()).kv("reason", packet.getReasonString()); + } + l.log("Failed to connect to AWS IoT Core"); + } - @Override - @SuppressWarnings("PMD.DoNotLogWithoutLogging") - public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { - int errorCode = onDisconnectionReturn.getErrorCode(); - DisconnectPacket packet = onDisconnectionReturn.getDisconnectPacket(); - // Error AWS_ERROR_MQTT5_USER_REQUESTED_STOP means that the disconnection was intentional. - // We do not need to run callbacks when we purposely interrupt a connection. - if ("AWS_ERROR_MQTT5_USER_REQUESTED_STOP".equals(CRT.awsErrorName(errorCode)) - || packet != null && packet.getReasonCode() - .equals(DisconnectPacket.DisconnectReasonCode.NORMAL_DISCONNECTION)) { - logger.atInfo().log("Connection purposefully interrupted"); - return; - } else { - LogEventBuilder l = logger.atWarn().kv("error", CRT.awsErrorString(errorCode)); - if (packet != null) { - l.kv("reasonCode", packet.getReasonCode().name()) - .kv("reason", packet.getReasonString()); + @Override + @SuppressWarnings("PMD.DoNotLogWithoutLogging") + public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { + int errorCode = onDisconnectionReturn.getErrorCode(); + DisconnectPacket packet = onDisconnectionReturn.getDisconnectPacket(); + // Error AWS_ERROR_MQTT5_USER_REQUESTED_STOP means that the disconnection was intentional. + // We do not need to run callbacks when we purposely interrupt a connection. + if ("AWS_ERROR_MQTT5_USER_REQUESTED_STOP".equals(CRT.awsErrorName(errorCode)) + || packet != null && packet.getReasonCode() + .equals(DisconnectPacket.DisconnectReasonCode.NORMAL_DISCONNECTION)) { + logger.atInfo().log("Connection purposefully interrupted"); + return; + } else { + LogEventBuilder l = logger.atWarn().kv("error", CRT.awsErrorString(errorCode)); + if (packet != null) { + l.kv("reasonCode", packet.getReasonCode().name()).kv("reason", packet.getReasonString()); + } + l.log("Connection interrupted"); + } + if (resubscribeFuture != null && !resubscribeFuture.isDone()) { + resubscribeFuture.cancel(true); + } + // To run the callbacks shared by the different IndividualMqttClient. + callbackEventManager.runOnConnectionInterrupted(errorCode); } - l.log("Connection interrupted"); - } - if (resubscribeFuture != null && !resubscribeFuture.isDone()) { - resubscribeFuture.cancel(true); - } - // To run the callbacks shared by the different IndividualMqttClient. - callbackEventManager.runOnConnectionInterrupted(errorCode); - } - @Override - public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { - client.close(); - CompletableFuture f = stopFuture.get(); - if (f != null) { - f.complete(null); - } - } - }; + @Override + public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { + client.close(); + CompletableFuture f = stopFuture.get(); + if (f != null) { + f.complete(null); + } + } + }; AwsIotMqtt5Client(Provider builderProvider, - Function> messageHandler, String clientId, int clientIdNum, - Topics mqttTopics, CallbackEventManager callbackEventManager, ExecutorService executorService, - ScheduledExecutorService ses) { + Function> messageHandler, String clientId, int clientIdNum, + Topics mqttTopics, CallbackEventManager callbackEventManager, ExecutorService executorService, + ScheduledExecutorService ses) { this.clientId = clientId; this.clientIdNum = clientIdNum; this.mqttTopics = mqttTopics; Consumer handler = messageHandler.apply(this); - this.messageHandler = - (client, publishReturn) -> handler.accept(Publish.fromCrtPublishPacket( - publishReturn.getPublishPacket())); + this.messageHandler = (client, publishReturn) -> handler + .accept(Publish.fromCrtPublishPacket(publishReturn.getPublishPacket())); this.callbackEventManager = callbackEventManager; this.executorService = executorService; this.ses = ses; @@ -216,15 +219,15 @@ void disableRateLimiting() { public long getThrottlingWaitTimeMicros() { // Return the worst possible wait time. // Time to wait is independent of how many permits we need because future transactions - // will pay this current transaction's cost. See the JavaDocs for RateLimiter for more info. + // will pay this current transaction's cost. See the JavaDocs for RateLimiter for more info. return Math.max(bandwidthLimiter.microTimeToNextPermit(), transactionLimiter.microTimeToNextPermit()); } @Override public boolean canAddNewSubscription() { try (LockScope ls = LockScope.lock(lock)) { - return (subscriptionTopics.size() + inprogressSubscriptions.get()) - < MqttClient.MAX_SUBSCRIPTIONS_PER_CONNECTION; + return (subscriptionTopics.size() + + inprogressSubscriptions.get()) < MqttClient.MAX_SUBSCRIPTIONS_PER_CONNECTION; } } @@ -268,8 +271,9 @@ protected CompletableFuture disconnect() { logger.atDebug().log("Disconnecting from AWS IoT Core"); CompletableFuture f = new CompletableFuture<>(); stopFuture.set(f); - client.stop(new DisconnectPacket.DisconnectPacketBuilder().withReasonCode( - DisconnectPacket.DisconnectReasonCode.NORMAL_DISCONNECTION).build()); + client.stop(new DisconnectPacket.DisconnectPacketBuilder() + .withReasonCode(DisconnectPacket.DisconnectReasonCode.NORMAL_DISCONNECTION) + .build()); connectionCleanup(); return f; } @@ -281,16 +285,20 @@ protected CompletableFuture disconnect() { public CompletableFuture subscribe(Subscribe subscribe) { try (LockScope ls1 = LockScope.lock(lock)) { return connect().thenCompose((client) -> { - logger.atDebug().kv(TOPIC_KEY, subscribe.getTopic()).kv(QOS_KEY, subscribe.getQos().name()) + logger.atDebug() + .kv(TOPIC_KEY, subscribe.getTopic()) + .kv(QOS_KEY, subscribe.getQos().name()) .log("Subscribing to topic"); inprogressSubscriptions.incrementAndGet(); - return client.subscribe(subscribe.toCrtSubscribePacket()).thenApply(SubscribeResponse::fromCrtSubAck) + return client.subscribe(subscribe.toCrtSubscribePacket()) + .thenApply(SubscribeResponse::fromCrtSubAck) .whenComplete((r, error) -> { try (LockScope ls2 = LockScope.lock(lock)) { // reason codes less than or equal to 2 are positive responses if (error == null && r != null && r.isSuccessful()) { subscriptionTopics.add(subscribe); - logger.atDebug().kv(TOPIC_KEY, subscribe.getTopic()) + logger.atDebug() + .kv(TOPIC_KEY, subscribe.getTopic()) .kv(QOS_KEY, subscribe.getQos().name()) .log("Successfully subscribed to topic"); } else { @@ -327,28 +335,28 @@ private void internalConnect() { long minConnectTimeSeconds = Coerce.toLong(mqttTopics.find("minimumConnectedTimeBeforeRetryResetSeconds")); - builder.withLifeCycleEvents(this.connectionEventCallback).withPublishEvents(this.messageHandler) + builder.withLifeCycleEvents(this.connectionEventCallback) + .withPublishEvents(this.messageHandler) // reset the session on initial connect, // but when we reconnect purposefully, // attempt to resume the session rather than clear it again - .withSessionBehavior( - hasConnectedOnce.get() ? Mqtt5ClientOptions.ClientSessionBehavior.REJOIN_ALWAYS - : Mqtt5ClientOptions.ClientSessionBehavior.REJOIN_POST_SUCCESS) + .withSessionBehavior(hasConnectedOnce.get() + ? Mqtt5ClientOptions.ClientSessionBehavior.REJOIN_ALWAYS + : Mqtt5ClientOptions.ClientSessionBehavior.REJOIN_POST_SUCCESS) .withOfflineQueueBehavior(Mqtt5ClientOptions.ClientOfflineQueueBehavior.FAIL_ALL_ON_DISCONNECT) .withMinReconnectDelayMs(minReconnectSeconds == 0 ? null : minReconnectSeconds * 1000) .withMaxReconnectDelayMs(maxReconnectSeconds == 0 ? null : maxReconnectSeconds * 1000) .withMinConnectedTimeToResetReconnectDelayMs( - minConnectTimeSeconds == 0 ? null : minConnectTimeSeconds * 1000).withConnectProperties( - new ConnectPacket.ConnectPacketBuilder().withRequestProblemInformation(true) - .withClientId(clientId).withKeepAliveIntervalSeconds(Coerce.toLong( - mqttTopics.findOrDefault(DEFAULT_MQTT_KEEP_ALIVE_TIMEOUT, - MQTT_KEEP_ALIVE_TIMEOUT_KEY)) - / 1000) - .withReceiveMaximum(Coerce.toLong(mqttTopics.findOrDefault(100L, - "receiveMaximum"))) - .withSessionExpiryIntervalSeconds( - Coerce.toLong(mqttTopics.findOrDefault(DEFAULT_SESSION_EXPIRY_SECONDS, - "sessionExpirySeconds")))); + minConnectTimeSeconds == 0 ? null : minConnectTimeSeconds * 1000) + .withConnectProperties(new ConnectPacket.ConnectPacketBuilder() + .withRequestProblemInformation(true) + .withClientId(clientId) + .withKeepAliveIntervalSeconds( + Coerce.toLong(mqttTopics.findOrDefault(DEFAULT_MQTT_KEEP_ALIVE_TIMEOUT, + MQTT_KEEP_ALIVE_TIMEOUT_KEY)) / 1000) + .withReceiveMaximum(Coerce.toLong(mqttTopics.findOrDefault(100L, "receiveMaximum"))) + .withSessionExpiryIntervalSeconds(Coerce.toLong(mqttTopics + .findOrDefault(DEFAULT_SESSION_EXPIRY_SECONDS, "sessionExpirySeconds")))); client = builder.build(); } catch (MqttException e) { connectFuture.completeExceptionally(e); @@ -371,8 +379,8 @@ public CompletableFuture unsubscribe(String topic) { try (LockScope ls1 = LockScope.lock(lock)) { return connect().thenCompose((client) -> { logger.atDebug().kv(TOPIC_KEY, topic).log("Unsubscribing from topic"); - return client.unsubscribe( - new UnsubscribePacket.UnsubscribePacketBuilder().withSubscription(topic).build()) + return client + .unsubscribe(new UnsubscribePacket.UnsubscribePacketBuilder().withSubscription(topic).build()) .thenApply(r -> { try (LockScope ls2 = LockScope.lock(lock)) { subscriptionTopics.removeIf(s -> s.getTopic().equals(topic)); @@ -392,7 +400,9 @@ public CompletableFuture publish(Publish publish) { // in the spooler thread before calling this method. transactionLimiter.acquire(); bandwidthLimiter.acquire(publish.getPayload().length); - logger.atTrace().kv(TOPIC_KEY, publish.getTopic()).kv(QOS_KEY, publish.getQos().name()) + logger.atTrace() + .kv(TOPIC_KEY, publish.getTopic()) + .kv(QOS_KEY, publish.getQos().name()) .log("Publishing message"); return client.publish(publish.toCrtPublishPacket()).thenApply(r -> { if (r.getType().equals(PublishResult.PublishResultType.NONE)) { @@ -450,11 +460,15 @@ private void resubscribe(boolean sessionPresent) { } private void resubscribeDroppedTopicsTask() { - long delayMillis = 0; // don't delay the first run + long delayMillis = 0; // don't delay the first run while (connected() && !droppedSubscriptionTopics.isEmpty()) { - logger.atDebug().event(RESUB_LOG_EVENT).kv("droppedTopics", - (Supplier>) () -> droppedSubscriptionTopics.stream().map(Subscribe::getTopic) - .collect(Collectors.toList())).kv("delayMillis", delayMillis) + logger.atDebug() + .event(RESUB_LOG_EVENT) + .kv("droppedTopics", + (Supplier>) () -> droppedSubscriptionTopics.stream() + .map(Subscribe::getTopic) + .collect(Collectors.toList())) + .kv("delayMillis", delayMillis) .log("Subscribing to dropped topics"); ScheduledFuture scheduledFuture = ses.schedule(() -> { List> subFutures = new ArrayList<>(); @@ -463,7 +477,10 @@ private void resubscribeDroppedTopicsTask() { if (error == null && (result == null || result.isSuccessful())) { droppedSubscriptionTopics.remove(sub); } else { - logger.atError().event(RESUB_LOG_EVENT).cause(error).kv(TOPIC_KEY, sub.getTopic()) + logger.atError() + .event(RESUB_LOG_EVENT) + .cause(error) + .kv(TOPIC_KEY, sub.getTopic()) .log("Failed to subscribe to topic. Will retry later"); } })); @@ -474,7 +491,9 @@ private void resubscribeDroppedTopicsTask() { try { allSubFutures.get(); } catch (InterruptedException e) { - logger.atWarn().event(RESUB_LOG_EVENT).cause(e) + logger.atWarn() + .event(RESUB_LOG_EVENT) + .cause(e) .log("Subscription interrupted. Cancelling subscriptions"); allSubFutures.cancel(true); } catch (ExecutionException e) { diff --git a/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqttClient.java b/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqttClient.java index 95ddd8a823..118ff1f073 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqttClient.java +++ b/src/main/java/com/aws/greengrass/mqttclient/AwsIotMqttClient.java @@ -57,15 +57,15 @@ import static com.aws.greengrass.mqttclient.MqttClient.CONNECT_LIMIT_PERMITS_FEATURE; /** - * Wrapper for a single AWS IoT MQTT client connection. - * Do not use except through {@link MqttClient}. + * Wrapper for a single AWS IoT MQTT client connection. Do not use except through {@link MqttClient}. */ class AwsIotMqttClient implements IndividualMqttClient { static final String TOPIC_KEY = "topic"; private static final String RESUB_LOG_EVENT = "resubscribe"; static final String QOS_KEY = "qos"; private static final Random RANDOM = new Random(); - private final Logger logger = LogManager.getLogger(AwsIotMqttClient.class).createChild() + private final Logger logger = LogManager.getLogger(AwsIotMqttClient.class) + .createChild() .dfltKv(MqttClient.CLIENT_ID_KEY, (Supplier) this::getClientId); private final ExecutorService executorService; @@ -101,12 +101,11 @@ class AwsIotMqttClient implements IndividualMqttClient { // Limit TPS to 1 which is IoT Core's limit for connect requests per client-id // IoT was throttling connect calls even at 1 TPS because the limit is actually 0.1 when // the same host is hit with the request. - private final RateLimiter connectLimiter = RateLimiter.create( - TestFeatureParameters.retrieveWithDefault(Double.class, CONNECT_LIMIT_PERMITS_FEATURE, 0.09)); + private final RateLimiter connectLimiter = RateLimiter + .create(TestFeatureParameters.retrieveWithDefault(Double.class, CONNECT_LIMIT_PERMITS_FEATURE, 0.09)); private final Lock lock = LockFactory.newReentrantLock(this); - @Getter(AccessLevel.PACKAGE) private final MqttClientConnectionEvents connectionEventCallback = new MqttClientConnectionEvents() { @Override @@ -138,17 +137,20 @@ public void onConnectionResumed(boolean sessionPresent) { }; AwsIotMqttClient(Provider builderProvider, - Function> messageHandler, String clientId, int clientIdNum, - Topics mqttTopics, CallbackEventManager callbackEventManager, ExecutorService executorService, - ScheduledExecutorService ses) { + Function> messageHandler, String clientId, int clientIdNum, + Topics mqttTopics, CallbackEventManager callbackEventManager, ExecutorService executorService, + ScheduledExecutorService ses) { this.builderProvider = builderProvider; this.clientId = clientId; this.clientIdNum = clientIdNum; this.mqttTopics = mqttTopics; Consumer handler = messageHandler.apply(this); - this.messageHandler = (m) -> handler.accept( - Publish.builder().topic(m.getTopic()).payload(m.getPayload()).qos(QOS.fromInt(m.getQos().getValue())) - .retain(m.getRetain()).build()); + this.messageHandler = (m) -> handler.accept(Publish.builder() + .topic(m.getTopic()) + .payload(m.getPayload()) + .qos(QOS.fromInt(m.getQos().getValue())) + .retain(m.getRetain()) + .build()); this.callbackEventManager = callbackEventManager; this.executorService = executorService; this.ses = ses; @@ -164,7 +166,7 @@ void disableRateLimiting() { public long getThrottlingWaitTimeMicros() { // Return the worst possible wait time. // Time to wait is independent of how many permits we need because future transactions - // will pay this current transaction's cost. See the JavaDocs for RateLimiter for more info. + // will pay this current transaction's cost. See the JavaDocs for RateLimiter for more info. return Math.max(bandwidthLimiter.microTimeToNextPermit(), transactionLimiter.microTimeToNextPermit()); } @@ -174,8 +176,7 @@ public long getThrottlingWaitTimeMicros() { private CompletableFuture subscribe(String topic, QualityOfService qos) { return connect().thenCompose((b) -> { - logger.atDebug().kv(TOPIC_KEY, topic).kv(QOS_KEY, qos.name()) - .log("Subscribing to topic"); + logger.atDebug().kv(TOPIC_KEY, topic).kv(QOS_KEY, qos.name()).log("Subscribing to topic"); try (LockScope ls1 = LockScope.lock(lock)) { throwIfNoConnection(); inprogressSubscriptions.incrementAndGet(); @@ -183,11 +184,12 @@ private CompletableFuture subscribe(String topic, QualityOfService qos) try (LockScope ls2 = LockScope.lock(lock)) { if (error == null) { subscriptionTopics.put(topic, qos); - logger.atDebug().kv(TOPIC_KEY, topic).kv(QOS_KEY, qos.name()) + logger.atDebug() + .kv(TOPIC_KEY, topic) + .kv(QOS_KEY, qos.name()) .log("Successfully subscribed to topic"); } else { - logger.atError().kv(TOPIC_KEY, topic) - .cause(error).log("Error subscribing to topic"); + logger.atError().kv(TOPIC_KEY, topic).cause(error).log("Error subscribing to topic"); } inprogressSubscriptions.decrementAndGet(); } @@ -198,17 +200,17 @@ private CompletableFuture subscribe(String topic, QualityOfService qos) @Override public CompletableFuture subscribe(Subscribe subscribe) { - return subscribe(subscribe.getTopic(), - QualityOfService.getEnumValueFromInteger(subscribe.getQos().getValue())) + return subscribe(subscribe.getTopic(), QualityOfService.getEnumValueFromInteger(subscribe.getQos().getValue())) .thenApply((i) -> new SubscribeResponse(null, subscribe.getQos().getValue(), null)); } @Override public CompletableFuture publish(Publish publish) { - return publish(new MqttMessage(publish.getTopic(), publish.getPayload(), + return publish( + new MqttMessage(publish.getTopic(), publish.getPayload(), QualityOfService.getEnumValueFromInteger(publish.getQos().getValue()), publish.isRetain()), - QualityOfService.getEnumValueFromInteger(publish.getQos().getValue()), publish.isRetain()).thenApply( - (i) -> new PubAck(PubAckPacket.PubAckReasonCode.SUCCESS.getValue(), null, null)); + QualityOfService.getEnumValueFromInteger(publish.getQos().getValue()), publish.isRetain()) + .thenApply((i) -> new PubAck(PubAckPacket.PubAckReasonCode.SUCCESS.getValue(), null, null)); } private CompletableFuture publish(MqttMessage message, QualityOfService qos, boolean retain) { @@ -220,7 +222,10 @@ private CompletableFuture publish(MqttMessage message, QualityOfService bandwidthLimiter.acquire(message.getPayload().length); try (LockScope ls = LockScope.lock(lock)) { throwIfNoConnection(); - logger.atTrace().kv(TOPIC_KEY, message.getTopic()).kv(QOS_KEY, qos.name()).kv("retain", retain) + logger.atTrace() + .kv(TOPIC_KEY, message.getTopic()) + .kv(QOS_KEY, qos.name()) + .kv("retain", retain) .log("Publishing message"); return connection.publish(message, qos, retain); } @@ -280,7 +285,8 @@ public CompletableFuture connect() { connectionFuture = voidCompletableFuture.thenCompose((b) -> establishConnection(false)).thenApply((sessionPresent) -> { currentlyConnected.set(true); - logger.atInfo().kv("sessionPresent", sessionPresent) + logger.atInfo() + .kv("sessionPresent", sessionPresent) .log("Successfully connected to AWS IoT Core"); resubscribe(sessionPresent); callbackEventManager.runOnInitialConnect(sessionPresent); @@ -324,15 +330,15 @@ private CompletableFuture establishConnection(boolean overrideCleanSess } int getTimeout() { - return Coerce.toInt(mqttTopics.findOrDefault( - MqttClient.DEFAULT_MQTT_OPERATION_TIMEOUT, MqttClient.MQTT_OPERATION_TIMEOUT_KEY)); + return Coerce.toInt(mqttTopics.findOrDefault(MqttClient.DEFAULT_MQTT_OPERATION_TIMEOUT, + MqttClient.MQTT_OPERATION_TIMEOUT_KEY)); } int getCloseTimeout() { // Use a shorter timeout for disconnection when closing client, // since the socket will close anyway when the process dies - return Coerce.toInt(mqttTopics.findOrDefault( - MqttClient.DEFAULT_MQTT_CLOSE_TIMEOUT, MqttClient.MQTT_OPERATION_TIMEOUT_KEY)); + return Coerce.toInt( + mqttTopics.findOrDefault(MqttClient.DEFAULT_MQTT_CLOSE_TIMEOUT, MqttClient.MQTT_OPERATION_TIMEOUT_KEY)); } /** @@ -357,10 +363,13 @@ private void resubscribe(boolean sessionPresent) { } private void resubscribeDroppedTopicsTask() { - long delayMillis = 0; // don't delay the first run + long delayMillis = 0; // don't delay the first run while (currentlyConnected.get() && !droppedSubscriptionTopics.isEmpty()) { - logger.atDebug().event(RESUB_LOG_EVENT).kv("droppedTopics", droppedSubscriptionTopics.keySet()) - .kv("delayMillis", delayMillis).log("Subscribing to dropped topics"); + logger.atDebug() + .event(RESUB_LOG_EVENT) + .kv("droppedTopics", droppedSubscriptionTopics.keySet()) + .kv("delayMillis", delayMillis) + .log("Subscribing to dropped topics"); ScheduledFuture scheduledFuture = ses.schedule(() -> { List> subFutures = new ArrayList<>(); for (Map.Entry entry : droppedSubscriptionTopics.entrySet()) { @@ -368,7 +377,10 @@ private void resubscribeDroppedTopicsTask() { if (error == null) { droppedSubscriptionTopics.remove(entry.getKey()); } else { - logger.atError().event(RESUB_LOG_EVENT).cause(error).kv(TOPIC_KEY, entry.getKey()) + logger.atError() + .event(RESUB_LOG_EVENT) + .cause(error) + .kv(TOPIC_KEY, entry.getKey()) .log("Failed to subscribe to topic. Will retry later"); } })); @@ -379,7 +391,9 @@ private void resubscribeDroppedTopicsTask() { try { allSubFutures.get(); } catch (InterruptedException e) { - logger.atWarn().event(RESUB_LOG_EVENT).cause(e) + logger.atWarn() + .event(RESUB_LOG_EVENT) + .cause(e) .log("Subscription interrupted. Cancelling subscriptions"); allSubFutures.cancel(true); } catch (ExecutionException e) { @@ -403,8 +417,8 @@ private void resubscribeDroppedTopicsTask() { @Override public boolean canAddNewSubscription() { try (LockScope ls = LockScope.lock(lock)) { - return (subscriptionTopics.size() + inprogressSubscriptionsCount()) - < MqttClient.MAX_SUBSCRIPTIONS_PER_CONNECTION; + return (subscriptionTopics.size() + + inprogressSubscriptionsCount()) < MqttClient.MAX_SUBSCRIPTIONS_PER_CONNECTION; } } diff --git a/src/main/java/com/aws/greengrass/mqttclient/CallbackEventManager.java b/src/main/java/com/aws/greengrass/mqttclient/CallbackEventManager.java index 7e6a80f21b..4f3444ac1b 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/CallbackEventManager.java +++ b/src/main/java/com/aws/greengrass/mqttclient/CallbackEventManager.java @@ -21,10 +21,10 @@ public interface OnConnectCallback { } /** - * A MqttClient may control multiple AwsIotMqttClients - * and each AwsIotMqttClients may have multiple callback events. - * @param curSessionPresent is specific for each AwsIotMqttClient controlled by the MqttClient. - * If false, mqtt Client do the callback actions. Otherwise, do nothing. + * A MqttClient may control multiple AwsIotMqttClients and each AwsIotMqttClients may have multiple callback events. + * + * @param curSessionPresent is specific for each AwsIotMqttClient controlled by the MqttClient. If false, mqtt + * Client do the callback actions. Otherwise, do nothing. * */ public void runOnConnectionResumed(boolean curSessionPresent) { @@ -37,8 +37,9 @@ public void runOnConnectionResumed(boolean curSessionPresent) { } /** - * A MqttClient may control multiple AwsIotMqttClients and when the first AwsIotMqttClients - * got connected, trigger Initial connect Event. + * A MqttClient may control multiple AwsIotMqttClients and when the first AwsIotMqttClients got connected, trigger + * Initial connect Event. + * * @param curSessionPresent current session present * */ @@ -53,6 +54,7 @@ public void runOnInitialConnect(boolean curSessionPresent) { /** * To run method of OnConnectionInterrupted if the connections are dropped. + * * @param errorCode would shared by all the callbacks. * */ @@ -66,6 +68,7 @@ public void runOnConnectionInterrupted(int errorCode) { /** * To add callback to the set of callBackEvents. + * * @param callback is an instance of MqttClientConnectionEvents. */ public void addToCallbackEvents(MqttClientConnectionEvents callback) { @@ -79,6 +82,7 @@ public void addToCallbackEvents(OnConnectCallback onConnect, MqttClientConnectio /** * To check whether the oneTimeCallback has been done. + * * @return boolean. */ public boolean hasCallbacked() { diff --git a/src/main/java/com/aws/greengrass/mqttclient/IotCoreTopicValidator.java b/src/main/java/com/aws/greengrass/mqttclient/IotCoreTopicValidator.java index b2c63020c8..7e37be61da 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/IotCoreTopicValidator.java +++ b/src/main/java/com/aws/greengrass/mqttclient/IotCoreTopicValidator.java @@ -13,12 +13,10 @@ import static com.aws.greengrass.mqttclient.MqttClient.MQTT_VERSION_5; - public final class IotCoreTopicValidator { public enum Operation { - PUBLISH, - SUBSCRIBE + PUBLISH, SUBSCRIBE } private static final int TOPIC_MAX_NUMBER_OF_FORWARD_SLASHES = 7; @@ -35,46 +33,39 @@ public enum Operation { private static final String MULTI_LEVEL_WILDCARD = "#"; private static final String SINGLE_LEVEL_WILDCARD = "+"; - private static final String ERROR_PUBLISH_TOPIC_TOO_LONG = String.format( - "The topic size of request must be no " - + "larger than %d bytes of UTF-8 encoded characters. This excludes the first " - + "3 mandatory segments for Basic Ingest topics ($AWS/rules/rule-name/)", - MAX_LENGTH_OF_TOPIC); - private static final String ERROR_SUBSCRIBE_TOPIC_TOO_LONG = String.format( - "%s or first 2 mandatory segments for MQTT Shared Subscriptions ($share/share-name/)", - ERROR_PUBLISH_TOPIC_TOO_LONG); + private static final String ERROR_PUBLISH_TOPIC_TOO_LONG = String.format("The topic size of request must be no " + + "larger than %d bytes of UTF-8 encoded characters. This excludes the first " + + "3 mandatory segments for Basic Ingest topics ($AWS/rules/rule-name/)", MAX_LENGTH_OF_TOPIC); + private static final String ERROR_SUBSCRIBE_TOPIC_TOO_LONG = + String.format("%s or first 2 mandatory segments for MQTT Shared Subscriptions ($share/share-name/)", + ERROR_PUBLISH_TOPIC_TOO_LONG); private static final String ERROR_UNKNOWN_RESERVED_TOPIC_TOO_LONG = String.format( "Reserved topic total length is greater than %d bytes of UTF-8 encoded characters " + "and is most likely over the IoT Core limit of %d bytes (excluding prefixes).", MAX_LENGTH_FOR_UNKNOWN_RESERVED_TOPIC, MAX_LENGTH_OF_TOPIC); private static final String ERROR_TOPIC_HAS_TOO_MANY_SLASHES = String.format( - "The request topic must have no more than %d forward slashes (/)", - TOPIC_MAX_NUMBER_OF_FORWARD_SLASHES); + "The request topic must have no more than %d forward slashes (/)", TOPIC_MAX_NUMBER_OF_FORWARD_SLASHES); private static final String ERROR_DIRECT_INGEST_TOPIC_EMPTY = "Effective direct ingest topic (without '$aws/rules/rule-name/' prefix) is empty"; private static final String ERROR_SHARED_SUBSCRIPTION_TOPIC_EMPTY = "Effective shared subscription topic (without '$share/share-group/' prefix) is empty"; private static final String ERROR_WILDCARD_IN_PUBLISH_TOPIC = "Publish topics must not contain wildcard characters of '#' or '+'"; - private static final String ERROR_EMPTY_TOPIC = - "Topic must not be empty"; - + private static final String ERROR_EMPTY_TOPIC = "Topic must not be empty"; private IotCoreTopicValidator() { } /** - * Check that a given topic adheres to IoT Core limits, - * such as number of forward slashes and length. + * Check that a given topic adheres to IoT Core limits, such as number of forward slashes and length. * - * @param topic topic + * @param topic topic * @param mqttVersion mqtt version (mqtt3, mqtt5) - * @param operation operation + * @param operation operation * @throws MqttRequestException if the topic is deemed to be invalid */ - public static void validateTopic(@NonNull String topic, - @NonNull String mqttVersion, - @NonNull Operation operation) throws MqttRequestException { + public static void validateTopic(@NonNull String topic, @NonNull String mqttVersion, @NonNull Operation operation) + throws MqttRequestException { if (Utils.isEmpty(topic)) { throw new MqttRequestException(ERROR_EMPTY_TOPIC); } @@ -131,16 +122,15 @@ private static void validateEffectiveTopic(String effectiveTopic, Operation oper throw new MqttRequestException(ERROR_TOPIC_HAS_TOO_MANY_SLASHES); } if (effectiveTopic.length() > MAX_LENGTH_OF_TOPIC) { - throw new MqttRequestException(operation == Operation.SUBSCRIBE - ? ERROR_SUBSCRIBE_TOPIC_TOO_LONG - : ERROR_PUBLISH_TOPIC_TOO_LONG); + throw new MqttRequestException( + operation == Operation.SUBSCRIBE ? ERROR_SUBSCRIBE_TOPIC_TOO_LONG : ERROR_PUBLISH_TOPIC_TOO_LONG); } } /** * Remove the given prefix from the topic. * - * @param topic non-null, non-empty, trimmed topic + * @param topic non-null, non-empty, trimmed topic * @param prefixRegex prefix to remove from topic * @return topic without prefix */ diff --git a/src/main/java/com/aws/greengrass/mqttclient/MqttClient.java b/src/main/java/com/aws/greengrass/mqttclient/MqttClient.java index 389d61ddef..00189086d5 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/MqttClient.java +++ b/src/main/java/com/aws/greengrass/mqttclient/MqttClient.java @@ -93,7 +93,9 @@ import static com.aws.greengrass.mqttclient.AwsIotMqttClient.TOPIC_KEY; import static com.aws.greengrass.util.RetryUtils.RANDOM; -@SuppressWarnings({"PMD.AvoidDuplicateLiterals"}) +@SuppressWarnings({ + "PMD.AvoidDuplicateLiterals" +}) public class MqttClient implements Closeable { private static final Logger logger = LogManager.getLogger(MqttClient.class); static final String MQTT_KEEP_ALIVE_TIMEOUT_KEY = "keepAliveTimeoutMs"; @@ -184,18 +186,20 @@ public void onConnectionResumed(boolean sessionPresent) { private final CallbackEventManager.OnConnectCallback onConnect = callbacks::onConnectionResumed; private final Map>, Subscribe> cbMapping = new ConcurrentHashMap<>(); @SuppressWarnings("PMD.DoubleBraceInitialization") - private final Set nonRetryablePubAckReasonCodes = new HashSet() {{ - // These first two are actually successes, so definitely don't need to retry them. - this.add(PubAckPacket.PubAckReasonCode.SUCCESS.getValue()); - this.add(PubAckPacket.PubAckReasonCode.NO_MATCHING_SUBSCRIBERS.getValue()); - - // These won't ever be resolved by retries - this.add(PubAckPacket.PubAckReasonCode.TOPIC_NAME_INVALID.getValue()); - this.add(PubAckPacket.PubAckReasonCode.PAYLOAD_FORMAT_INVALID.getValue()); - - // Not authorized could be resolved, but not in a short time span. Better to just give up - this.add(PubAckPacket.PubAckReasonCode.NOT_AUTHORIZED.getValue()); - }}; + private final Set nonRetryablePubAckReasonCodes = new HashSet() { + { + // These first two are actually successes, so definitely don't need to retry them. + this.add(PubAckPacket.PubAckReasonCode.SUCCESS.getValue()); + this.add(PubAckPacket.PubAckReasonCode.NO_MATCHING_SUBSCRIBERS.getValue()); + + // These won't ever be resolved by retries + this.add(PubAckPacket.PubAckReasonCode.TOPIC_NAME_INVALID.getValue()); + this.add(PubAckPacket.PubAckReasonCode.PAYLOAD_FORMAT_INVALID.getValue()); + + // Not authorized could be resolved, but not in a short time span. Better to just give up + this.add(PubAckPacket.PubAckReasonCode.NOT_AUTHORIZED.getValue()); + } + }; // // TODO: [P41214930] Handle timeouts and retries @@ -205,15 +209,15 @@ public void onConnectionResumed(boolean sessionPresent) { * Constructor for injection. * * @param deviceConfiguration device configuration - * @param ses scheduled executor service - * @param executorService executor service - * @param securityService security service - * @param kernel kernel instance + * @param ses scheduled executor service + * @param executorService executor service + * @param securityService security service + * @param kernel kernel instance */ @Inject @SuppressWarnings("PMD.PreserveStackTrace") public MqttClient(DeviceConfiguration deviceConfiguration, ScheduledExecutorService ses, - ExecutorService executorService, SecurityService securityService, Kernel kernel) { + ExecutorService executorService, SecurityService securityService, Kernel kernel) { this(deviceConfiguration, null, ses, executorService, kernel); this.builderProvider = (clientBootstrap) -> { @@ -224,25 +228,27 @@ public MqttClient(DeviceConfiguration deviceConfiguration, ScheduledExecutorServ throw new MqttException(e.getMessage()); } - int pingTimeoutMs = Coerce.toInt( - mqttTopics.findOrDefault(DEFAULT_MQTT_PING_TIMEOUT, MQTT_PING_TIMEOUT_KEY)); - int keepAliveMs = Coerce.toInt( - mqttTopics.findOrDefault(DEFAULT_MQTT_KEEP_ALIVE_TIMEOUT, MQTT_KEEP_ALIVE_TIMEOUT_KEY)); + int pingTimeoutMs = + Coerce.toInt(mqttTopics.findOrDefault(DEFAULT_MQTT_PING_TIMEOUT, MQTT_PING_TIMEOUT_KEY)); + int keepAliveMs = Coerce + .toInt(mqttTopics.findOrDefault(DEFAULT_MQTT_KEEP_ALIVE_TIMEOUT, MQTT_KEEP_ALIVE_TIMEOUT_KEY)); if (keepAliveMs != 0 && keepAliveMs <= pingTimeoutMs) { - throw new MqttException(String.format("%s must be greater than %s", - MQTT_KEEP_ALIVE_TIMEOUT_KEY, MQTT_PING_TIMEOUT_KEY)); + throw new MqttException(String.format("%s must be greater than %s", MQTT_KEEP_ALIVE_TIMEOUT_KEY, + MQTT_PING_TIMEOUT_KEY)); } String endpoint = Coerce.toString(deviceConfiguration.getIotDataEndpoint()); builder.withCertificateAuthorityFromPath(null, Coerce.toString(deviceConfiguration.getRootCAFilePath())) .withEndpoint(endpoint) .withPort((short) Coerce.toInt(mqttTopics.findOrDefault(DEFAULT_MQTT_PORT, MQTT_PORT_KEY))) - .withCleanSession(false).withBootstrap(clientBootstrap) + .withCleanSession(false) + .withBootstrap(clientBootstrap) .withKeepAliveMs(keepAliveMs) .withProtocolOperationTimeoutMs(getMqttOperationTimeoutMillis()) .withPingTimeoutMs(pingTimeoutMs) - .withSocketOptions(new SocketOptions()).withTimeoutMs(Coerce.toInt( - mqttTopics.findOrDefault(DEFAULT_MQTT_SOCKET_TIMEOUT, MQTT_SOCKET_TIMEOUT_KEY))); + .withSocketOptions(new SocketOptions()) + .withTimeoutMs(Coerce + .toInt(mqttTopics.findOrDefault(DEFAULT_MQTT_SOCKET_TIMEOUT, MQTT_SOCKET_TIMEOUT_KEY))); try (LockScope ls = LockScope.lock(httpProxyLock)) { HttpProxyOptions httpProxyOptions = ProxyUtils.getHttpProxyOptions(deviceConfiguration, proxyTlsContext); @@ -263,8 +269,8 @@ public MqttClient(DeviceConfiguration deviceConfiguration, ScheduledExecutorServ } protected MqttClient(DeviceConfiguration deviceConfiguration, - Function builderProvider, - ScheduledExecutorService ses, ExecutorService executorService, Kernel kernel) { + Function builderProvider, ScheduledExecutorService ses, + ExecutorService executorService, Kernel kernel) { this.deviceConfiguration = deviceConfiguration; this.executorService = executorService; this.ses = ses; @@ -302,10 +308,10 @@ protected MqttClient(DeviceConfiguration deviceConfiguration, } // List of configuration nodes that we need to reconfigure for if they change - if (!(node.childOf(DEVICE_MQTT_NAMESPACE) || node.childOf(DEVICE_PARAM_THING_NAME) || node.childOf( - DEVICE_PARAM_IOT_DATA_ENDPOINT) || node.childOf(DEVICE_PARAM_PRIVATE_KEY_PATH) || node.childOf( - DEVICE_PARAM_CERTIFICATE_FILE_PATH) || node.childOf(DEVICE_PARAM_ROOT_CA_PATH) || node.childOf( - DEVICE_PARAM_AWS_REGION))) { + if (!(node.childOf(DEVICE_MQTT_NAMESPACE) || node.childOf(DEVICE_PARAM_THING_NAME) + || node.childOf(DEVICE_PARAM_IOT_DATA_ENDPOINT) || node.childOf(DEVICE_PARAM_PRIVATE_KEY_PATH) + || node.childOf(DEVICE_PARAM_CERTIFICATE_FILE_PATH) || node.childOf(DEVICE_PARAM_ROOT_CA_PATH) + || node.childOf(DEVICE_PARAM_AWS_REGION))) { return true; } @@ -314,7 +320,9 @@ protected MqttClient(DeviceConfiguration deviceConfiguration, return true; } - logger.atDebug().kv("modifiedNode", node.getFullName()).kv("changeType", what) + logger.atDebug() + .kv("modifiedNode", node.getFullName()) + .kv("changeType", what) .log("Reconfiguring MQTT clients"); return false; }, (what) -> { @@ -351,7 +359,9 @@ protected MqttClient(DeviceConfiguration deviceConfiguration, connection.reconnect(getMqttOperationTimeoutMillis()); brokenConnections.remove(connection); } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.atError().setCause(e).kv(CLIENT_ID_KEY, connection.getClientId()) + logger.atError() + .setCause(e) + .kv(CLIENT_ID_KEY, connection.getClientId()) .log("Error while reconnecting MQTT client"); } } @@ -370,14 +380,13 @@ protected MqttClient(DeviceConfiguration deviceConfiguration, * constructor specific for unit and integration test with spooler. * * @param deviceConfiguration device configuration - * @param spool spooler - * @param mqttOnline indicator for whether mqtt is online or not - * @param builderProvider builder provider - * @param executorService executor service + * @param spool spooler + * @param mqttOnline indicator for whether mqtt is online or not + * @param builderProvider builder provider + * @param executorService executor service */ public MqttClient(DeviceConfiguration deviceConfiguration, Spool spool, boolean mqttOnline, - Function builderProvider, - ExecutorService executorService) { + Function builderProvider, ExecutorService executorService) { this.deviceConfiguration = deviceConfiguration; mqttTopics = this.deviceConfiguration.getMQTTNamespace(); @@ -401,17 +410,16 @@ private TlsContextOptions getTlsContextOptions(String rootCaPath) { } private void validateAndSetMqttPublishConfiguration() { - maxInFlightPublishes = Coerce.toInt(mqttTopics - .findOrDefault(DEFAULT_MAX_IN_FLIGHT_PUBLISHES, - MQTT_MAX_IN_FLIGHT_PUBLISHES_KEY)); + maxInFlightPublishes = Coerce + .toInt(mqttTopics.findOrDefault(DEFAULT_MAX_IN_FLIGHT_PUBLISHES, MQTT_MAX_IN_FLIGHT_PUBLISHES_KEY)); if (maxInFlightPublishes > IOT_MAX_LIMIT_IN_FLIGHT_OF_QOS1_PUBLISHES) { logger.atWarn() .kv(MQTT_MAX_IN_FLIGHT_PUBLISHES_KEY, maxInFlightPublishes) .kv("Max acceptable configuration", IOT_MAX_LIMIT_IN_FLIGHT_OF_QOS1_PUBLISHES) .log("The configuration of {} may hit the AWS IoT Core restricting number of " - + "unacknowledged QoS=1 publish requests per client. " - + "Will change to the maximum allowed setting: {}", - MQTT_MAX_IN_FLIGHT_PUBLISHES_KEY, IOT_MAX_LIMIT_IN_FLIGHT_OF_QOS1_PUBLISHES); + + "unacknowledged QoS=1 publish requests per client. " + + "Will change to the maximum allowed setting: {}", MQTT_MAX_IN_FLIGHT_PUBLISHES_KEY, + IOT_MAX_LIMIT_IN_FLIGHT_OF_QOS1_PUBLISHES); maxInFlightPublishes = IOT_MAX_LIMIT_IN_FLIGHT_OF_QOS1_PUBLISHES; } @@ -419,16 +427,18 @@ private void validateAndSetMqttPublishConfiguration() { maxPublishMessageSize = Coerce.toInt(mqttTopics.findOrDefault(DEFAULT_MQTT_MAX_OF_MESSAGE_SIZE_IN_BYTES, MQTT_MAX_OF_MESSAGE_SIZE_IN_BYTES_KEY)); if (maxPublishMessageSize > MQTT_MAX_LIMIT_OF_MESSAGE_SIZE_IN_BYTES) { - logger.atWarn().kv(MQTT_MAX_OF_MESSAGE_SIZE_IN_BYTES_KEY, maxPublishMessageSize).kv("Max acceptable " - + "configuration", MQTT_MAX_LIMIT_OF_MESSAGE_SIZE_IN_BYTES).log("The configuration of {} " + logger.atWarn() + .kv(MQTT_MAX_OF_MESSAGE_SIZE_IN_BYTES_KEY, maxPublishMessageSize) + .kv("Max acceptable " + "configuration", MQTT_MAX_LIMIT_OF_MESSAGE_SIZE_IN_BYTES) + .log("The configuration of {} " + "exceeds the max limit and will change to the maximum allowed setting: {} bytes", - MQTT_MAX_OF_MESSAGE_SIZE_IN_BYTES_KEY, MQTT_MAX_LIMIT_OF_MESSAGE_SIZE_IN_BYTES); + MQTT_MAX_OF_MESSAGE_SIZE_IN_BYTES_KEY, MQTT_MAX_LIMIT_OF_MESSAGE_SIZE_IN_BYTES); maxPublishMessageSize = MQTT_MAX_LIMIT_OF_MESSAGE_SIZE_IN_BYTES; } // if maxPublishRetryCount = -1, publish request would be retried with unlimited times. - maxPublishRetryCount = Coerce.toInt(mqttTopics.findOrDefault(DEFAULT_MQTT_MAX_OF_PUBLISH_RETRY_COUNT, - MQTT_MAX_OF_PUBLISH_RETRY_COUNT_KEY)); + maxPublishRetryCount = Coerce.toInt( + mqttTopics.findOrDefault(DEFAULT_MQTT_MAX_OF_PUBLISH_RETRY_COUNT, MQTT_MAX_OF_PUBLISH_RETRY_COUNT_KEY)); } /** @@ -438,14 +448,14 @@ private void validateAndSetMqttPublishConfiguration() { * @throws MqttRequestException if the request is invalid for any reason */ @SuppressWarnings("PMD.CloseResource") - public CompletableFuture subscribe(Subscribe request) - throws MqttRequestException { + public CompletableFuture subscribe(Subscribe request) throws MqttRequestException { try (LockScope ls = LockScope.lock(lock)) { if (isClosed.get()) { throw new MqttRequestException("MQTT client is shut down"); } if (!deviceConfiguration.isDeviceConfiguredToTalkToCloud()) { - logger.atError().kv(TOPIC_KEY, request.getTopic()) + logger.atError() + .kv(TOPIC_KEY, request.getTopic()) .log("Cannot subscribe because device is configured to run offline"); throw new MqttRequestException("Device is not configured to connect to AWS"); } @@ -493,16 +503,15 @@ public CompletableFuture subscribe(Subscribe request) * Subscribe to a MQTT topic. * * @param request subscribe request - * @throws ExecutionException if an error occurs + * @throws ExecutionException if an error occurs * @throws InterruptedException if the thread is interrupted while subscribing - * @throws TimeoutException if the request times out - * @throws MqttException if the request fails + * @throws TimeoutException if the request times out + * @throws MqttException if the request fails * @deprecated Use {@code subscribe(Subscribe request)} instead */ @Deprecated @SuppressWarnings("PMD.AvoidCatchingGenericException") - public void subscribe(SubscribeRequest request) - throws ExecutionException, InterruptedException, TimeoutException { + public void subscribe(SubscribeRequest request) throws ExecutionException, InterruptedException, TimeoutException { try { // Deduplicate subscription callbacks so that retries do not result in getting called multiple times Subscribe newReq = cbMapping.computeIfAbsent(new Pair<>(request.getTopic(), request.getCallback()), (p) -> { @@ -510,33 +519,35 @@ public void subscribe(SubscribeRequest request) .accept(new MqttMessage(m.getTopic(), m.getPayload(), QualityOfService.getEnumValueFromInteger(m.getQos().getValue()), m.isRetain())); - return Subscribe.builder().qos(QOS.fromInt(request.getQos().getValue())).topic(request.getTopic()) - .callback(cb).build(); + return Subscribe.builder() + .qos(QOS.fromInt(request.getQos().getValue())) + .topic(request.getTopic()) + .callback(cb) + .build(); }); - subscribe(newReq) - .thenApply((v) -> { - // null is a success because subscribe returns null if the subscription already existed - if (v == null || v.isSuccessful()) { - return v; - } - String rcString = SubAckPacket.SubAckReasonCode.UNSPECIFIED_ERROR.name(); - try { - rcString = SubAckPacket.SubAckReasonCode.getEnumValueFromInteger(v.getReasonCode()).name(); - } catch (RuntimeException ignored) { - } - // Consumers of this deprecated API expect to receive an MqttException if subscribing fails - throw new MqttException( - "Error subscribing. Reason: " + rcString); - }) - .get(getMqttOperationTimeoutMillis(), TimeUnit.MILLISECONDS); + subscribe(newReq).thenApply((v) -> { + // null is a success because subscribe returns null if the subscription already existed + if (v == null || v.isSuccessful()) { + return v; + } + String rcString = SubAckPacket.SubAckReasonCode.UNSPECIFIED_ERROR.name(); + try { + rcString = SubAckPacket.SubAckReasonCode.getEnumValueFromInteger(v.getReasonCode()).name(); + } catch (RuntimeException ignored) { + } + // Consumers of this deprecated API expect to receive an MqttException if subscribing fails + throw new MqttException("Error subscribing. Reason: " + rcString); + }).get(getMqttOperationTimeoutMillis(), TimeUnit.MILLISECONDS); } catch (MqttRequestException e) { throw new ExecutionException(e); } } private Optional> findExistingSubscriberForTopic(String topic) { - return subscriptionTopics.entrySet().stream().filter(s -> s.getKey().isSupersetOf(new MqttTopic(topic))) + return subscriptionTopics.entrySet() + .stream() + .filter(s -> s.getKey().isSupersetOf(new MqttTopic(topic))) .findAny(); } @@ -544,8 +555,7 @@ private Optional> findExistingSubscri private void triggerSpooler() { // Do not synchronize on MqttClient because that causes a dead lock try (LockScope ls = LockScope.lock(spoolingFutureLock)) { - if (spoolingFuture.get() == null || spoolingFuture.get().isDone() - && !spoolingFuture.get().isCancelled()) { + if (spoolingFuture.get() == null || spoolingFuture.get().isDone() && !spoolingFuture.get().isCancelled()) { try { spoolingFuture.set(executorService.submit(this::runSpooler)); } catch (RejectedExecutionException e) { @@ -569,8 +579,8 @@ public CompletableFuture unsubscribe(Unsubscribe request) throws MqttReque // Use the write lock because we're modifying the subscriptions and trying to consolidate them try (LockScope scope = LockScope.lock(connectionLock.writeLock())) { for (Map.Entry sub : subscriptions.entrySet()) { - if (sub.getKey().getCallback() == request.getSubscriptionCallback() && sub.getKey().getTopic() - .equals(request.getTopic())) { + if (sub.getKey().getCallback() == request.getSubscriptionCallback() + && sub.getKey().getTopic().equals(request.getTopic())) { subscriptions.remove(sub.getKey()); } @@ -578,10 +588,12 @@ public CompletableFuture unsubscribe(Unsubscribe request) throws MqttReque } // If we have no remaining subscriptions for a topic, then unsubscribe from it in the cloud - Set> deadSubscriptionTopics = - subscriptionTopics.entrySet().stream().filter(s -> subscriptions.keySet().stream() - .noneMatch(sub -> s.getKey().isSupersetOf(new MqttTopic(sub.getTopic())))) - .collect(Collectors.toSet()); + Set> deadSubscriptionTopics = subscriptionTopics.entrySet() + .stream() + .filter(s -> subscriptions.keySet() + .stream() + .noneMatch(sub -> s.getKey().isSupersetOf(new MqttTopic(sub.getTopic())))) + .collect(Collectors.toSet()); if (deadSubscriptionTopics.isEmpty()) { return CompletableFuture.completedFuture(null); @@ -595,11 +607,13 @@ public CompletableFuture unsubscribe(Unsubscribe request) throws MqttReque // Since we changed the cloud subscriptions, we need to recalculate the client // to use for each subscription, since it may have changed - subscriptions.entrySet().stream() + subscriptions.entrySet() + .stream() // if the cloud clients are the same, and the removed topic covered the topic // that we're looking at, then recalculate that topic's client - .filter(s -> s.getValue() == sub.getValue() && sub.getKey() - .isSupersetOf(new MqttTopic(s.getKey().getTopic()))).forEach(e -> { + .filter(s -> s.getValue() == sub.getValue() + && sub.getKey().isSupersetOf(new MqttTopic(s.getKey().getTopic()))) + .forEach(e -> { // recalculate and replace the client Optional> subscriberForTopic = findExistingSubscriberForTopic(e.getKey().getTopic()); @@ -622,9 +636,9 @@ public CompletableFuture unsubscribe(Unsubscribe request) throws MqttReque * Unsubscribe from a MQTT topic. * * @param request unsubscribe request - * @throws ExecutionException if an error occurs + * @throws ExecutionException if an error occurs * @throws InterruptedException if the thread is interrupted while unsubscribing - * @throws TimeoutException if the request times out + * @throws TimeoutException if the request times out */ public void unsubscribe(UnsubscribeRequest request) throws ExecutionException, InterruptedException, TimeoutException { @@ -638,8 +652,9 @@ public void unsubscribe(UnsubscribeRequest request) if (subReq == null) { return; } - unsubscribe(Unsubscribe.builder().subscriptionCallback(subReq.getCallback()) - .topic(request.getTopic()).build()).thenAccept((m) -> cbMapping.remove(lookup)) + unsubscribe( + Unsubscribe.builder().subscriptionCallback(subReq.getCallback()).topic(request.getTopic()).build()) + .thenAccept((m) -> cbMapping.remove(lookup)) .get(getMqttOperationTimeoutMillis(), TimeUnit.MILLISECONDS); } catch (MqttRequestException e) { throw new ExecutionException(e); @@ -647,8 +662,7 @@ public void unsubscribe(UnsubscribeRequest request) } /** - * Publish to a MQTT topic. This method will exit when the message is successfully - * added into the spooler. + * Publish to a MQTT topic. This method will exit when the message is successfully added into the spooler. * * @param request publish request * @throws MqttRequestException if the exception is invalid @@ -671,8 +685,8 @@ public PublishResponse publish(Publish request) throw e; } - boolean willDropTheRequest = !mqttOnline.get() && request.getQos().getValue() == 0 && !spool.getSpoolConfig() - .isKeepQos0WhenOffline(); + boolean willDropTheRequest = !mqttOnline.get() && request.getQos().getValue() == 0 + && !spool.getSpoolConfig().isKeepQos0WhenOffline(); if (willDropTheRequest) { SpoolerStoreException e = new SpoolerStoreException("Device is offline. Dropping QoS 0 message."); @@ -691,8 +705,8 @@ public PublishResponse publish(Publish request) } /** - * Publish to a MQTT topic. The future will be completed immediately no matter what. - * It only represents that the message has been successfully stored in the spooler. + * Publish to a MQTT topic. The future will be completed immediately no matter what. It only represents that the + * message has been successfully stored in the spooler. * * @param request publish request */ @@ -711,14 +725,17 @@ private void isValidPublishRequest(Publish request) throws MqttRequestException // Payload size should be smaller than MQTT maximum message size int messageSize = request.getPayload().length; if (messageSize > maxPublishMessageSize) { - throw new MqttRequestException(String.format("The publishing message size %d bytes exceeds the " - + "configured limit of %d bytes", messageSize, maxPublishMessageSize)); + throw new MqttRequestException( + String.format("The publishing message size %d bytes exceeds the " + "configured limit of %d bytes", + messageSize, maxPublishMessageSize)); } IotCoreTopicValidator.validateTopic(request.getTopic(), getMqttVersion(), IotCoreTopicValidator.Operation.PUBLISH); } - @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.PreserveStackTrace"}) + @SuppressWarnings({ + "PMD.AvoidCatchingThrowable", "PMD.PreserveStackTrace" + }) protected CompletableFuture publishSingleSpoolerMessage(IndividualMqttClient connection) throws InterruptedException { long id = -1L; @@ -728,57 +745,55 @@ protected CompletableFuture publishSingleSpoolerMessage(IndividualMqttCl Publish request = spooledMessage.getRequest(); long finalId = id; - return connection.publish(request) - .whenComplete((response, throwable) -> { - if (throwable == null && (response == null || response.isSuccessful())) { + return connection.publish(request).whenComplete((response, throwable) -> { + if (throwable == null && (response == null || response.isSuccessful())) { + spool.removeMessageById(finalId); + logger.atTrace() + .kv("id", finalId) + .kv("topic", request.getTopic()) + .log("Successfully published message"); + } else { + // Handle reason codes by retrying (or not) + if (response != null && !response.isSuccessful()) { + int rc = response.getReasonCode(); + // If the error isn't retryable, then remove the message to stop + // retrying it and log the problem. + if (nonRetryablePubAckReasonCodes.contains(rc)) { spool.removeMessageById(finalId); - logger.atTrace().kv("id", finalId).kv("topic", request.getTopic()) - .log("Successfully published message"); - } else { - // Handle reason codes by retrying (or not) - if (response != null && !response.isSuccessful()) { - int rc = response.getReasonCode(); - // If the error isn't retryable, then remove the message to stop - // retrying it and log the problem. - if (nonRetryablePubAckReasonCodes.contains(rc)) { - spool.removeMessageById(finalId); - logger.atInfo() - .kv("reasonCode", response.getReasonCode()) - .kv("reason", response.getReasonString()) - .kv(TOPIC_KEY, request.getTopic()) - .log("Publishing message got a non-retryable reason code, not retrying"); - } - // otherwise, fallthrough and let it retry - } - if (maxPublishRetryCount == -1 || spooledMessage.getRetried().getAndIncrement() - < maxPublishRetryCount) { - spool.addId(finalId); - LogEventBuilder l = logger.atError(); - if (response != null) { - l = l.kv("reasonCode", response.getReasonCode()) - .kv("reason", response.getReasonString()); - } - if (throwable != null) { - l = l.cause(throwable); - } - l.log("Failed to publish the message via Spooler and will retry"); - } else { - LogEventBuilder l = logger.atError(); - if (response != null) { - l = l.kv("reasonCode", response.getReasonCode()) - .kv("reason", response.getReasonString()); - } - if (throwable != null) { - l = l.cause(throwable); - } - l.log("Failed to publish the message via Spooler" - + " after retried {} times and will drop the message", - maxPublishRetryCount); - spool.removeMessageById(finalId); - } - + logger.atInfo() + .kv("reasonCode", response.getReasonCode()) + .kv("reason", response.getReasonString()) + .kv(TOPIC_KEY, request.getTopic()) + .log("Publishing message got a non-retryable reason code, not retrying"); } - }); + // otherwise, fallthrough and let it retry + } + if (maxPublishRetryCount == -1 + || spooledMessage.getRetried().getAndIncrement() < maxPublishRetryCount) { + spool.addId(finalId); + LogEventBuilder l = logger.atError(); + if (response != null) { + l = l.kv("reasonCode", response.getReasonCode()).kv("reason", response.getReasonString()); + } + if (throwable != null) { + l = l.cause(throwable); + } + l.log("Failed to publish the message via Spooler and will retry"); + } else { + LogEventBuilder l = logger.atError(); + if (response != null) { + l = l.kv("reasonCode", response.getReasonCode()).kv("reason", response.getReasonString()); + } + if (throwable != null) { + l = l.cause(throwable); + } + l.log("Failed to publish the message via Spooler" + + " after retried {} times and will drop the message", maxPublishRetryCount); + spool.removeMessageById(finalId); + } + + } + }); } catch (Throwable t) { // valid id is starting from 0 if (id >= 0) { @@ -798,7 +813,9 @@ protected CompletableFuture publishSingleSpoolerMessage(IndividualMqttCl /** * Iterate the spooler queue to publish all the spooled message. */ - @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.CloseResource"}) + @SuppressWarnings({ + "PMD.AvoidCatchingThrowable", "PMD.CloseResource" + }) @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER") protected void runSpooler() { // Do not use CompletableFuture.anyOf to wait for this set to have space @@ -874,8 +891,8 @@ protected void runSpooler() { private IndividualMqttClient getConnection(boolean forSubscription) { try (LockScope ls = LockScope.lock(lock)) { // If we have no connections, or our connections are over-subscribed, create a new connection - if (connections.isEmpty() || forSubscription && connections.stream() - .noneMatch(IndividualMqttClient::canAddNewSubscription)) { + if (connections.isEmpty() + || forSubscription && connections.stream().noneMatch(IndividualMqttClient::canAddNewSubscription)) { IndividualMqttClient conn = getNewMqttClient(); activeClientIds.add(conn.getClientIdNum()); connections.add(conn); @@ -885,9 +902,9 @@ private IndividualMqttClient getConnection(boolean forSubscription) { if (connections.stream().filter(IndividualMqttClient::canAddNewSubscription).count() > 1) { // Check for, and then close and remove any connection that has no subscriptions or any in progress // subscriptions. - Set closableConnections = - connections.stream().filter(IndividualMqttClient::isConnectionClosable) - .collect(Collectors.toSet()); + Set closableConnections = connections.stream() + .filter(IndividualMqttClient::isConnectionClosable) + .collect(Collectors.toSet()); for (IndividualMqttClient closableConnection : closableConnections) { // Leave the last connection alive to use for publishing if (connections.size() == 1) { @@ -915,7 +932,9 @@ private IndividualMqttClient getConnection(boolean forSubscription) { @SuppressWarnings("PMD.AvoidCatchingThrowable") Consumer getMessageHandlerForClient(IndividualMqttClient client) { return (message) -> { - logger.atTrace().kv(CLIENT_ID_KEY, client.getClientId()).kv(TOPIC_KEY, message.getTopic()) + logger.atTrace() + .kv(CLIENT_ID_KEY, client.getClientId()) + .kv(TOPIC_KEY, message.getTopic()) .log("Received MQTT message"); // Each subscription is associated with a single IndividualMqttClient even if this @@ -928,7 +947,8 @@ Consumer getMessageHandlerForClient(IndividualMqttClient client) { Predicate> subscriptionsMatchingTopic = s -> MqttTopic.topicIsSupersetOf(s.getKey().getTopic(), message.getTopic()); - Set exactlyMatchingSubs = subscriptions.entrySet().stream() + Set exactlyMatchingSubs = subscriptions.entrySet() + .stream() .filter(s -> s.getValue() == client) .filter(subscriptionsMatchingTopic) .map(Map.Entry::getKey) @@ -941,20 +961,25 @@ Consumer getMessageHandlerForClient(IndividualMqttClient client) { // message back to the same client which sent the update request, and not to the client that has // subscribed to the update/accepted topic. - subs = subscriptions.entrySet().stream() + subs = subscriptions.entrySet() + .stream() .filter(subscriptionsMatchingTopic) .map(Map.Entry::getKey) .collect(Collectors.toSet()); if (subs.isEmpty()) { // We found no subscribers at all, so we'll log out an error and exit. - logger.atError().kv(TOPIC_KEY, message.getTopic()).kv(CLIENT_ID_KEY, client.getClientId()) + logger.atError() + .kv(TOPIC_KEY, message.getTopic()) + .kv(CLIENT_ID_KEY, client.getClientId()) .log("Somehow got message from topic that no one subscribed to"); return; } else { // We did find at least one subscriber matching the topic, but it didn't match the client // that we subscribed on. This is weird, but it can be expected for IoT Jobs as explained above. - logger.atWarn().kv(TOPIC_KEY, message.getTopic()).kv(CLIENT_ID_KEY, client.getClientId()) + logger.atWarn() + .kv(TOPIC_KEY, message.getTopic()) + .kv(CLIENT_ID_KEY, client.getClientId()) .log("Got a message from a topic on a different client than what we subscribed with." + " This is odd, but it isn't a problem"); } @@ -963,7 +988,9 @@ Consumer getMessageHandlerForClient(IndividualMqttClient client) { try { h.getCallback().accept(message); } catch (Throwable t) { - logger.atError().kv("message", message).kv(CLIENT_ID_KEY, client.getClientId()) + logger.atError() + .kv("message", message) + .kv(CLIENT_ID_KEY, client.getClientId()) .log("Unhandled error in MQTT message callback", t); } }); @@ -979,12 +1006,14 @@ protected int getNextClientIdNumber() { return 0; } - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.AvoidRethrowingException" + }) protected IndividualMqttClient getNewMqttClient() { int clientIdNum = getNextClientIdNumber(); // Name client by thingName# except for the first connection which will just be thingName - String clientId = Coerce.toString(deviceConfiguration.getThingName()) + (clientIdNum == 0 ? "" - : "#" + (clientIdNum + 1)); + String clientId = + Coerce.toString(deviceConfiguration.getThingName()) + (clientIdNum == 0 ? "" : "#" + (clientIdNum + 1)); logger.atDebug().kv("clientId", clientId).log("Getting new MQTT connection"); if (MQTT_VERSION_5.equalsIgnoreCase(getMqttVersion())) { @@ -1035,7 +1064,7 @@ public void addToCallbackEvents(MqttClientConnectionEvents callbacks) { } public void addToCallbackEvents(CallbackEventManager.OnConnectCallback onConnect, - MqttClientConnectionEvents callbacks) { + MqttClientConnectionEvents callbacks) { callbackEventManager.addToCallbackEvents(onConnect, callbacks); } diff --git a/src/main/java/com/aws/greengrass/mqttclient/MqttRequestException.java b/src/main/java/com/aws/greengrass/mqttclient/MqttRequestException.java index c1b4d4f1f7..c024009aff 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/MqttRequestException.java +++ b/src/main/java/com/aws/greengrass/mqttclient/MqttRequestException.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package com.aws.greengrass.mqttclient; public class MqttRequestException extends Exception { diff --git a/src/main/java/com/aws/greengrass/mqttclient/MqttTopic.java b/src/main/java/com/aws/greengrass/mqttclient/MqttTopic.java index 5bfd702d7b..c2eeaf0384 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/MqttTopic.java +++ b/src/main/java/com/aws/greengrass/mqttclient/MqttTopic.java @@ -5,7 +5,6 @@ package com.aws.greengrass.mqttclient; - import lombok.EqualsAndHashCode; import lombok.Getter; diff --git a/src/main/java/com/aws/greengrass/mqttclient/PublishRequest.java b/src/main/java/com/aws/greengrass/mqttclient/PublishRequest.java index fca705250b..dc76ba7c5b 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/PublishRequest.java +++ b/src/main/java/com/aws/greengrass/mqttclient/PublishRequest.java @@ -15,11 +15,13 @@ @SuppressWarnings("PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal") @Value public class PublishRequest { - @NonNull String topic; - @NonNull QualityOfService qos; + @NonNull + String topic; + @NonNull + QualityOfService qos; /** - * Retain the message in the cloud MQTT broker (only last message with retain is actually kept). - * Subscribers will immediately receive the last retained message when they first subscribe. + * Retain the message in the cloud MQTT broker (only last message with retain is actually kept). Subscribers will + * immediately receive the last retained message when they first subscribe. */ boolean retain; byte[] payload; @@ -42,8 +44,11 @@ protected PublishRequest(String topic, QualityOfService qos, boolean retain, byt * @return {@link Publish} */ public Publish toPublish() { - return Publish.builder().topic(getTopic()).payload(getPayload()) + return Publish.builder() + .topic(getTopic()) + .payload(getPayload()) .qos(QOS.fromInt(getQos().getValue())) - .retain(isRetain()).build(); + .retain(isRetain()) + .build(); } } diff --git a/src/main/java/com/aws/greengrass/mqttclient/SubscribeRequest.java b/src/main/java/com/aws/greengrass/mqttclient/SubscribeRequest.java index 3d21968a9e..fc26ddde91 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/SubscribeRequest.java +++ b/src/main/java/com/aws/greengrass/mqttclient/SubscribeRequest.java @@ -16,8 +16,11 @@ @Builder @Value public class SubscribeRequest { - @NonNull String topic; + @NonNull + String topic; @Builder.Default - @NonNull QualityOfService qos = QualityOfService.AT_LEAST_ONCE; - @NonNull Consumer callback; + @NonNull + QualityOfService qos = QualityOfService.AT_LEAST_ONCE; + @NonNull + Consumer callback; } diff --git a/src/main/java/com/aws/greengrass/mqttclient/UnsubscribeRequest.java b/src/main/java/com/aws/greengrass/mqttclient/UnsubscribeRequest.java index 0c92a8c76a..e4959bb08a 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/UnsubscribeRequest.java +++ b/src/main/java/com/aws/greengrass/mqttclient/UnsubscribeRequest.java @@ -15,6 +15,8 @@ @Builder @Value public class UnsubscribeRequest { - @NonNull String topic; - @NonNull Consumer callback; + @NonNull + String topic; + @NonNull + Consumer callback; } diff --git a/src/main/java/com/aws/greengrass/mqttclient/WrapperMqttClientConnection.java b/src/main/java/com/aws/greengrass/mqttclient/WrapperMqttClientConnection.java index 8cbb0c7d69..eba8d928d3 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/WrapperMqttClientConnection.java +++ b/src/main/java/com/aws/greengrass/mqttclient/WrapperMqttClientConnection.java @@ -21,7 +21,6 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; - public class WrapperMqttClientConnection extends MqttClientConnection { private final MqttClient mqttClient; @@ -30,8 +29,9 @@ public class WrapperMqttClientConnection extends MqttClientConnection { /** * Constructor. + * * @param mqttClient is from package of com.aws.greengrass.mqtt to replace the old MqttClient from - * software.amazon.awssdk.crt.mqtt.MqttClient + * software.amazon.awssdk.crt.mqtt.MqttClient */ public WrapperMqttClientConnection(MqttClient mqttClient) { super(getMqttConnectionConfig()); @@ -42,17 +42,16 @@ public WrapperMqttClientConnection(MqttClient mqttClient) { } /* - * This is to initialize a valid MqttConnectionConfig which could be used - * in the WrapperMqttClientConnection + * This is to initialize a valid MqttConnectionConfig which could be used in the WrapperMqttClientConnection * * @return MqttConnectionConfig */ private static MqttConnectionConfig getMqttConnectionConfig() { try (EventLoopGroup eventLoopGroup = new EventLoopGroup(0); - HostResolver resolver = new HostResolver(eventLoopGroup); - ClientBootstrap clientBootstrap = new ClientBootstrap(eventLoopGroup, resolver); - software.amazon.awssdk.crt.mqtt.MqttClient oldMqttClient = new software.amazon.awssdk.crt.mqtt.MqttClient( - clientBootstrap)) { + HostResolver resolver = new HostResolver(eventLoopGroup); + ClientBootstrap clientBootstrap = new ClientBootstrap(eventLoopGroup, resolver); + software.amazon.awssdk.crt.mqtt.MqttClient oldMqttClient = + new software.amazon.awssdk.crt.mqtt.MqttClient(clientBootstrap)) { String fakeClientId = "fakeClientId"; String fakeEndpoint = "fakeEndpoint"; int fakePortNumber = 1; @@ -93,11 +92,11 @@ public CompletableFuture subscribe(String topic, QualityOfService qos) @Override public CompletableFuture publish(MqttMessage message, QualityOfService qos, boolean retain) { - String topic = message.getTopic(); - byte[] payload = message.getPayload(); - PublishRequest publishRequest = - PublishRequest.builder().topic(topic).retain(retain).payload(payload).qos(qos).build(); - return mqttClient.publish(publishRequest); + String topic = message.getTopic(); + byte[] payload = message.getPayload(); + PublishRequest publishRequest = + PublishRequest.builder().topic(topic).retain(retain).payload(payload).qos(qos).build(); + return mqttClient.publish(publishRequest); } @Override diff --git a/src/main/java/com/aws/greengrass/mqttclient/spool/Spool.java b/src/main/java/com/aws/greengrass/mqttclient/spool/Spool.java index 4fc52c9841..4ebf4e0383 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/spool/Spool.java +++ b/src/main/java/com/aws/greengrass/mqttclient/spool/Spool.java @@ -45,16 +45,13 @@ public class Spool { private final AtomicLong nextId = new AtomicLong(0); private final BlockingDeque queueOfMessageId = new LinkedBlockingDeque<>(); /** - * Flag to see if we need to check for QOS0 messages or not, when we attempt to remove QOS0 messages - * with removeMessagesWithQosZeromethod. - * removeMessagesWithQosZero is called to remove QOS0 messages from Queue either when we are offline - * or when we want to make space to accommodate a new incoming message. - * - It is set to true everytime a new message has been added to spooler queue. - * - If the flag is true, we will check the queue to look for QOS 0 messages - * when removeMessagesWithQosZero is called. - * - It is set back to false at the end of the removeMessagesWithQosZero method. - * - The flag remains false, if we know for sure that we removed all QOS0 messages due to being offline, or while - * trying to make space for a new message(and failed to do so) + * Flag to see if we need to check for QOS0 messages or not, when we attempt to remove QOS0 messages with + * removeMessagesWithQosZeromethod. removeMessagesWithQosZero is called to remove QOS0 messages from Queue either + * when we are offline or when we want to make space to accommodate a new incoming message. - It is set to true + * everytime a new message has been added to spooler queue. - If the flag is true, we will check the queue to look + * for QOS 0 messages when removeMessagesWithQosZero is called. - It is set back to false at the end of the + * removeMessagesWithQosZero method. - The flag remains false, if we know for sure that we removed all QOS0 messages + * due to being offline, or while trying to make space for a new message(and failed to do so) */ private final AtomicBoolean qos0MessageCheckRequired = new AtomicBoolean(false); private final AtomicLong curMessageQueueSizeInBytes = new AtomicLong(0); @@ -65,7 +62,7 @@ public class Spool { * Constructor. * * @param deviceConfiguration the device configuration - * @param kernel a kernel instance + * @param kernel a kernel instance */ public Spool(DeviceConfiguration deviceConfiguration, Kernel kernel) { inMemorySpooler = new InMemorySpool(); @@ -82,43 +79,45 @@ public Spool(DeviceConfiguration deviceConfiguration, Kernel kernel) { }); } - private void setSpoolerConfigFromDeviceConfig(Topics topics) { - SpoolerStorageType spoolStorageType = Coerce.toEnum(SpoolerStorageType.class, topics - .findOrDefault(DEFAULT_SPOOL_STORAGE_TYPE, SPOOL_STORAGE_TYPE_KEY)); - long spoolMaxMessageQueueSizeInBytes = Coerce.toLong(topics - .findOrDefault(DEFAULT_SPOOL_MAX_MESSAGE_QUEUE_SIZE_IN_BYTES, - SPOOL_MAX_SIZE_IN_BYTES_KEY)); - boolean spoolKeepQos0WhenOffline = Coerce.toBoolean(topics - .findOrDefault(DEFAULT_KEEP_Q0S_0_WHEN_OFFLINE, SPOOL_KEEP_QOS_0_WHEN_OFFLINE_KEY)); - String persistenceSpoolerServiceName = Coerce.toString(topics - .findOrDefault(DEFAULT_GG_PERSISTENCE_SPOOL_SERVICE_NAME, PERSISTENCE_SPOOL_SERVICE_NAME_KEY)); + SpoolerStorageType spoolStorageType = Coerce.toEnum(SpoolerStorageType.class, + topics.findOrDefault(DEFAULT_SPOOL_STORAGE_TYPE, SPOOL_STORAGE_TYPE_KEY)); + long spoolMaxMessageQueueSizeInBytes = Coerce.toLong( + topics.findOrDefault(DEFAULT_SPOOL_MAX_MESSAGE_QUEUE_SIZE_IN_BYTES, SPOOL_MAX_SIZE_IN_BYTES_KEY)); + boolean spoolKeepQos0WhenOffline = Coerce + .toBoolean(topics.findOrDefault(DEFAULT_KEEP_Q0S_0_WHEN_OFFLINE, SPOOL_KEEP_QOS_0_WHEN_OFFLINE_KEY)); + String persistenceSpoolerServiceName = Coerce.toString( + topics.findOrDefault(DEFAULT_GG_PERSISTENCE_SPOOL_SERVICE_NAME, PERSISTENCE_SPOOL_SERVICE_NAME_KEY)); - logger.atInfo().kv(SPOOL_STORAGE_TYPE_KEY, spoolStorageType) + logger.atInfo() + .kv(SPOOL_STORAGE_TYPE_KEY, spoolStorageType) .kv(SPOOL_MAX_SIZE_IN_BYTES_KEY, spoolMaxMessageQueueSizeInBytes) .kv(SPOOL_KEEP_QOS_0_WHEN_OFFLINE_KEY, spoolKeepQos0WhenOffline) .log("Spooler has been configured"); - this.config = SpoolerConfig.builder().storageType(spoolStorageType) + this.config = SpoolerConfig.builder() + .storageType(spoolStorageType) .spoolSizeInBytes(spoolMaxMessageQueueSizeInBytes) .keepQos0WhenOffline(spoolKeepQos0WhenOffline) - .persistenceSpoolServiceName(persistenceSpoolerServiceName).build(); + .persistenceSpoolServiceName(persistenceSpoolerServiceName) + .build(); } /** * create a spooler instance. * - * @return CloudMessageSpool spooler instance + * @return CloudMessageSpool spooler instance */ private CloudMessageSpool setupSpooler() { if (config.getStorageType() == SpoolerStorageType.Disk) { try { return getPersistenceSpoolGGService(); } catch (ServiceLoadException | IOException e) { - //log and use InMemorySpool + // log and use InMemorySpool logger.atWarn() .kv(PERSISTENCE_SPOOL_SERVICE_NAME_KEY, config.getPersistenceSpoolServiceName()) - .cause(e).log("Persistence spool set up failed, defaulting to InMemory Spooler"); + .cause(e) + .log("Persistence spool set up failed, defaulting to InMemory Spooler"); } } logger.atInfo().log("Memory Spooler has been set up"); @@ -131,8 +130,7 @@ private CloudMessageSpool setupSpooler() { * @return CloudMessageSpool instance * @throws ServiceLoadException thrown if the service cannot be located */ - private CloudMessageSpool getPersistenceSpoolGGService() - throws ServiceLoadException, IOException { + private CloudMessageSpool getPersistenceSpoolGGService() throws ServiceLoadException, IOException { GreengrassService locatedService = kernel.locate(config.getPersistenceSpoolServiceName()); if (locatedService instanceof CloudMessageSpool) { CloudMessageSpool persistenceSpool = (CloudMessageSpool) locatedService; @@ -142,7 +140,8 @@ private CloudMessageSpool getPersistenceSpoolGGService() } catch (SpoolerStoreException e) { logger.atWarn() .kv(PERSISTENCE_SPOOL_SERVICE_NAME_KEY, config.getPersistenceSpoolServiceName()) - .cause(e).log("Persistence spool queue sync was not completed, continuing with" + .cause(e) + .log("Persistence spool queue sync was not completed, continuing with" + " Persistent Spooler anyways"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -154,9 +153,7 @@ private CloudMessageSpool getPersistenceSpoolGGService() logger.atInfo().log("Persistent Spooler has been set up"); return persistenceSpool; } else { - throw new ServiceLoadException( - "The Greengrass service located was not an instance of CloudMessageSpool" - ); + throw new ServiceLoadException("The Greengrass service located was not an instance of CloudMessageSpool"); } } @@ -171,17 +168,17 @@ public void addId(long id) { /** * Spool the given PublishRequest. - *

- * If there is no room for the given PublishRequest, then QoS 0 PublishRequests will be deleted to make room. - * If there is still no room after deleting QoS 0 PublishRequests, then an exception will be thrown. + *

+ *

+ * If there is no room for the given PublishRequest, then QoS 0 PublishRequests will be deleted to make room. If + * there is still no room after deleting QoS 0 PublishRequests, then an exception will be thrown. * * @param request publish request * @return SpoolMessage spool message - * @throws InterruptedException result from the queue implementation + * @throws InterruptedException result from the queue implementation * @throws SpoolerStoreException if the message cannot be inserted into the message spool */ - public SpoolMessage addMessage(Publish request) throws InterruptedException, - SpoolerStoreException { + public SpoolMessage addMessage(Publish request) throws InterruptedException, SpoolerStoreException { try (LockScope ls = LockScope.lock(lock)) { queueCapacityCheck(request, true); long id = nextId.getAndIncrement(); @@ -224,9 +221,10 @@ public long popId() throws InterruptedException { /** * Get message from spooler, based on the given message ID. - *

- * Always try reading from InMemory spooler first as there might be messages put there due to fallback. - * If not, continue reading from the configured spooler (either "Disk" or "Memory"). + *

+ *

+ * Always try reading from InMemory spooler first as there might be messages put there due to fallback. If not, + * continue reading from the configured spooler (either "Disk" or "Memory"). * * @param messageId messageID for the messae * @return SpoolMessage spool message @@ -282,7 +280,10 @@ private void removeMessagesWithQosZero(boolean needToCheckCurSpoolerSize) { int qos = request.getQos().getValue(); if (qos == 0) { removeMessageById(id); - logger.atDebug().kv("id", id).kv("topic", request.getTopic()).kv("Qos", qos) + logger.atDebug() + .kv("id", id) + .kv("topic", request.getTopic()) + .kv("Qos", qos) .log("The spooler is configured to drop QoS 0 when offline. Dropping message now."); } } @@ -310,14 +311,13 @@ public SpoolerConfig getSpoolConfig() { } /** - * Extract message ids from the persistenceSpool plugin's disk database and insert the message - * ids into queueOfMessageId, this function is only used in Disk storage mode. If Sync fails midway, - * we continue anyway with that DiskSpooler. If we fail to get all Message IDs from Disk Spooler Database, - * we default to InMemory spooler. + * Extract message ids from the persistenceSpool plugin's disk database and insert the message ids into + * queueOfMessageId, this function is only used in Disk storage mode. If Sync fails midway, we continue anyway with + * that DiskSpooler. If we fail to get all Message IDs from Disk Spooler Database, we default to InMemory spooler. * - * @param diskQueueOfIds list of messageIds to sync + * @param diskQueueOfIds list of messageIds to sync * @param persistenceSpool instance of CloudMessageSpool - * @throws InterruptedException If interrupted + * @throws InterruptedException If interrupted * @throws SpoolerStoreException thrown if message too large or spooler capacity exceeded */ public void persistentQueueSync(Iterable diskQueueOfIds, CloudMessageSpool persistenceSpool) @@ -330,7 +330,7 @@ public void persistentQueueSync(Iterable diskQueueOfIds, CloudMessageSpool int queueOfMessageIdInitSize = queueOfMessageId.size(); for (long currentId : diskQueueOfIds) { numMessages++; - //Check for queue space and remove if necessary + // Check for queue space and remove if necessary SpoolMessage message = persistenceSpool.getMessageById(currentId); Publish request = message.getRequest(); queueCapacityCheck(request, false); @@ -350,10 +350,9 @@ public void persistentQueueSync(Iterable diskQueueOfIds, CloudMessageSpool nextId.set(Math.max(nextId.get(), highestId + 1)); } - /** - * This method checks if the max size of the queue will be reached if we add the current request. - * (This function is extracted from addMessage to avoid unnecessary code duplication) + * This method checks if the max size of the queue will be reached if we add the current request. (This function is + * extracted from addMessage to avoid unnecessary code duplication) * * @param request : PublishRequest instance * @throws SpoolerStoreException : thrown if message too large or spooler capacity exceeded diff --git a/src/main/java/com/aws/greengrass/mqttclient/spool/SpoolMessage.java b/src/main/java/com/aws/greengrass/mqttclient/spool/SpoolMessage.java index 35edc9016c..3737b72ae3 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/spool/SpoolMessage.java +++ b/src/main/java/com/aws/greengrass/mqttclient/spool/SpoolMessage.java @@ -16,7 +16,8 @@ @Getter public class SpoolMessage { private long id; - @Builder.Default @Setter + @Builder.Default + @Setter private AtomicInteger retried = new AtomicInteger(0); private Publish request; } diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/PubAck.java b/src/main/java/com/aws/greengrass/mqttclient/v5/PubAck.java index 9df1dd1274..811370350e 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/PubAck.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/PubAck.java @@ -25,8 +25,11 @@ public class PubAck { */ public static PubAck fromCrtPubAck(PubAckPacket p) { return new PubAck(p.getReasonCode() == null ? 0 : p.getReasonCode().getValue(), p.getReasonString(), - p.getUserProperties() == null ? null - : p.getUserProperties().stream().map(u -> new UserProperty(u.key, u.value)) + p.getUserProperties() == null + ? null + : p.getUserProperties() + .stream() + .map(u -> new UserProperty(u.key, u.value)) .collect(Collectors.toList())); } diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/Publish.java b/src/main/java/com/aws/greengrass/mqttclient/v5/Publish.java index e5b1e8dc4c..2b69bdcc8a 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/Publish.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/Publish.java @@ -13,14 +13,18 @@ import java.util.List; import java.util.stream.Collectors; -@SuppressWarnings({"PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal", "PMD.ExcessiveParameterList"}) +@SuppressWarnings({ + "PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal", "PMD.ExcessiveParameterList" +}) @Value public class Publish { - @NonNull String topic; - @NonNull QOS qos; + @NonNull + String topic; + @NonNull + QOS qos; /** - * Retain the message in the cloud MQTT broker (only last message with retain is actually kept). - * Subscribers will immediately receive the last retained message when they first subscribe. + * Retain the message in the cloud MQTT broker (only last message with retain is actually kept). Subscribers will + * immediately receive the last retained message when they first subscribe. */ boolean retain; byte[] payload; @@ -36,8 +40,8 @@ public class Publish { @Builder protected Publish(String topic, QOS qos, boolean retain, byte[] payload, PayloadFormatIndicator payloadFormat, - Long messageExpiryIntervalSeconds, String responseTopic, byte[] correlationData, - String contentType, List userProperties, List subscriptionIdentifiers) { + Long messageExpiryIntervalSeconds, String responseTopic, byte[] correlationData, String contentType, + List userProperties, List subscriptionIdentifiers) { // Intern the string to deduplicate topic strings in memory this.topic = topic.intern(); if (qos == null) { @@ -73,15 +77,20 @@ public static Publish fromCrtPublishPacket(PublishPacket m) { .qos(m.getQOS() == null ? QOS.AT_MOST_ONCE : QOS.fromInt(m.getQOS().getValue())) .retain(m.getRetain()) .topic(m.getTopic()) - .payloadFormat(m.getPayloadFormat() == null ? null : - PayloadFormatIndicator.fromInt(m.getPayloadFormat().getValue())) + .payloadFormat(m.getPayloadFormat() == null + ? null + : PayloadFormatIndicator.fromInt(m.getPayloadFormat().getValue())) .messageExpiryIntervalSeconds(m.getMessageExpiryIntervalSeconds()) .responseTopic(m.getResponseTopic()) .correlationData(m.getCorrelationData()) .subscriptionIdentifiers(m.getSubscriptionIdentifiers()) .contentType(m.getContentType()) - .userProperties(m.getUserProperties() == null ? null : m.getUserProperties().stream() - .map(u -> new UserProperty(u.key, u.value)).collect(Collectors.toList())) + .userProperties(m.getUserProperties() == null + ? null + : m.getUserProperties() + .stream() + .map(u -> new UserProperty(u.key, u.value)) + .collect(Collectors.toList())) .build(); } @@ -91,20 +100,23 @@ public static Publish fromCrtPublishPacket(PublishPacket m) { * @return PublishPacket */ public PublishPacket toCrtPublishPacket() { - return new PublishPacket.PublishPacketBuilder() - .withPayload(payload) + return new PublishPacket.PublishPacketBuilder().withPayload(payload) .withQOS(software.amazon.awssdk.crt.mqtt5.QOS.getEnumValueFromInteger(qos.getValue())) .withRetain(retain) .withTopic(topic) - .withPayloadFormat(payloadFormat == null ? null : PublishPacket.PayloadFormatIndicator - .getEnumValueFromInteger(payloadFormat.getValue())) + .withPayloadFormat(payloadFormat == null + ? null + : PublishPacket.PayloadFormatIndicator.getEnumValueFromInteger(payloadFormat.getValue())) .withMessageExpiryIntervalSeconds(messageExpiryIntervalSeconds) .withResponseTopic(responseTopic) .withCorrelationData(correlationData) .withContentType(contentType) - .withUserProperties(userProperties == null ? null : userProperties.stream() - .map(u -> new software.amazon.awssdk.crt.mqtt5.packets.UserProperty(u.getKey(), u.getValue())) - .collect(Collectors.toList())) + .withUserProperties(userProperties == null + ? null + : userProperties.stream() + .map(u -> new software.amazon.awssdk.crt.mqtt5.packets.UserProperty(u.getKey(), + u.getValue())) + .collect(Collectors.toList())) .build(); } @@ -127,6 +139,7 @@ public enum PayloadFormatIndicator { /** * Get the integer value. + * * @return The native enum integer value associated with this Java enum value. */ public int getValue() { @@ -142,13 +155,12 @@ public int getValue() { */ public static PayloadFormatIndicator fromInt(int i) { switch (i) { - case 0: - return BYTES; - case 1: - return UTF8; - default: - throw new IllegalArgumentException( - String.format("Invalid value for payload format indicator %d", i)); + case 0: + return BYTES; + case 1: + return UTF8; + default: + throw new IllegalArgumentException(String.format("Invalid value for payload format indicator %d", i)); } } } diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/QOS.java b/src/main/java/com/aws/greengrass/mqttclient/v5/QOS.java index fffc3cf771..ba69841b5d 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/QOS.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/QOS.java @@ -37,19 +37,20 @@ public enum QOS { */ public static QOS fromInt(int value) { switch (value) { - case 0: - return QOS.AT_MOST_ONCE; - case 1: - return QOS.AT_LEAST_ONCE; - case 2: - return QOS.EXACTLY_ONCE; - default: - throw new IllegalArgumentException(String.format("Value %d is not a valid QOS", value)); + case 0: + return QOS.AT_MOST_ONCE; + case 1: + return QOS.AT_LEAST_ONCE; + case 2: + return QOS.EXACTLY_ONCE; + default: + throw new IllegalArgumentException(String.format("Value %d is not a valid QOS", value)); } } /** * Get the integer value. + * * @return The native enum integer value associated with this Java enum value */ public int getValue() { diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/Subscribe.java b/src/main/java/com/aws/greengrass/mqttclient/v5/Subscribe.java index b897225aad..a59c6b1c82 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/Subscribe.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/Subscribe.java @@ -16,9 +16,11 @@ @Builder @Value public class Subscribe { - @NonNull String topic; + @NonNull + String topic; @Builder.Default - @NonNull QOS qos = QOS.AT_LEAST_ONCE; + @NonNull + QOS qos = QOS.AT_LEAST_ONCE; @Builder.Default boolean noLocal = false; @@ -39,10 +41,11 @@ public class Subscribe { */ public SubscribePacket toCrtSubscribePacket() { return new SubscribePacket.SubscribePacketBuilder().withSubscription(topic, - software.amazon.awssdk.crt.mqtt5.QOS.getEnumValueFromInteger(qos.getValue()), noLocal, - retainAsPublished, retainHandlingType == null ? null - : SubscribePacket.RetainHandlingType - .getEnumValueFromInteger(retainHandlingType.getValue())) + software.amazon.awssdk.crt.mqtt5.QOS.getEnumValueFromInteger(qos.getValue()), noLocal, + retainAsPublished, + retainHandlingType == null + ? null + : SubscribePacket.RetainHandlingType.getEnumValueFromInteger(retainHandlingType.getValue())) .build(); } diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/SubscribeResponse.java b/src/main/java/com/aws/greengrass/mqttclient/v5/SubscribeResponse.java index 82d8914b18..54f632df6e 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/SubscribeResponse.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/SubscribeResponse.java @@ -31,11 +31,20 @@ public class SubscribeResponse { * @return SubscribeResponse */ public static SubscribeResponse fromCrtSubAck(SubAckPacket r) { - return new SubscribeResponse(r.getReasonString(), r.getReasonCodes() == null ? 0 - : r.getReasonCodes().stream().map(SubAckPacket.SubAckReasonCode::getValue).max(Integer::compareTo) - .orElse(0), r.getUserProperties() == null ? null - : r.getUserProperties().stream().map((u) -> new UserProperty(u.key, u.value)) - .collect(Collectors.toList())); + return new SubscribeResponse(r.getReasonString(), + r.getReasonCodes() == null + ? 0 + : r.getReasonCodes() + .stream() + .map(SubAckPacket.SubAckReasonCode::getValue) + .max(Integer::compareTo) + .orElse(0), + r.getUserProperties() == null + ? null + : r.getUserProperties() + .stream() + .map((u) -> new UserProperty(u.key, u.value)) + .collect(Collectors.toList())); } public boolean isSuccessful() { diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/Unsubscribe.java b/src/main/java/com/aws/greengrass/mqttclient/v5/Unsubscribe.java index dbb3e7b038..33bec966f1 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/Unsubscribe.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/Unsubscribe.java @@ -14,7 +14,8 @@ @Builder @Value public class Unsubscribe { - @NonNull String topic; + @NonNull + String topic; // The callback provided in Subscribe which should be removed from the MQTT client's callback mapping. Consumer subscriptionCallback; } diff --git a/src/main/java/com/aws/greengrass/mqttclient/v5/UnsubscribeResponse.java b/src/main/java/com/aws/greengrass/mqttclient/v5/UnsubscribeResponse.java index 64e0eea4dd..964d388182 100644 --- a/src/main/java/com/aws/greengrass/mqttclient/v5/UnsubscribeResponse.java +++ b/src/main/java/com/aws/greengrass/mqttclient/v5/UnsubscribeResponse.java @@ -30,10 +30,18 @@ public class UnsubscribeResponse { * @return UnsubscribeResponse */ public static UnsubscribeResponse fromCrtUnsubAck(UnsubAckPacket r) { - return new UnsubscribeResponse(r.getReasonString(), r.getReasonCodes() == null ? null - : r.getReasonCodes().stream().map(UnsubAckPacket.UnsubAckReasonCode::getValue) - .collect(Collectors.toList()), r.getUserProperties() == null ? null - : r.getUserProperties().stream().map(u -> new UserProperty(u.key, u.value)) - .collect(Collectors.toList())); + return new UnsubscribeResponse(r.getReasonString(), + r.getReasonCodes() == null + ? null + : r.getReasonCodes() + .stream() + .map(UnsubAckPacket.UnsubAckReasonCode::getValue) + .collect(Collectors.toList()), + r.getUserProperties() == null + ? null + : r.getUserProperties() + .stream() + .map(u -> new UserProperty(u.key, u.value)) + .collect(Collectors.toList())); } } diff --git a/src/main/java/com/aws/greengrass/network/HttpClientProvider.java b/src/main/java/com/aws/greengrass/network/HttpClientProvider.java index a365b1c599..c872f7d0ec 100644 --- a/src/main/java/com/aws/greengrass/network/HttpClientProvider.java +++ b/src/main/java/com/aws/greengrass/network/HttpClientProvider.java @@ -8,7 +8,6 @@ import com.aws.greengrass.util.ProxyUtils; import software.amazon.awssdk.http.SdkHttpClient; - public class HttpClientProvider { /** * Provides a SdkHttpClient with Proxy configuration if it is available or a regular ApacheHttpClient. Invoker diff --git a/src/main/java/com/aws/greengrass/provisioning/ProvisioningConfigUpdateHelper.java b/src/main/java/com/aws/greengrass/provisioning/ProvisioningConfigUpdateHelper.java index 45956bfc58..c2a2917cbe 100644 --- a/src/main/java/com/aws/greengrass/provisioning/ProvisioningConfigUpdateHelper.java +++ b/src/main/java/com/aws/greengrass/provisioning/ProvisioningConfigUpdateHelper.java @@ -30,11 +30,12 @@ public class ProvisioningConfigUpdateHelper { /** * Updates the system configuration values in kernel config as per the given {@link SystemConfiguration}. + * * @param systemConfiguration {@link SystemConfiguration} * @param updateBehavior Update behavior indicating either merge or replace */ public void updateSystemConfiguration(@NonNull SystemConfiguration systemConfiguration, - @NonNull UpdateBehaviorTree.UpdateBehavior updateBehavior) { + @NonNull UpdateBehaviorTree.UpdateBehavior updateBehavior) { Map updateMap = new HashMap<>(); if (systemConfiguration.getCertificateFilePath() != null) { updateMap.put(DeviceConfiguration.DEVICE_PARAM_CERTIFICATE_FILE_PATH, @@ -55,23 +56,24 @@ public void updateSystemConfiguration(@NonNull SystemConfiguration systemConfigu /** * Updates the nucleus configuration value in kernel config as per the given {@link NucleusConfiguration}. + * * @param nucleusConfiguration {@link NucleusConfiguration} * @param updateBehavior Update behavior indicating either merge or replace */ public void updateNucleusConfiguration(@NonNull NucleusConfiguration nucleusConfiguration, - @NonNull UpdateBehaviorTree.UpdateBehavior updateBehavior) { + @NonNull UpdateBehaviorTree.UpdateBehavior updateBehavior) { Map updateMap = new HashMap<>(); if (nucleusConfiguration.getAwsRegion() != null) { updateMap.put(DeviceConfiguration.DEVICE_PARAM_AWS_REGION, nucleusConfiguration.getAwsRegion()); } if (nucleusConfiguration.getIotCredentialsEndpoint() != null) { - updateMap.put(DeviceConfiguration.DEVICE_PARAM_IOT_CRED_ENDPOINT, nucleusConfiguration - .getIotCredentialsEndpoint()); + updateMap.put(DeviceConfiguration.DEVICE_PARAM_IOT_CRED_ENDPOINT, + nucleusConfiguration.getIotCredentialsEndpoint()); } if (nucleusConfiguration.getIotDataEndpoint() != null) { - updateMap.put(DeviceConfiguration.DEVICE_PARAM_IOT_DATA_ENDPOINT, nucleusConfiguration - .getIotDataEndpoint()); + updateMap.put(DeviceConfiguration.DEVICE_PARAM_IOT_DATA_ENDPOINT, + nucleusConfiguration.getIotDataEndpoint()); } if (nucleusConfiguration.getIotRoleAlias() != null) { updateMap.put(DeviceConfiguration.IOT_ROLE_ALIAS_TOPIC, nucleusConfiguration.getIotRoleAlias()); @@ -79,6 +81,6 @@ public void updateNucleusConfiguration(@NonNull NucleusConfiguration nucleusConf String nucleusComponentName = kernel.getContext().get(DeviceConfiguration.class).getNucleusComponentName(); Topics nucleusConfig = kernel.getConfig() .lookupTopics(SERVICES_NAMESPACE_TOPIC, nucleusComponentName, CONFIGURATION_CONFIG_KEY); - nucleusConfig.updateFromMap(updateMap, new UpdateBehaviorTree(updateBehavior, System.currentTimeMillis())); + nucleusConfig.updateFromMap(updateMap, new UpdateBehaviorTree(updateBehavior, System.currentTimeMillis())); } } diff --git a/src/main/java/com/aws/greengrass/security/SecurityService.java b/src/main/java/com/aws/greengrass/security/SecurityService.java index 0176da7304..4acea56c8a 100644 --- a/src/main/java/com/aws/greengrass/security/SecurityService.java +++ b/src/main/java/com/aws/greengrass/security/SecurityService.java @@ -51,15 +51,20 @@ public final class SecurityService { // retry 3 times with exponential backoff, start with 200ms, // if service still not available, pop exception to the caller private static final RetryUtils.RetryConfig GET_KEY_MANAGERS_RETRY_CONFIG = RetryUtils.RetryConfig.builder() - .initialRetryInterval(Duration.ofSeconds(5)).maxAttempt(Integer.MAX_VALUE) + .initialRetryInterval(Duration.ofSeconds(5)) + .maxAttempt(Integer.MAX_VALUE) .maxRetryInterval(Duration.ofSeconds(30)) - .retryableExceptions(Collections.singletonList(ServiceUnavailableException.class)).build(); + .retryableExceptions(Collections.singletonList(ServiceUnavailableException.class)) + .build(); // retry 4 times with exponential backoff, start with 300ms, // if service still not available, pop exception to the caller private static final RetryUtils.RetryConfig GET_MQTT_CONNECTION_BUILDER_RETRY_CONFIG = - RetryUtils.RetryConfig.builder().initialRetryInterval(Duration.ofMillis(300)).maxAttempt(4) - .retryableExceptions(Collections.singletonList(ServiceUnavailableException.class)).build(); + RetryUtils.RetryConfig.builder() + .initialRetryInterval(Duration.ofMillis(300)) + .maxAttempt(4) + .retryableExceptions(Collections.singletonList(ServiceUnavailableException.class)) + .build(); @Getter(AccessLevel.PACKAGE) private final ConcurrentMap cryptoKeyProviderMap = new ConcurrentHashMap<>(); @@ -70,6 +75,7 @@ public final class SecurityService { /** * Constructor of security service. + * * @param deviceConfiguration device configuration */ @Inject @@ -97,8 +103,8 @@ public void registerCryptoKeyProvider(CryptoKeySpi keyProvider) throws ServicePr logger.atInfo().kv(KEY_TYPE, keyType).log("Register crypto key service provider"); CryptoKeySpi provider = cryptoKeyProviderMap.computeIfAbsent(keyType, k -> keyProvider); if (!provider.equals(keyProvider)) { - throw new ServiceProviderConflictException(String.format("Key type %s crypto key provider is registered", - keyType)); + throw new ServiceProviderConflictException( + String.format("Key type %s crypto key provider is registered", keyType)); } } @@ -113,8 +119,8 @@ public void registerMqttConnectionProvider(MqttConnectionSpi mqttProvider) throw logger.atInfo().kv(KEY_TYPE, keyType).log("Register MQTT connection security provider"); MqttConnectionSpi provider = mqttConnectionProviderMap.computeIfAbsent(keyType, k -> mqttProvider); if (!provider.equals(mqttProvider)) { - throw new ServiceProviderConflictException(String.format("Key type %s mqtt connection provider is " - + "registered", keyType)); + throw new ServiceProviderConflictException( + String.format("Key type %s mqtt connection provider is " + "registered", keyType)); } } @@ -127,8 +133,9 @@ public void deregisterCryptoKeyProvider(CryptoKeySpi keyProvider) { CaseInsensitiveString keyType = new CaseInsensitiveString(keyProvider.supportedKeyType()); boolean removed = cryptoKeyProviderMap.remove(keyType, keyProvider); if (!removed) { - logger.atInfo().kv(KEY_TYPE, keyType).log("Crypto key service provider is either already removed or " - + "unregistered"); + logger.atInfo() + .kv(KEY_TYPE, keyType) + .log("Crypto key service provider is either already removed or " + "unregistered"); } } @@ -141,8 +148,9 @@ public void deregisterMqttConnectionProvider(MqttConnectionSpi mqttProvider) { CaseInsensitiveString keyType = new CaseInsensitiveString(mqttProvider.supportedKeyType()); boolean removed = mqttConnectionProviderMap.remove(keyType, mqttProvider); if (!removed) { - logger.atInfo().kv(KEY_TYPE, keyType).log("Mqtt connection provider is either already removed or " - + "unregistered"); + logger.atInfo() + .kv(KEY_TYPE, keyType) + .log("Mqtt connection provider is either already removed or " + "unregistered"); } } @@ -157,8 +165,7 @@ public void deregisterMqttConnectionProvider(MqttConnectionSpi mqttProvider) { */ public KeyManager[] getKeyManagers(URI privateKeyUri, URI certificateUri) throws ServiceUnavailableException, KeyLoadingException { - logger.atTrace().kv(KEY_URI, privateKeyUri).kv(CERT_URI, certificateUri) - .log("Get key managers by key URI"); + logger.atTrace().kv(KEY_URI, privateKeyUri).kv(CERT_URI, certificateUri).log("Get key managers by key URI"); CryptoKeySpi provider = selectCryptoKeyProvider(privateKeyUri); return provider.getKeyManagers(privateKeyUri, certificateUri); } @@ -216,8 +223,8 @@ private MqttConnectionSpi selectMqttConnectionProvider(URI uri) throws ServiceUn CaseInsensitiveString keyType = new CaseInsensitiveString(uri.getScheme()); MqttConnectionSpi provider = mqttConnectionProviderMap.get(keyType); if (provider == null) { - throw new ServiceUnavailableException(String.format("Mqtt connection provider for %s is unavailable", - keyType)); + throw new ServiceUnavailableException( + String.format("Mqtt connection provider for %s is unavailable", keyType)); } return provider; } @@ -255,7 +262,9 @@ public static URI uriFromPossibleFileURIString(String path) { * @return key managers * @throws TLSAuthException if any error happens */ - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" + }) public KeyManager[] getDeviceIdentityKeyManagers() throws TLSAuthException { URI privateKey = getDeviceIdentityPrivateKeyURI(); URI certPath = getDeviceIdentityCertificateURI(); @@ -276,14 +285,15 @@ public KeyManager[] getDeviceIdentityKeyManagers() throws TLSAuthException { * @return AwsIotMqttConnectionBuilder that build mqtt client * @throws MqttConnectionProviderException if mqtt connection provider fails to create builder */ - @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace"}) + @SuppressWarnings({ + "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" + }) public AwsIotMqttConnectionBuilder getDeviceIdentityMqttConnectionBuilder() throws MqttConnectionProviderException { URI privateKey = getDeviceIdentityPrivateKeyURI(); URI certPath = getDeviceIdentityCertificateURI(); try { return RetryUtils.runWithRetry(GET_MQTT_CONNECTION_BUILDER_RETRY_CONFIG, - () -> getMqttConnectionBuilder(privateKey, certPath), - "get-mqtt-connection-builder", logger); + () -> getMqttConnectionBuilder(privateKey, certPath), "get-mqtt-connection-builder", logger); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new MqttConnectionProviderException("Get mqtt connection builder interrupted", e); @@ -297,8 +307,7 @@ static class DefaultCryptoKeyProvider implements CryptoKeySpi, MqttConnectionSpi @SuppressWarnings("PMD.PrematureDeclaration") @Override - public KeyManager[] getKeyManagers(URI privateKeyUri, URI certificateUri) - throws KeyLoadingException { + public KeyManager[] getKeyManagers(URI privateKeyUri, URI certificateUri) throws KeyLoadingException { KeyPair keyPair = getKeyPair(privateKeyUri, certificateUri); if (!isUriSupportedKeyType(certificateUri)) { @@ -324,8 +333,7 @@ public KeyManager[] getKeyManagers(URI privateKeyUri, URI certificateUri) } @Override - public KeyPair getKeyPair(URI privateKeyUri, URI certificateUri) - throws KeyLoadingException { + public KeyPair getKeyPair(URI privateKeyUri, URI certificateUri) throws KeyLoadingException { if (!isUriSupportedKeyType(privateKeyUri)) { throw new KeyLoadingException(String.format("Only support %s type private key", supportedKeyType())); } @@ -340,12 +348,12 @@ public KeyPair getKeyPair(URI privateKeyUri, URI certificateUri) public AwsIotMqttConnectionBuilder getMqttConnectionBuilder(URI privateKeyUri, URI certificateUri) throws MqttConnectionProviderException { if (!isUriSupportedKeyType(privateKeyUri)) { - throw new MqttConnectionProviderException(String.format("Only support %s type private key", - supportedKeyType())); + throw new MqttConnectionProviderException( + String.format("Only support %s type private key", supportedKeyType())); } if (!isUriSupportedKeyType(certificateUri)) { - throw new MqttConnectionProviderException(String.format("Only support %s type certificate", - supportedKeyType())); + throw new MqttConnectionProviderException( + String.format("Only support %s type certificate", supportedKeyType())); } return AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(Paths.get(certificateUri).toString(), Paths.get(privateKeyUri).toString()); diff --git a/src/main/java/com/aws/greengrass/status/FleetStatusService.java b/src/main/java/com/aws/greengrass/status/FleetStatusService.java index 713961fc64..0d43617eb9 100644 --- a/src/main/java/com/aws/greengrass/status/FleetStatusService.java +++ b/src/main/java/com/aws/greengrass/status/FleetStatusService.java @@ -90,8 +90,8 @@ public class FleetStatusService extends GreengrassService { static final String FLEET_STATUS_SEQUENCE_NUMBER_TOPIC = "sequenceNumber"; static final String FLEET_STATUS_LAST_PERIODIC_UPDATE_TIME_TOPIC = "lastPeriodicUpdateTime"; private static final int MAX_PAYLOAD_LENGTH_BYTES = 128_000; - public static final String DEVICE_OFFLINE_MESSAGE = "Device not configured to talk to AWS IoT cloud. " - + "FleetStatusService is offline"; + public static final String DEVICE_OFFLINE_MESSAGE = + "Device not configured to talk to AWS IoT cloud. " + "FleetStatusService is offline"; // setter is only used for testing @Setter @@ -108,7 +108,7 @@ public class FleetStatusService extends GreengrassService { private final MqttChunkedPayloadPublisher publisher; private final DeploymentStatusKeeper deploymentStatusKeeper; private final KernelLifecycle kernelLifecycle; - //For testing + // For testing @Getter private final AtomicBoolean isConnected = new AtomicBoolean(true); private final AtomicBoolean isFSSSetupComplete = new AtomicBoolean(false); @@ -151,8 +151,11 @@ public void onConnectionResumed(boolean sessionPresent) { if (newPeriodicUpdateIntervalSec < DEFAULT_PERIODIC_PUBLISH_INTERVAL_SEC) { return; } - this.periodicPublishIntervalSec = TestFeatureParameters.retrieveWithDefault(Double.class, - FLEET_STATUS_TEST_PERIODIC_UPDATE_INTERVAL_SEC, newPeriodicUpdateIntervalSec).intValue(); + this.periodicPublishIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, FLEET_STATUS_TEST_PERIODIC_UPDATE_INTERVAL_SEC, + newPeriodicUpdateIntervalSec) + .intValue(); if (periodicUpdateFuture != null) { schedulePeriodicFleetStatusDataUpdate(false); } @@ -162,20 +165,19 @@ public void onConnectionResumed(boolean sessionPresent) { /** * Constructor for FleetStatusService. * - * @param topics root Configuration topic for this service - * @param mqttClient {@link MqttClient} + * @param topics root Configuration topic for this service + * @param mqttClient {@link MqttClient} * @param deploymentStatusKeeper {@link DeploymentStatusKeeper} - * @param kernel {@link Kernel} - * @param deviceConfiguration {@link DeviceConfiguration} - * @param platformResolver {@link PlatformResolver} - * @param kernelLifecycle {@link KernelLifecycle} - * @param ses {@link ScheduledExecutorService} + * @param kernel {@link Kernel} + * @param deviceConfiguration {@link DeviceConfiguration} + * @param platformResolver {@link PlatformResolver} + * @param kernelLifecycle {@link KernelLifecycle} + * @param ses {@link ScheduledExecutorService} */ @Inject public FleetStatusService(Topics topics, MqttClient mqttClient, DeploymentStatusKeeper deploymentStatusKeeper, - Kernel kernel, DeviceConfiguration deviceConfiguration, - PlatformResolver platformResolver, KernelLifecycle kernelLifecycle, - ScheduledExecutorService ses) { + Kernel kernel, DeviceConfiguration deviceConfiguration, PlatformResolver platformResolver, + KernelLifecycle kernelLifecycle, ScheduledExecutorService ses) { this(topics, mqttClient, deploymentStatusKeeper, kernel, deviceConfiguration, platformResolver, kernelLifecycle, ses, DEFAULT_PERIODIC_PUBLISH_INTERVAL_SEC); } @@ -183,21 +185,19 @@ public FleetStatusService(Topics topics, MqttClient mqttClient, DeploymentStatus /** * Constructor for FleetStatusService. * - * @param topics root Configuration topic for this service - * @param mqttClient {@link MqttClient} - * @param deploymentStatusKeeper {@link DeploymentStatusKeeper} - * @param kernel {@link Kernel} - * @param deviceConfiguration {@link DeviceConfiguration} - * @param platformResolver {@link PlatformResolver} - * @param kernelLifecycle {@link KernelLifecycle} - * @param ses {@link ScheduledExecutorService} - * @param periodicPublishIntervalSec interval for cadence based status update. + * @param topics root Configuration topic for this service + * @param mqttClient {@link MqttClient} + * @param deploymentStatusKeeper {@link DeploymentStatusKeeper} + * @param kernel {@link Kernel} + * @param deviceConfiguration {@link DeviceConfiguration} + * @param platformResolver {@link PlatformResolver} + * @param kernelLifecycle {@link KernelLifecycle} + * @param ses {@link ScheduledExecutorService} + * @param periodicPublishIntervalSec interval for cadence based status update. */ public FleetStatusService(Topics topics, MqttClient mqttClient, DeploymentStatusKeeper deploymentStatusKeeper, - Kernel kernel, DeviceConfiguration deviceConfiguration, - PlatformResolver platformResolver, KernelLifecycle kernelLifecycle, - ScheduledExecutorService ses, - int periodicPublishIntervalSec) { + Kernel kernel, DeviceConfiguration deviceConfiguration, PlatformResolver platformResolver, + KernelLifecycle kernelLifecycle, ScheduledExecutorService ses, int periodicPublishIntervalSec) { super(topics); this.mqttClient = mqttClient; this.deploymentStatusKeeper = deploymentStatusKeeper; @@ -207,8 +207,11 @@ public FleetStatusService(Topics topics, MqttClient mqttClient, DeploymentStatus this.publisher = new MqttChunkedPayloadPublisher<>(this.mqttClient); this.architecture = platformResolver.getCurrentPlatform() .getOrDefault(PlatformResolver.ARCHITECTURE_KEY, PlatformResolver.UNKNOWN_KEYWORD); - this.periodicPublishIntervalSec = TestFeatureParameters.retrieveWithDefault(Double.class, - FLEET_STATUS_TEST_PERIODIC_UPDATE_INTERVAL_SEC, periodicPublishIntervalSec).intValue(); + this.periodicPublishIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, FLEET_STATUS_TEST_PERIODIC_UPDATE_INTERVAL_SEC, + periodicPublishIntervalSec) + .intValue(); this.publisher.setMaxPayloadLengthBytes(MAX_PAYLOAD_LENGTH_BYTES); this.platform = platformResolver.getCurrentPlatform() .getOrDefault(PlatformResolver.OS_KEY, PlatformResolver.UNKNOWN_KEYWORD); @@ -220,7 +223,7 @@ public FleetStatusService(Topics topics, MqttClient mqttClient, DeploymentStatus this.mqttClient.addToCallbackEvents(callbacks); TestFeatureParameters.registerHandlerCallback(this.getName(), this::handleTestFeatureParametersHandlerChange); - //populating services when kernel starts up + // populating services when kernel starts up Instant now = Instant.now(); this.kernel.orderedDependencies().forEach(greengrassService -> { serviceFssTracksMap.put(greengrassService, now); @@ -254,7 +257,8 @@ private void setUpFSS() throws DeviceConfigurationException { if (isFSSSetupComplete.compareAndSet(false, true)) { Topics configurationTopics = deviceConfiguration.getStatusConfigurationTopics(); configurationTopics.lookup(FLEET_STATUS_PERIODIC_PUBLISH_INTERVAL_SEC) - .dflt(DEFAULT_PERIODIC_PUBLISH_INTERVAL_SEC).subscribe(publishIntervalSubscriber); + .dflt(DEFAULT_PERIODIC_PUBLISH_INTERVAL_SEC) + .subscribe(publishIntervalSubscriber); config.getContext().addGlobalStateChangeListener(handleServiceStateChange); @@ -270,8 +274,11 @@ private void setUpFSS() throws DeviceConfigurationException { @SuppressWarnings("PMD.UnusedFormalParameter") private void handleTestFeatureParametersHandlerChange(Boolean isDefault) { - this.periodicPublishIntervalSec = TestFeatureParameters.retrieveWithDefault(Double.class, - FLEET_STATUS_TEST_PERIODIC_UPDATE_INTERVAL_SEC, this.periodicPublishIntervalSec).intValue(); + this.periodicPublishIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, FLEET_STATUS_TEST_PERIODIC_UPDATE_INTERVAL_SEC, + this.periodicPublishIntervalSec) + .intValue(); if (periodicUpdateFuture != null) { schedulePeriodicFleetStatusDataUpdate(false); } @@ -289,7 +296,7 @@ private void updateThingNameAndPublishTopic(String newThingName) { * Schedule cadence based periodic updates for fleet status. * * @param isDuringConnectionResumed boolean to indicate if the cadence based update is being rescheduled after - * connection resumed. + * connection resumed. */ public void schedulePeriodicFleetStatusDataUpdate(boolean isDuringConnectionResumed) { // If the last periodic update was missed, update the fleet status service for all running services. @@ -315,13 +322,12 @@ public void schedulePeriodicFleetStatusDataUpdate(boolean isDuringConnectionResu // Add some jitter as an initial delay. If the fleet has a lot of devices associated to it, // we don't want all the devices to send the periodic update for fleet statuses at the same time. long initialDelay = RandomUtils.nextLong(0, maxInitialDelay + 1); - this.periodicUpdateFuture = ses.scheduleWithFixedDelay(this::updatePeriodicFleetStatusData, - initialDelay, periodicPublishIntervalSec, TimeUnit.SECONDS); + this.periodicUpdateFuture = ses.scheduleWithFixedDelay(this::updatePeriodicFleetStatusData, initialDelay, + periodicPublishIntervalSec, TimeUnit.SECONDS); } @SuppressWarnings("PMD.UnusedFormalParameter") - private void handleServiceStateChange(GreengrassService greengrassService, State oldState, - State newState) { + private void handleServiceStateChange(GreengrassService greengrassService, State oldState, State newState) { try (LockScope ls = LockScope.lock(serviceSetLock)) { updatedGreengrassServiceSet.add(greengrassService); } @@ -333,8 +339,7 @@ private void handleServiceStateChange(GreengrassService greengrassService, State if (isDeploymentInProgress.get()) { Set erroredComponent = new HashSet<>(); erroredComponent.add(greengrassService); - uploadFleetStatusServiceData(erroredComponent, overallStatus, null, - Trigger.COMPONENT_STATUS_CHANGE); + uploadFleetStatusServiceData(erroredComponent, overallStatus, null, Trigger.COMPONENT_STATUS_CHANGE); return; } // Report status of other components, in case recovery duration takes too long and other components need @@ -351,8 +356,8 @@ private void handleServiceStateChange(GreengrassService greengrassService, State // update the fleet status as UNHEALTHY. if (newState.equals(State.BROKEN)) { try (LockScope ls = LockScope.lock(serviceSetLock)) { - uploadFleetStatusServiceData(updatedGreengrassServiceSet, OverallStatus.UNHEALTHY, - null, Trigger.COMPONENT_STATUS_CHANGE); + uploadFleetStatusServiceData(updatedGreengrassServiceSet, OverallStatus.UNHEALTHY, null, + Trigger.COMPONENT_STATUS_CHANGE); } } // If kernel is not shutting down and all components reached terminal states, @@ -401,16 +406,18 @@ public void triggerFleetStatusUpdateAtKernelLaunch() { // kernel launch indicates FSS setup is completed isLaunchMessageSent.set(true); if (!deviceConfiguration.isDeviceConfiguredToTalkToCloud()) { - logger.atWarn().kv("trigger", Trigger.NUCLEUS_LAUNCH).log("Status won't be published until Nucleus is " - + "configured online"); + logger.atWarn() + .kv("trigger", Trigger.NUCLEUS_LAUNCH) + .log("Status won't be published until Nucleus is " + "configured online"); return; } updateFleetStatusUpdateForAllComponents(Trigger.NUCLEUS_LAUNCH); } /** - * Update the Fleet Status information for all the components. - * This function calls under assumption that device is configured to talk to cloud. + * Update the Fleet Status information for all the components. This function calls under assumption that device is + * configured to talk to cloud. + * * @param trigger Trigger of FSS update */ public void updateFleetStatusUpdateForAllComponents(Trigger trigger) { @@ -432,7 +439,8 @@ private Boolean deploymentStatusChanged(Map deploymentDetails) { return true; } - logger.atDebug().kv("deployment details", deploymentDetails) + logger.atDebug() + .kv("deployment details", deploymentDetails) .log("Updating Fleet Status service for deployment"); isDeploymentInProgress.set(false); DeploymentInformation deploymentInformation = getDeploymentInformation(deploymentDetails); @@ -446,16 +454,17 @@ private Boolean deploymentStatusChanged(Map deploymentDetails) { return true; } - private void updateEventTriggeredFleetStatusData(DeploymentInformation deploymentInformation, - Trigger trigger) { + private void updateEventTriggeredFleetStatusData(DeploymentInformation deploymentInformation, Trigger trigger) { if (!isConnected.get()) { // spool deployment updates even if mqtt connection interrupted if (Trigger.isCloudDeploymentTrigger(trigger)) { - logger.atDebug().log("Attempting to publish and spool cloud deployment FSS updates even though MQTT " - + "connection is interrupted"); + logger.atDebug() + .log("Attempting to publish and spool cloud deployment FSS updates even though MQTT " + + "connection is interrupted"); } else { - logger.atDebug().log("Not updating FSS data on local deployment and component events since MQTT " - + "connection is interrupted"); + logger.atDebug() + .log("Not updating FSS data on local deployment and component events since MQTT " + + "connection is interrupted"); return; } } @@ -488,7 +497,7 @@ private void updateEventTriggeredFleetStatusData(DeploymentInformation deploymen // TODO: better throttling mechanism for FSS updates if (updatedGreengrassServiceSet.isEmpty() && Trigger.RECONNECT.equals(trigger) && lastReconnectUpdateTime.plusSeconds(MINIMAL_RECONNECT_PUBLISH_INTERVAL_SEC) - .isAfter(Instant.now())) { + .isAfter(Instant.now())) { return; } @@ -502,8 +511,8 @@ private void updateEventTriggeredFleetStatusData(DeploymentInformation deploymen } }); // remove any component from unchanged status component list if it's in updatedGreengrassServiceSet - deploymentInformation.getUnchangedRootComponents().removeIf( - componentName -> updatedGreengrassServiceSet.stream() + deploymentInformation.getUnchangedRootComponents() + .removeIf(componentName -> updatedGreengrassServiceSet.stream() .anyMatch(service -> service.getName().equals(componentName))); } uploadFleetStatusServiceData(updatedGreengrassServiceSet, overAllStatus.get(), deploymentInformation, @@ -516,10 +525,8 @@ private void updateEventTriggeredFleetStatusData(DeploymentInformation deploymen } } - private void uploadFleetStatusServiceData(Set greengrassServiceSet, - OverallStatus overAllStatus, - DeploymentInformation deploymentInformation, - Trigger trigger) { + private void uploadFleetStatusServiceData(Set greengrassServiceSet, OverallStatus overAllStatus, + DeploymentInformation deploymentInformation, Trigger trigger) { // Only allow component state change update publish if FSS set up is complete // If set up is incomplete, it may cause a deadlock if (!isLaunchMessageSent.get() && !Trigger.NUCLEUS_LAUNCH.equals(trigger)) { @@ -533,15 +540,15 @@ private void uploadFleetStatusServiceData(Set greengrassServi } List components = new ArrayList<>(); - //When a component version is bumped up, FSS may have pointers to both old and new service instances - //Filtering out the old version and only sending the update for the new version + // When a component version is bumped up, FSS may have pointers to both old and new service instances + // Filtering out the old version and only sending the update for the new version Set filteredServices = new HashSet<>(); greengrassServiceSet.forEach(service -> { try { GreengrassService runningService = kernel.locate(service.getName()); filteredServices.add(runningService); } catch (ServiceLoadException e) { - //not able to find service, service might be removed. + // not able to find service, service might be removed. filteredServices.add(service); } }); @@ -550,15 +557,17 @@ private void uploadFleetStatusServiceData(Set greengrassServi HashSet allGroups = new HashSet<>(); DeploymentService deploymentService = null; try { - GreengrassService deploymentServiceLocateResult = this.kernel - .locate(DeploymentService.DEPLOYMENT_SERVICE_TOPICS); + GreengrassService deploymentServiceLocateResult = + this.kernel.locate(DeploymentService.DEPLOYMENT_SERVICE_TOPICS); if (deploymentServiceLocateResult instanceof DeploymentService) { deploymentService = (DeploymentService) deploymentServiceLocateResult; componentsToGroupsTopics = deploymentService.getConfig().lookupTopics(COMPONENTS_TO_GROUPS_TOPICS); } } catch (ServiceLoadException e) { - logger.atError().cause(e).log("Unable to locate {} service while uploading FSS data", - DeploymentService.DEPLOYMENT_SERVICE_TOPICS); + logger.atError() + .cause(e) + .log("Unable to locate {} service while uploading FSS data", + DeploymentService.DEPLOYMENT_SERVICE_TOPICS); } Topics finalComponentsToGroupsTopics = componentsToGroupsTopics; @@ -572,7 +581,10 @@ private void uploadFleetStatusServiceData(Set greengrassServi if (finalComponentsToGroupsTopics != null) { Topics groupsTopics = finalComponentsToGroupsTopics.findTopics(service.getName()); if (groupsTopics != null) { - groupsTopics.children.values().stream().map(n -> (Topic) n).map(Topic::getName) + groupsTopics.children.values() + .stream() + .map(n -> (Topic) n) + .map(Topic::getName) .forEach(groupName -> { componentGroups.add(groupName); // Get all the group names from the user components. @@ -606,7 +618,8 @@ private void uploadFleetStatusServiceData(Set greengrassServi .componentStatusDetails(getComponentStatusDetails(service)) .version(Coerce.toString(versionTopic)) .fleetConfigArns(new ArrayList<>(allGroups)) - .isRoot(false) // Set false for all system level services. + .isRoot(false) // Set + // false for all system level services. .build(); components.add(componentDetails); }); @@ -631,15 +644,15 @@ private void uploadFleetStatusServiceData(Set greengrassServi } private void publishMessage(FleetStatusDetails fleetStatusDetails, List components, - Trigger trigger) { + Trigger trigger) { Instant expectedPublishTime; long delay; // lock to avoid concurrent modifying of lastFSSPublishTime try (LockScope ls = LockScope.lock(publishLock)) { // add a 10 sec gap between each publish request to avoid message receiving out of order in cloud - Instant minimalAllowedPublishTime = lastFSSPublishTime.get() - .plusSeconds(FLEET_STATUS_MESSAGE_PUBLISH_MIN_WAIT_TIME_SEC); + Instant minimalAllowedPublishTime = + lastFSSPublishTime.get().plusSeconds(FLEET_STATUS_MESSAGE_PUBLISH_MIN_WAIT_TIME_SEC); Instant now = Instant.now(); // if last publish time is already more than 10 sec old, publish without delay if (now.isAfter(minimalAllowedPublishTime) || this.waitBetweenPublishDisabled) { @@ -655,14 +668,18 @@ private void publishMessage(FleetStatusDetails fleetStatusDetails, List { fleetStatusDetails.setTimestamp(expectedPublishTime.toEpochMilli()); publisher.publish(fleetStatusDetails, components); - logger.atInfo().event("fss-status-update-published").kv("trigger", trigger) + logger.atInfo() + .event("fss-status-update-published") + .kv("trigger", trigger) .log("Status update published to FSS"); }, delay, TimeUnit.SECONDS); } @@ -689,9 +706,8 @@ private boolean isSystemLevelService(GreengrassService service) { } private OverallStatus getOverallStatusBasedOnServiceState(OverallStatus overallStatus, - GreengrassService greengrassService) { - if (State.BROKEN.equals(greengrassService.getState()) - || OverallStatus.UNHEALTHY.equals(overallStatus)) { + GreengrassService greengrassService) { + if (State.BROKEN.equals(greengrassService.getState()) || OverallStatus.UNHEALTHY.equals(overallStatus)) { return OverallStatus.UNHEALTHY; } return OverallStatus.HEALTHY; @@ -703,7 +719,8 @@ private DeploymentInformation getDeploymentInformation(Map deplo // Reporting GG deployment id in FSS because ListInstalledComponents API // relies on this field to set up last installation source link to deployments. .deploymentId((String) deploymentDetails.get(GG_DEPLOYMENT_ID_KEY_NAME)) - .fleetConfigurationArnForStatus((String) deploymentDetails.get(CONFIGURATION_ARN_KEY_NAME)).build(); + .fleetConfigurationArnForStatus((String) deploymentDetails.get(CONFIGURATION_ARN_KEY_NAME)) + .build(); if (deploymentDetails.containsKey(DEPLOYMENT_STATUS_DETAILS_KEY_NAME)) { Map statusDetailsMap = (Map) deploymentDetails.get(DEPLOYMENT_STATUS_DETAILS_KEY_NAME); @@ -719,8 +736,8 @@ private DeploymentInformation getDeploymentInformation(Map deplo if (deploymentDetails.containsKey(DEPLOYMENT_ROOT_PACKAGES_KEY_NAME)) { // Setting the unchangedRootComponents to be the entire list of root packages, and then later // if a component changed state since last FSS update we will remove it from this list. - deploymentInformation.setUnchangedRootComponents((List) deploymentDetails - .get(DEPLOYMENT_ROOT_PACKAGES_KEY_NAME)); + deploymentInformation.setUnchangedRootComponents( + (List) deploymentDetails.get(DEPLOYMENT_ROOT_PACKAGES_KEY_NAME)); } return deploymentInformation; } @@ -744,10 +761,9 @@ public void shutdown() { * Used for unit tests only. Adds a list of Greengrass services of previously * * @param greengrassServices List of Greengrass services to add - * @param instant last time the service was processed. + * @param instant last time the service was processed. */ - void addServicesToPreviouslyKnownServicesList(List greengrassServices, - Instant instant) { + void addServicesToPreviouslyKnownServicesList(List greengrassServices, Instant instant) { greengrassServices.forEach(greengrassService -> serviceFssTracksMap.put(greengrassService, instant)); } diff --git a/src/main/java/com/aws/greengrass/status/model/MessageType.java b/src/main/java/com/aws/greengrass/status/model/MessageType.java index a66d7e60f5..ff902243d5 100644 --- a/src/main/java/com/aws/greengrass/status/model/MessageType.java +++ b/src/main/java/com/aws/greengrass/status/model/MessageType.java @@ -5,10 +5,8 @@ package com.aws.greengrass.status.model; - public enum MessageType { - COMPLETE, - PARTIAL; + COMPLETE, PARTIAL; /** * Get MessageStatus from MessageType. @@ -19,18 +17,18 @@ public enum MessageType { */ public static MessageType fromTrigger(Trigger trigger) { switch (trigger) { - case LOCAL_DEPLOYMENT: - case THING_DEPLOYMENT: - case THING_GROUP_DEPLOYMENT: - case COMPONENT_STATUS_CHANGE: - case RECONNECT: - return PARTIAL; - case CADENCE: - case NUCLEUS_LAUNCH: - case NETWORK_RECONFIGURE: - return COMPLETE; - default: - throw new IllegalArgumentException("Invalid trigger: " + trigger); + case LOCAL_DEPLOYMENT: + case THING_DEPLOYMENT: + case THING_GROUP_DEPLOYMENT: + case COMPONENT_STATUS_CHANGE: + case RECONNECT: + return PARTIAL; + case CADENCE: + case NUCLEUS_LAUNCH: + case NETWORK_RECONFIGURE: + return COMPLETE; + default: + throw new IllegalArgumentException("Invalid trigger: " + trigger); } } } diff --git a/src/main/java/com/aws/greengrass/status/model/OverallStatus.java b/src/main/java/com/aws/greengrass/status/model/OverallStatus.java index 38023be4f8..1460dc3890 100644 --- a/src/main/java/com/aws/greengrass/status/model/OverallStatus.java +++ b/src/main/java/com/aws/greengrass/status/model/OverallStatus.java @@ -6,6 +6,5 @@ package com.aws.greengrass.status.model; public enum OverallStatus { - HEALTHY, - UNHEALTHY + HEALTHY, UNHEALTHY } diff --git a/src/main/java/com/aws/greengrass/status/model/Trigger.java b/src/main/java/com/aws/greengrass/status/model/Trigger.java index 1842d3e06c..6620fd3487 100644 --- a/src/main/java/com/aws/greengrass/status/model/Trigger.java +++ b/src/main/java/com/aws/greengrass/status/model/Trigger.java @@ -8,10 +8,7 @@ import com.aws.greengrass.deployment.model.Deployment.DeploymentType; public enum Trigger { - LOCAL_DEPLOYMENT, - THING_DEPLOYMENT, - THING_GROUP_DEPLOYMENT, - COMPONENT_STATUS_CHANGE, + LOCAL_DEPLOYMENT, THING_DEPLOYMENT, THING_GROUP_DEPLOYMENT, COMPONENT_STATUS_CHANGE, // when mqtt connection resumes RECONNECT, // when nucleus initially connects IoT Core, a complete FSS update is sent @@ -30,14 +27,14 @@ public enum Trigger { */ public static Trigger fromDeploymentType(DeploymentType deploymentType) { switch (deploymentType) { - case LOCAL: - return LOCAL_DEPLOYMENT; - case SHADOW: - return THING_DEPLOYMENT; - case IOT_JOBS: - return THING_GROUP_DEPLOYMENT; - default: - throw new IllegalArgumentException("Invalid deployment type: " + deploymentType); + case LOCAL: + return LOCAL_DEPLOYMENT; + case SHADOW: + return THING_DEPLOYMENT; + case IOT_JOBS: + return THING_GROUP_DEPLOYMENT; + default: + throw new IllegalArgumentException("Invalid deployment type: " + deploymentType); } } diff --git a/src/main/java/com/aws/greengrass/telemetry/AggregatedMetric.java b/src/main/java/com/aws/greengrass/telemetry/AggregatedMetric.java index f63c12f09b..7bea8e6772 100644 --- a/src/main/java/com/aws/greengrass/telemetry/AggregatedMetric.java +++ b/src/main/java/com/aws/greengrass/telemetry/AggregatedMetric.java @@ -25,8 +25,8 @@ public class AggregatedMetric { @JsonProperty("N") private String name; // TODO: We do not need this to be a map. This map assumes that a metric can have multiple aggregation types and - // values, which is incorrect. This can just be replaced by a String (for aggregation type) - // and an Object (for value). + // values, which is incorrect. This can just be replaced by a String (for aggregation type) + // and an Object (for value). @Setter private Map value = new HashMap<>(); @JsonProperty("U") diff --git a/src/main/java/com/aws/greengrass/telemetry/MetricsAggregator.java b/src/main/java/com/aws/greengrass/telemetry/MetricsAggregator.java index cefd7cc270..0734af12ce 100644 --- a/src/main/java/com/aws/greengrass/telemetry/MetricsAggregator.java +++ b/src/main/java/com/aws/greengrass/telemetry/MetricsAggregator.java @@ -38,16 +38,13 @@ public class MetricsAggregator { private final MetricFactory metricFactory = new MetricFactory(AGGREGATE_METRICS_FILE); /** - * Read namespaces from files. - * Telemetry log files format : fileName + "_%d{yyyy_MM_dd_HH}_%i" + "." + prefix + * Read namespaces from files. Telemetry log files format : fileName + "_%d{yyyy_MM_dd_HH}_%i" + "." + prefix * * @return namespace set */ public static Set getNamespaceSet() { Set namespaces = new HashSet<>(); - try (Stream paths = Files - .walk(TelemetryConfig.getTelemetryDirectory()) - .filter(Files::isRegularFile)) { + try (Stream paths = Files.walk(TelemetryConfig.getTelemetryDirectory()).filter(Files::isRegularFile)) { paths.forEach((p) -> { String fileName = Coerce.toString(p.getFileName()).split(".log")[0]; if (fileName.contains("_")) { @@ -66,7 +63,7 @@ public static Set getNamespaceSet() { /** * This method performs aggregation on the metrics emitted over the aggregation interval and writes them to a file. * - * @param lastAgg timestamp at which the last aggregation was done. + * @param lastAgg timestamp at which the last aggregation was done. * @param currTimestamp timestamp at which the current aggregation is initiated. */ protected void aggregateMetrics(long lastAgg, long currTimestamp) { @@ -77,40 +74,26 @@ protected void aggregateMetrics(long lastAgg, long currTimestamp) { // TODO: [P41214521] Read only those files that are modified after the last aggregation. // file.lastModified() behavior is platform dependent. // filter only files with given namespace that end in ".log" - try (Stream paths = Files - .walk(TelemetryConfig.getTelemetryDirectory()) + try (Stream paths = Files.walk(TelemetryConfig.getTelemetryDirectory()) .filter(Files::isRegularFile) .filter((path) -> Coerce.toString(path.getFileName()).startsWith(namespace) - && Coerce.toString(path.getFileName()).endsWith(".log")) - ) { + && Coerce.toString(path.getFileName()).endsWith(".log"))) { paths.forEach(path -> { try (Stream logs = Files.lines(path)) { logs.forEach((log) -> { try { - /* { - "thread": "pool-3-thread-4", - "level": "TRACE", - "eventType": null, - "message": { - "NS": "SystemMetrics", - "N": "TotalNumberOfFDs", - "U": "Count", - "A": "Average", - "V": 4583, - "TS": 1600127641506 - }, - "contexts": {}, - "loggerName": "Metrics-SystemMetrics", - "timestamp": 1600127641506, - "cause": null - } */ - GreengrassLogMessage egLog = objectMapper.readValue(log, - GreengrassLogMessage.class); + /* + * { "thread": "pool-3-thread-4", "level": "TRACE", "eventType": null, "message": { + * "NS": "SystemMetrics", "N": "TotalNumberOfFDs", "U": "Count", "A": "Average", "V": + * 4583, "TS": 1600127641506 }, "contexts": {}, "loggerName": "Metrics-SystemMetrics", + * "timestamp": 1600127641506, "cause": null } + */ + GreengrassLogMessage egLog = objectMapper.readValue(log, GreengrassLogMessage.class); Metric mdp = objectMapper.readValue(egLog.getMessage(), Metric.class); // Avoid the metrics that are emitted at/after the currTimestamp and before the // aggregation interval - if (mdp != null && currTimestamp > mdp.getTimestamp() && mdp.getTimestamp() - >= lastAgg) { + if (mdp != null && currTimestamp > mdp.getTimestamp() + && mdp.getTimestamp() >= lastAgg) { metrics.computeIfAbsent(mdp.getName(), k -> new ArrayList<>()).add(mdp); } } catch (IOException e) { @@ -137,17 +120,12 @@ protected void aggregateMetrics(long lastAgg, long currTimestamp) { /** * This function takes in the map of metrics with metric name as key and returns a list of metrics with aggregation. - * Example: - * Input: - * NumOfComponentsInstalled + * Example: Input: NumOfComponentsInstalled * |___GreengrassComponents,NumOfComponentsInstalled,Count,Average,10,1234567890 - * |___GreengrassComponents,NumOfComponentsInstalled,Count,Average,15,1234567891 - * NumOfComponentsBroken + * |___GreengrassComponents,NumOfComponentsInstalled,Count,Average,15,1234567891 NumOfComponentsBroken * |___GreengrassComponents,NumOfComponentsBroken,Count,Average,10,1234567890 - * |___GreengrassComponents,NumOfComponentsBroken,Count,Average,20,1234567891 - * Output: - * |___N - NumOfComponentsInstalled,Average - 12.5,U - Count - * |___N - NumOfComponentsBroken,Average - 15,U - Count + * |___GreengrassComponents,NumOfComponentsBroken,Count,Average,20,1234567891 Output: |___N - + * NumOfComponentsInstalled,Average - 12.5,U - Count |___N - NumOfComponentsBroken,Average - 15,U - Count * * @param map metric name -> metric * @return a list of {@link AggregatedMetric} @@ -180,7 +158,7 @@ private List doAggregation(Map> map) { * since the last upload. This also includes one extra aggregated point for each namespace which is the aggregation * of aggregated points in that publish interval. * - * @param lastPublish timestamp at which the last publish was done. + * @param lastPublish timestamp at which the last publish was done. * @param currTimestamp timestamp at which the current publish is initiated. */ protected Map> getMetricsToPublish(long lastPublish, long currTimestamp) { @@ -188,35 +166,22 @@ protected Map> getMetricsToPublish(long last Map> aggUploadMetrics = new HashMap<>(); // Read from the Telemetry/AggregatedMetrics.log file. // TODO: [P41214521] Read only those files that are modified after the last publish. - try (Stream paths = Files - .walk(TelemetryConfig.getTelemetryDirectory()) + try (Stream paths = Files.walk(TelemetryConfig.getTelemetryDirectory()) .filter(Files::isRegularFile) .filter((path) -> Coerce.toString(path.getFileName()).startsWith(AGGREGATE_METRICS_FILE))) { paths.forEach(path -> { try (Stream logs = Files.lines(path)) { logs.forEach(log -> { try { - /* { - "thread": "pool-3-thread-4", - "level": "TRACE", - "eventType": null, - "message": { - "NS": "SystemMetrics", - "N": "TotalNumberOfFDs", - "U": "Count", - "A": "Average", - "V": 4583, - "TS": 1600127641506 - }, - "contexts": {}, - "loggerName": "Metrics-SystemMetrics", - "timestamp": 1600127641506, - "cause": null - } */ - GreengrassLogMessage egLog = objectMapper.readValue(log, - GreengrassLogMessage.class); - AggregatedNamespaceData am = objectMapper.readValue(egLog.getMessage(), - AggregatedNamespaceData.class); + /* + * { "thread": "pool-3-thread-4", "level": "TRACE", "eventType": null, "message": { "NS": + * "SystemMetrics", "N": "TotalNumberOfFDs", "U": "Count", "A": "Average", "V": 4583, "TS": + * 1600127641506 }, "contexts": {}, "loggerName": "Metrics-SystemMetrics", "timestamp": + * 1600127641506, "cause": null } + */ + GreengrassLogMessage egLog = objectMapper.readValue(log, GreengrassLogMessage.class); + AggregatedNamespaceData am = + objectMapper.readValue(egLog.getMessage(), AggregatedNamespaceData.class); // Avoid the metrics that are aggregated at/after the currTimestamp and before the // upload interval if (am != null && currTimestamp > am.getTimestamp() && am.getTimestamp() >= lastPublish) { @@ -245,7 +210,7 @@ protected Map> getMetricsToPublish(long last }); // TODO: [P41214636] Verify the aggregation type of v2 metrics. As of now, all the v1 - // metrics have "Sum" aggregation type and so is the cloud validation. + // metrics have "Sum" aggregation type and so is the cloud validation. // The following code changes any aggregation type of the metrics to "Sum" only in the final result to keep // it compatible with v1 and UATs for now. However, metrics are still defined and aggregated with on their // own aggregation type. @@ -269,7 +234,8 @@ protected Map> getMetricsToPublish(long last }); try { - logger.atDebug().kv("metrics", new ObjectMapper().writeValueAsString(aggUploadMetrics)) + logger.atDebug() + .kv("metrics", new ObjectMapper().writeValueAsString(aggUploadMetrics)) .log("Preparing to upload metrics"); } catch (JsonProcessingException e) { logger.atWarn().setCause(e).log("Could not convert aggregated metrics to json, continuing"); @@ -280,41 +246,35 @@ protected Map> getMetricsToPublish(long last @SuppressWarnings("PMD.DoubleBraceInitialization") protected List getKernelAndOSMetrics() { List kernelAndOSMetrics = new ArrayList<>(); - Platform.getInstance().getOSAndKernelMetrics().forEach((key, value) -> - kernelAndOSMetrics.add(AggregatedMetric.builder() - .name(key) - .unit(Coerce.toString(value)) - .value(new HashMap() {{ - put("Sum", 1.0); - }}) - .build())); + Platform.getInstance() + .getOSAndKernelMetrics() + .forEach((key, + value) -> kernelAndOSMetrics.add(AggregatedMetric.builder() + .name(key) + .unit(Coerce.toString(value)) + .value(new HashMap() { + { + put("Sum", 1.0); + } + }) + .build())); return kernelAndOSMetrics; } /** * This function takes a list of aggregated metrics and returns their aggregation in a list(Aggregation of - * aggregated metrics). This is published to the cloud along with the aggregated metric points - * Example: - * Input: - * TS:123456 - * NS:GreengrassComponents - * |___N - NumOfComponentsInstalled,Average - 20,U - Count - * |___N - NumOfComponentsBroken,Average - 5,U - Count - * TS:123457 - * NS:GreengrassComponents - * |___N - NumOfComponentsInstalled,Average - 10,U - Count - * |___N - NumOfComponentsBroken,Average - 15,U - Count - * Output: - * TS:123457 - * NS:GreengrassComponents - * |___N - NumOfComponentsInstalled,Average - 15,U - Count - * |___N - NumOfComponentsBroken,Average - 10,U - Count + * aggregated metrics). This is published to the cloud along with the aggregated metric points Example: Input: + * TS:123456 NS:GreengrassComponents |___N - NumOfComponentsInstalled,Average - 20,U - Count |___N - + * NumOfComponentsBroken,Average - 5,U - Count TS:123457 NS:GreengrassComponents |___N - + * NumOfComponentsInstalled,Average - 10,U - Count |___N - NumOfComponentsBroken,Average - 15,U - Count Output: + * TS:123457 NS:GreengrassComponents |___N - NumOfComponentsInstalled,Average - 15,U - Count |___N - + * NumOfComponentsBroken,Average - 10,U - Count * * @param aggList list of {@link AggregatedNamespaceData} * @return a list of {@link AggregatedNamespaceData} */ private List getAggForThePublishInterval(List aggList, - long currTimestamp) { + long currTimestamp) { List list = new ArrayList<>(); for (String namespace : getNamespaceSet()) { HashMap> metrics = new HashMap<>(); @@ -342,17 +302,10 @@ private List getAggForThePublishInterval(List aggregated metric * @return list of {@link AggregatedMetric } @@ -380,33 +333,32 @@ private List doAggregationForPublish(Map values, String aggregationType) { double aggregation = 0; switch (aggregationType) { - case "Average": - aggregation = values.stream().mapToDouble(Coerce::toDouble).sum(); - if (!values.isEmpty()) { - aggregation = aggregation / values.size(); - } - break; - case "Sum": - aggregation = values.stream().mapToDouble(Coerce::toDouble).sum(); - break; - case "Maximum": - aggregation = values.stream().mapToDouble(Coerce::toDouble).max().getAsDouble(); - break; - case "Minimum": - aggregation = values.stream().mapToDouble(Coerce::toDouble).min().getAsDouble(); - break; - default: - logger.atError().log("Unknown aggregation type: {}", aggregationType); - break; + case "Average": + aggregation = values.stream().mapToDouble(Coerce::toDouble).sum(); + if (!values.isEmpty()) { + aggregation = aggregation / values.size(); + } + break; + case "Sum": + aggregation = values.stream().mapToDouble(Coerce::toDouble).sum(); + break; + case "Maximum": + aggregation = values.stream().mapToDouble(Coerce::toDouble).max().getAsDouble(); + break; + case "Minimum": + aggregation = values.stream().mapToDouble(Coerce::toDouble).min().getAsDouble(); + break; + default: + logger.atError().log("Unknown aggregation type: {}", aggregationType); + break; } return aggregation; } } - diff --git a/src/main/java/com/aws/greengrass/telemetry/MetricsPayload.java b/src/main/java/com/aws/greengrass/telemetry/MetricsPayload.java index ae4f0824ac..c7b7aad803 100644 --- a/src/main/java/com/aws/greengrass/telemetry/MetricsPayload.java +++ b/src/main/java/com/aws/greengrass/telemetry/MetricsPayload.java @@ -32,6 +32,6 @@ public void setVariablePayload(List variablePayload) { @Override public void setChunkInfo(int id, int totalChunks) { - //no-op + // no-op } } diff --git a/src/main/java/com/aws/greengrass/telemetry/PeriodicMetricsEmitter.java b/src/main/java/com/aws/greengrass/telemetry/PeriodicMetricsEmitter.java index b752841a5a..507a542559 100644 --- a/src/main/java/com/aws/greengrass/telemetry/PeriodicMetricsEmitter.java +++ b/src/main/java/com/aws/greengrass/telemetry/PeriodicMetricsEmitter.java @@ -14,8 +14,8 @@ public abstract class PeriodicMetricsEmitter { protected ScheduledFuture future; /** - * This method will be scheduled to run. So this method typically assigns values to the metrics and emit them. - * Uses getMetrics() to get the raw metric data. + * This method will be scheduled to run. So this method typically assigns values to the metrics and emit them. Uses + * getMetrics() to get the raw metric data. */ public abstract void emitMetrics(); diff --git a/src/main/java/com/aws/greengrass/telemetry/SystemMetricsEmitter.java b/src/main/java/com/aws/greengrass/telemetry/SystemMetricsEmitter.java index 1ff5f3fa10..ef12d35e48 100644 --- a/src/main/java/com/aws/greengrass/telemetry/SystemMetricsEmitter.java +++ b/src/main/java/com/aws/greengrass/telemetry/SystemMetricsEmitter.java @@ -42,6 +42,7 @@ public void emitMetrics() { /** * Retrieve kernel component state metrics. + * * @return a list of {@link Metric} */ @Override diff --git a/src/main/java/com/aws/greengrass/telemetry/TelemetryAgent.java b/src/main/java/com/aws/greengrass/telemetry/TelemetryAgent.java index adf8d3ed1f..cdc8730e74 100644 --- a/src/main/java/com/aws/greengrass/telemetry/TelemetryAgent.java +++ b/src/main/java/com/aws/greengrass/telemetry/TelemetryAgent.java @@ -41,10 +41,10 @@ public class TelemetryAgent extends GreengrassService { public static final String TELEMETRY_AGENT_SERVICE_TOPICS = "TelemetryAgent"; public static final String DEFAULT_TELEMETRY_METRICS_PUBLISH_TOPIC = "$aws/things/{thingName}/greengrass/health/json"; - public static final String TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC - = "telemetryPeriodicAggregateMetricsIntervalSec"; - public static final String TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC - = "telemetryPeriodicPublishMetricsIntervalSec"; + public static final String TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC = + "telemetryPeriodicAggregateMetricsIntervalSec"; + public static final String TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC = + "telemetryPeriodicPublishMetricsIntervalSec"; public static final String TELEMETRY_LAST_PERIODIC_PUBLISH_TIME_TOPIC = "lastPeriodicPublishMetricsTime"; public static final String TELEMETRY_LAST_PERIODIC_AGGREGATION_TIME_TOPIC = "lastPeriodicAggregationMetricsTime"; public static final int DEFAULT_PERIODIC_AGGREGATE_INTERVAL_SEC = 3_600; @@ -63,7 +63,7 @@ public class TelemetryAgent extends GreengrassService { private final List periodicMetricsEmitters = new ArrayList<>(); @Getter(AccessLevel.PACKAGE) private ScheduledFuture periodicAggregateMetricsFuture = null; - @Getter //used in e2e + @Getter // used in e2e private ScheduledFuture periodicPublishMetricsFuture = null; private final MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() { @Override @@ -87,19 +87,19 @@ public void onConnectionResumed(boolean sessionPresent) { /** * Constructor for the class. * - * @param topics root configuration topic for this service - * @param mqttClient {@link MqttClient} + * @param topics root configuration topic for this service + * @param mqttClient {@link MqttClient} * @param deviceConfiguration {@link DeviceConfiguration} - * @param ma {@link MetricsAggregator} - * @param sme {@link SystemMetricsEmitter} - * @param kme {@link KernelMetricsEmitter} - * @param ses {@link ScheduledExecutorService} - * @param executorService {@link ExecutorService} + * @param ma {@link MetricsAggregator} + * @param sme {@link SystemMetricsEmitter} + * @param kme {@link KernelMetricsEmitter} + * @param ses {@link ScheduledExecutorService} + * @param executorService {@link ExecutorService} */ @Inject public TelemetryAgent(Topics topics, MqttClient mqttClient, DeviceConfiguration deviceConfiguration, - MetricsAggregator ma, SystemMetricsEmitter sme, KernelMetricsEmitter kme, - ScheduledExecutorService ses, ExecutorService executorService) { + MetricsAggregator ma, SystemMetricsEmitter sme, KernelMetricsEmitter kme, ScheduledExecutorService ses, + ExecutorService executorService) { this(topics, mqttClient, deviceConfiguration, ma, sme, kme, ses, executorService, DEFAULT_PERIODIC_PUBLISH_INTERVAL_SEC, DEFAULT_PERIODIC_AGGREGATE_INTERVAL_SEC); } @@ -107,22 +107,22 @@ public TelemetryAgent(Topics topics, MqttClient mqttClient, DeviceConfiguration /** * Constructor for the class. * - * @param topics root configuration topic for this service - * @param mqttClient {@link MqttClient} - * @param deviceConfiguration {@link DeviceConfiguration} - * @param ma {@link MetricsAggregator} - * @param sme {@link SystemMetricsEmitter} - * @param kme {@link KernelMetricsEmitter} - * @param ses {@link ScheduledExecutorService} - * @param executorService {@link ExecutorService} - * @param periodicPublishMetricsIntervalSec interval for cadence based telemetry publish. + * @param topics root configuration topic for this service + * @param mqttClient {@link MqttClient} + * @param deviceConfiguration {@link DeviceConfiguration} + * @param ma {@link MetricsAggregator} + * @param sme {@link SystemMetricsEmitter} + * @param kme {@link KernelMetricsEmitter} + * @param ses {@link ScheduledExecutorService} + * @param executorService {@link ExecutorService} + * @param periodicPublishMetricsIntervalSec interval for cadence based telemetry publish. * @param periodicAggregateMetricsIntervalSec interval for cadence based telemetry metrics aggregation. */ @SuppressWarnings("PMD.ExcessiveParameterList") - TelemetryAgent(Topics topics, MqttClient mqttClient, DeviceConfiguration deviceConfiguration, - MetricsAggregator ma, SystemMetricsEmitter sme, KernelMetricsEmitter kme, - ScheduledExecutorService ses, ExecutorService executorService, int periodicPublishMetricsIntervalSec, - int periodicAggregateMetricsIntervalSec) { + TelemetryAgent(Topics topics, MqttClient mqttClient, DeviceConfiguration deviceConfiguration, MetricsAggregator ma, + SystemMetricsEmitter sme, KernelMetricsEmitter kme, ScheduledExecutorService ses, + ExecutorService executorService, int periodicPublishMetricsIntervalSec, + int periodicAggregateMetricsIntervalSec) { super(topics); this.mqttClient = mqttClient; this.publisher = new MqttChunkedPayloadPublisher<>(this.mqttClient); @@ -132,10 +132,16 @@ public TelemetryAgent(Topics topics, MqttClient mqttClient, DeviceConfiguration this.metricsAggregator = ma; this.deviceConfiguration = deviceConfiguration; this.thingName = Coerce.toString(deviceConfiguration.getThingName()); - int finalPeriodicAggregateMetricsIntervalSec = TestFeatureParameters.retrieveWithDefault(Double.class, - TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC, periodicAggregateMetricsIntervalSec).intValue(); - int finalPeriodicPublishMetricsIntervalSec = TestFeatureParameters.retrieveWithDefault(Double.class, - TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC, periodicPublishMetricsIntervalSec).intValue(); + int finalPeriodicAggregateMetricsIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC, + periodicAggregateMetricsIntervalSec) + .intValue(); + int finalPeriodicPublishMetricsIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC, + periodicPublishMetricsIntervalSec) + .intValue(); currentConfiguration.set(TelemetryConfiguration.builder() .periodicAggregateMetricsIntervalSeconds(finalPeriodicAggregateMetricsIntervalSec) .periodicPublishMetricsIntervalSeconds(finalPeriodicPublishMetricsIntervalSec) @@ -205,8 +211,8 @@ void schedulePeriodicAggregateMetrics(boolean isReconfigured) { /** * Schedules the publishing of metrics based on the configured publish interval or the mqtt connection status. * - * @param isReconfigured will be true if the publish interval is reconfigured or when - * the mqtt connection is resumed. + * @param isReconfigured will be true if the publish interval is reconfigured or when the mqtt connection is + * resumed. */ void schedulePeriodicPublishMetrics(boolean isReconfigured) { // If we missed to publish the metrics due to connection loss or if the publish interval is reconfigured, @@ -269,8 +275,7 @@ void publishPeriodicMetrics() { } private Topic getPeriodicPublishTimeTopic() { - return getRuntimeConfig().lookup(TELEMETRY_LAST_PERIODIC_PUBLISH_TIME_TOPIC) - .dflt(Instant.now().toEpochMilli()); + return getRuntimeConfig().lookup(TELEMETRY_LAST_PERIODIC_PUBLISH_TIME_TOPIC).dflt(Instant.now().toEpochMilli()); } private Topic getPeriodicAggregateTimeTopic() { @@ -318,12 +323,14 @@ private void handleTelemetryConfiguration(Topics configurationTopics) { if (newTelemetryConfiguration.isEnabled()) { // If the current aggregation interval is different from the new interval, then reschedule // the periodic aggregation task - aggregateMetricsIntervalSecChanged = configuration.getPeriodicAggregateMetricsIntervalSeconds() - != newTelemetryConfiguration.getPeriodicAggregateMetricsIntervalSeconds(); + aggregateMetricsIntervalSecChanged = + configuration.getPeriodicAggregateMetricsIntervalSeconds() != newTelemetryConfiguration + .getPeriodicAggregateMetricsIntervalSeconds(); // If the current publish interval is different from the new interval, then reschedule // the publish aggregation task - publishMetricsIntervalSecChanged = configuration.getPeriodicPublishMetricsIntervalSeconds() - != newTelemetryConfiguration.getPeriodicPublishMetricsIntervalSeconds(); + publishMetricsIntervalSecChanged = + configuration.getPeriodicPublishMetricsIntervalSeconds() != newTelemetryConfiguration + .getPeriodicPublishMetricsIntervalSeconds(); } else { // If telemetry is not enabled, then cancel the futures. cancelAllJobs(); @@ -350,8 +357,8 @@ private void setPeriodicPublishMetricsIntervalAndScheduleTask(int defaultValue) .periodicPublishMetricsIntervalSeconds(TestFeatureParameters .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC, defaultValue) .intValue()) - .periodicAggregateMetricsIntervalSeconds(telemetryConfiguration - .getPeriodicAggregateMetricsIntervalSeconds()) + .periodicAggregateMetricsIntervalSeconds( + telemetryConfiguration.getPeriodicAggregateMetricsIntervalSeconds()) .enabled(telemetryConfiguration.isEnabled()) .build()); try (LockScope ls = LockScope.lock(periodicPublishMetricsInProgressLock)) { @@ -367,8 +374,8 @@ private void setPeriodicAggregateMetricsIntervalAndSchedule(int defaultValue) { .periodicAggregateMetricsIntervalSeconds(TestFeatureParameters .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC, defaultValue) .intValue()) - .periodicPublishMetricsIntervalSeconds(telemetryConfiguration - .getPeriodicPublishMetricsIntervalSeconds()) + .periodicPublishMetricsIntervalSeconds( + telemetryConfiguration.getPeriodicPublishMetricsIntervalSeconds()) .enabled(telemetryConfiguration.isEnabled()) .build()); diff --git a/src/main/java/com/aws/greengrass/telemetry/TelemetryConfiguration.java b/src/main/java/com/aws/greengrass/telemetry/TelemetryConfiguration.java index 2d690d0ad6..947d396d00 100644 --- a/src/main/java/com/aws/greengrass/telemetry/TelemetryConfiguration.java +++ b/src/main/java/com/aws/greengrass/telemetry/TelemetryConfiguration.java @@ -30,8 +30,9 @@ public class TelemetryConfiguration { /** * Get the telemetry configuration from the POJO map. - * @param pojo POJO object. - * @return the telemetry configuration. + * + * @param pojo POJO object. + * @return the telemetry configuration. */ public static TelemetryConfiguration fromPojo(Map pojo) { int periodicAggregateMetricsIntervalSec = DEFAULT_PERIODIC_AGGREGATE_INTERVAL_SEC; @@ -39,37 +40,41 @@ public static TelemetryConfiguration fromPojo(Map pojo) { boolean isEnabled = true; for (Map.Entry entry : pojo.entrySet()) { switch (entry.getKey()) { - case "enabled": - isEnabled = Coerce.toBoolean(entry.getValue()); + case "enabled": + isEnabled = Coerce.toBoolean(entry.getValue()); + break; + case "periodicAggregateMetricsIntervalSec": + int newPeriodicAggregateMetricsIntervalSec = Coerce.toInt(entry.getValue()); + // if the aggregation interval is smaller than it then return since we don't want to + // aggregate more frequently than the default. + if (newPeriodicAggregateMetricsIntervalSec < periodicAggregateMetricsIntervalSec) { break; - case "periodicAggregateMetricsIntervalSec": - int newPeriodicAggregateMetricsIntervalSec = Coerce.toInt(entry.getValue()); - // if the aggregation interval is smaller than it then return since we don't want to - // aggregate more frequently than the default. - if (newPeriodicAggregateMetricsIntervalSec < periodicAggregateMetricsIntervalSec) { - break; - } - periodicAggregateMetricsIntervalSec = newPeriodicAggregateMetricsIntervalSec; - break; - case "periodicPublishMetricsIntervalSec": - int newPeriodicPublishMetricsIntervalSec = Coerce.toInt(entry.getValue()); - // if the publish interval is smaller than it then return since we don't want to - // publish more frequently than the default. - if (newPeriodicPublishMetricsIntervalSec < periodicPublishMetricsIntervalSec) { - break; - } - periodicPublishMetricsIntervalSec = newPeriodicPublishMetricsIntervalSec; - break; - default: + } + periodicAggregateMetricsIntervalSec = newPeriodicAggregateMetricsIntervalSec; + break; + case "periodicPublishMetricsIntervalSec": + int newPeriodicPublishMetricsIntervalSec = Coerce.toInt(entry.getValue()); + // if the publish interval is smaller than it then return since we don't want to + // publish more frequently than the default. + if (newPeriodicPublishMetricsIntervalSec < periodicPublishMetricsIntervalSec) { break; + } + periodicPublishMetricsIntervalSec = newPeriodicPublishMetricsIntervalSec; + break; + default: + break; } } - periodicAggregateMetricsIntervalSec = TestFeatureParameters - .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC, - periodicAggregateMetricsIntervalSec).intValue(); - periodicPublishMetricsIntervalSec = TestFeatureParameters - .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC, - periodicPublishMetricsIntervalSec).intValue(); + periodicAggregateMetricsIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_AGGREGATE_INTERVAL_SEC, + periodicAggregateMetricsIntervalSec) + .intValue(); + periodicPublishMetricsIntervalSec = + TestFeatureParameters + .retrieveWithDefault(Double.class, TELEMETRY_TEST_PERIODIC_PUBLISH_INTERVAL_SEC, + periodicPublishMetricsIntervalSec) + .intValue(); return TelemetryConfiguration.builder() .enabled(isEnabled) .periodicAggregateMetricsIntervalSeconds(periodicAggregateMetricsIntervalSec) diff --git a/src/main/java/com/aws/greengrass/tes/CredentialRequestHandler.java b/src/main/java/com/aws/greengrass/tes/CredentialRequestHandler.java index b22b380f5a..a82f9fbbf4 100644 --- a/src/main/java/com/aws/greengrass/tes/CredentialRequestHandler.java +++ b/src/main/java/com/aws/greengrass/tes/CredentialRequestHandler.java @@ -104,17 +104,16 @@ private static class TESCache { /** * Constructor. * - * @param cloudHelper {@link IotCloudHelper} for making http requests to cloud. - * @param connectionManager {@link IotConnectionManager} underlying connection manager for cloud. + * @param cloudHelper {@link IotCloudHelper} for making http requests to cloud. + * @param connectionManager {@link IotConnectionManager} underlying connection manager for cloud. * @param authenticationHandler {@link AuthenticationHandler} authN module for authenticating requests. - * @param authZHandler {@link AuthorizationHandler} authZ module for authorizing requests. - * @param deviceConfiguration {@link DeviceConfiguration} for getting device configuration. + * @param authZHandler {@link AuthorizationHandler} authZ module for authorizing requests. + * @param deviceConfiguration {@link DeviceConfiguration} for getting device configuration. */ @Inject public CredentialRequestHandler(final IotCloudHelper cloudHelper, final IotConnectionManager connectionManager, - final AuthenticationHandler authenticationHandler, - final AuthorizationHandler authZHandler, - final DeviceConfiguration deviceConfiguration) { + final AuthenticationHandler authenticationHandler, final AuthorizationHandler authZHandler, + final DeviceConfiguration deviceConfiguration) { this.iotCloudHelper = cloudHelper; this.iotConnectionManager = connectionManager; this.authNHandler = authenticationHandler; @@ -152,8 +151,7 @@ public void handle(final HttpExchange exchange) throws IOException { return; } if (!exchange.getRequestURI().getPath().equals(URL)) { - LOGGER.atWarn().log("Unexpected URI: {}.", - exchange.getRequestURI().getPath()); + LOGGER.atWarn().log("Unexpected URI: {}.", exchange.getRequestURI().getPath()); generateError(exchange, HttpURLConnection.HTTP_BAD_REQUEST); return; } @@ -173,8 +171,8 @@ public void handle(final HttpExchange exchange) throws IOException { } catch (Throwable e) { // Broken pipe is ignorable; it just means that the client went away if ("Broken pipe".equalsIgnoreCase(e.getMessage()) - || "An established connection was aborted by the software in your host machine".equalsIgnoreCase( - e.getMessage())) { + || "An established connection was aborted by the software in your host machine" + .equalsIgnoreCase(e.getMessage())) { LOGGER.atDebug().log("Client gave up before we could respond"); } else { // Don't let the server crash, swallow problems with a 5xx @@ -200,7 +198,8 @@ private byte[] getCredentialsWithTimeout(int timeout, TimeUnit timeUnit) throws } } if (future != null) { - LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atDebug() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .log("IAM credentials not found in cache or already expired. A request to fetch new credentials " + "is already ongoing, waiting for it to complete."); try { @@ -219,7 +218,8 @@ private byte[] getCredentialsWithTimeout(int timeout, TimeUnit timeUnit) throws } // Get new credentials from cloud - LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atDebug() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .log("IAM credentials not found in cache or already expired. Fetching new ones from TES"); return getCredentialsBypassCache(); } @@ -252,12 +252,13 @@ private byte[] getCredentialsBypassCache() { Instant newExpiry = tesCache.get(iotCredentialsPath).expiry; try { - final IotCloudResponse cloudResponse = iotCloudHelper - .sendHttpRequest(iotConnectionManager, thingName, - iotCredentialsPath, IOT_CREDENTIALS_HTTP_VERB, null); + final IotCloudResponse cloudResponse = iotCloudHelper.sendHttpRequest(iotConnectionManager, thingName, + iotCredentialsPath, IOT_CREDENTIALS_HTTP_VERB, null); final String credentials = cloudResponse.toString(); final int cloudResponseCode = cloudResponse.getStatusCode(); - LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).kv("statusCode", cloudResponseCode) + LOGGER.atDebug() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + .kv("statusCode", cloudResponseCode) .log("Received response from cloud: {}", cloudResponseCode == 200 ? "response code 200, not logging credentials" : credentials); @@ -278,19 +279,22 @@ private byte[] getCredentialsBypassCache() { String responseString = "TES responded with credentials that expired at " + expiry; response = responseString.getBytes(StandardCharsets.UTF_8); tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR; - LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atError() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .log("Unable to cache expired credentials which expired at {}", expiry); } else { newExpiry = expiry.minus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN)); tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_OK; if (newExpiry.isBefore(Instant.now(clock))) { - LOGGER.atWarn().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atWarn() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .log("Can't cache credentials as new credentials {} will " - + "expire in less than {} minutes", expiry, + + "expire in less than {} minutes", expiry, TIME_BEFORE_CACHE_EXPIRE_IN_MIN); } else { - LOGGER.atInfo().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atInfo() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .log("Received IAM credentials that will be cached until {}", newExpiry); } } @@ -302,9 +306,8 @@ private byte[] getCredentialsBypassCache() { } } else { // Cloud errors should be cached - String responseString = - String.format("TES responded with status code: %d. Caching response. %s", cloudResponseCode, - credentials); + String responseString = String.format("TES responded with status code: %d. Caching response. %s", + cloudResponseCode, credentials); response = responseString.getBytes(StandardCharsets.UTF_8); newExpiry = getExpiryPolicyForErr(cloudResponseCode); tesCache.get(iotCredentialsPath).responseCode = cloudResponseCode; @@ -322,7 +325,8 @@ private byte[] getCredentialsBypassCache() { tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR; tesCache.get(iotCredentialsPath).expiry = newExpiry; tesCache.get(iotCredentialsPath).credentials = response; - LOGGER.atWarn().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atWarn() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .log("Encountered error while fetching credentials", e); } finally { try (LockScope ls = LockScope.lock(cacheEntry.lock)) { @@ -375,11 +379,11 @@ public AwsCredentials getAwsCredentialsBypassCache() { private AwsCredentials getCredentialsFromByte(byte[] data) { try { Map credentials = OBJECT_MAPPER.readValue(data, Map.class); - return AwsSessionCredentials - .create(credentials.get(ACCESS_KEY_DOWNSTREAM_STR), credentials.get(SECRET_ACCESS_DOWNSTREAM_STR), - credentials.get(SESSION_TOKEN_DOWNSTREAM_STR)); + return AwsSessionCredentials.create(credentials.get(ACCESS_KEY_DOWNSTREAM_STR), + credentials.get(SECRET_ACCESS_DOWNSTREAM_STR), credentials.get(SESSION_TOKEN_DOWNSTREAM_STR)); } catch (IOException e) { - LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath) + LOGGER.atError() + .kv(IOT_CRED_PATH_KEY, iotCredentialsPath) .kv("credentialData", new String(data, StandardCharsets.UTF_8)) .log("Error in retrieving AwsCredentials from TES"); return null; @@ -406,8 +410,11 @@ private void doAuth(final HttpExchange exchange) throws UnauthenticatedException String authNToken = exchange.getRequestHeaders().getFirst(AUTH_HEADER); String clientService = authNHandler.doAuthentication(authNToken); authZHandler.isAuthorized(TokenExchangeService.TOKEN_EXCHANGE_SERVICE_TOPICS, - Permission.builder().principal(clientService).operation(TokenExchangeService.AUTHZ_TES_OPERATION) - .resource(null).build()); + Permission.builder() + .principal(clientService) + .operation(TokenExchangeService.AUTHZ_TES_OPERATION) + .resource(null) + .build()); } private String parseExpiryFromResponse(final String credentials) throws AWSIotException { diff --git a/src/main/java/com/aws/greengrass/tes/HttpServerImpl.java b/src/main/java/com/aws/greengrass/tes/HttpServerImpl.java index d7a03dfb75..5a626a191a 100644 --- a/src/main/java/com/aws/greengrass/tes/HttpServerImpl.java +++ b/src/main/java/com/aws/greengrass/tes/HttpServerImpl.java @@ -20,6 +20,7 @@ public class HttpServerImpl implements Server { /** * Constructor. + * * @param port Http server port * @param credentialRequestHandler request handler for server requests * @throws IOException When server creation fails diff --git a/src/main/java/com/aws/greengrass/tes/TokenExchangeService.java b/src/main/java/com/aws/greengrass/tes/TokenExchangeService.java index c7fc934c51..2e8f419e4c 100644 --- a/src/main/java/com/aws/greengrass/tes/TokenExchangeService.java +++ b/src/main/java/com/aws/greengrass/tes/TokenExchangeService.java @@ -46,15 +46,15 @@ public class TokenExchangeService extends GreengrassService implements AwsCreden /** * Constructor. + * * @param topics the configuration coming from kernel * @param credentialRequestHandler {@link CredentialRequestHandler} * @param authZHandler {@link AuthorizationHandler} * @param deviceConfiguration device's system configuration */ @Inject - public TokenExchangeService(Topics topics, - CredentialRequestHandler credentialRequestHandler, - AuthorizationHandler authZHandler, DeviceConfiguration deviceConfiguration) { + public TokenExchangeService(Topics topics, CredentialRequestHandler credentialRequestHandler, + AuthorizationHandler authZHandler, DeviceConfiguration deviceConfiguration) { super(topics); port = Coerce.toInt(config.lookup(CONFIGURATION_CONFIG_KEY, PORT_TOPIC).dflt(DEFAULT_PORT)); config.subscribe((why, node) -> { @@ -63,7 +63,10 @@ public TokenExchangeService(Topics topics, port = Coerce.toInt(node); Topic activePortTopic = config.lookup(CONFIGURATION_CONFIG_KEY, ACTIVE_PORT_TOPIC); if (port != Coerce.toInt(activePortTopic)) { - logger.atInfo("tes-config-change").kv(PORT_TOPIC, port).kv("node", node).kv("why", why) + logger.atInfo("tes-config-change") + .kv(PORT_TOPIC, port) + .kv("node", node) + .kv("why", why) .log("Restarting TES server due to port config change"); requestRestart(); } @@ -91,7 +94,9 @@ public void postInject() { @Override @SuppressWarnings("PMD.CloseResource") protected void startup() { - logger.atInfo().addKeyValue(PORT_TOPIC, port).addKeyValue(IOT_ROLE_ALIAS_TOPIC, iotRoleAlias) + logger.atInfo() + .addKeyValue(PORT_TOPIC, port) + .addKeyValue(IOT_ROLE_ALIAS_TOPIC, iotRoleAlias) .log("Attempting to start server at configured port {}", port); try { validateConfig(); diff --git a/src/main/java/com/aws/greengrass/testing/TestFeatureParameters.java b/src/main/java/com/aws/greengrass/testing/TestFeatureParameters.java index 0422c7e1c8..d2cd4674ed 100644 --- a/src/main/java/com/aws/greengrass/testing/TestFeatureParameters.java +++ b/src/main/java/com/aws/greengrass/testing/TestFeatureParameters.java @@ -14,8 +14,8 @@ import java.util.function.Consumer; /** - * Some functionality is enabled only for integration testing. Such functionality is subject to change between - * releases of the Greengrass Nucleus and/or may result in unstable behavior in production and should be avoided. + * Some functionality is enabled only for integration testing. Such functionality is subject to change between releases + * of the Greengrass Nucleus and/or may result in unstable behavior in production and should be avoided. */ public final class TestFeatureParameters { private static final Logger LOGGER = LogManager.getLogger(TestFeatureParameters.class); @@ -37,15 +37,15 @@ public T retrieveWithDefault(Class cls, String featureParameter }; private static final AtomicReference handler = - new AtomicReference<>(DEFAULT_HANDLER); + new AtomicReference<>(DEFAULT_HANDLER); private TestFeatureParameters() { // No instance methods } /** - * Retrieve either the provided default (production) value of a parameter, or, under test conditions, an - * alternative value specific for the test being undertaken. + * Retrieve either the provided default (production) value of a parameter, or, under test conditions, an alternative + * value specific for the test being undertaken. * * @param cls Expected type to handle runtime validation of override type * @param featureParameterName Name of parameter to query. @@ -59,13 +59,15 @@ public static T retrieveWithDefault(Class cls, String featurePa T value = actualHandler.retrieveWithDefault(cls, featureParameterName, defaultValue); if (defaultValue == value) { // Pass through default value logged at debug level - LOGGER.atDebug().addKeyValue("FeatureParameterName", featureParameterName) + LOGGER.atDebug() + .addKeyValue("FeatureParameterName", featureParameterName) .addKeyValue("DefaultValue", defaultValue) .log("Default Feature Parameter \"{}\"=\"{}\" via {}", featureParameterName, value, actualHandler.getClass().getName()); } else { // Override occurred, this is intentionally noisy - LOGGER.atWarn().addKeyValue("FeatureParameterName", featureParameterName) + LOGGER.atWarn() + .addKeyValue("FeatureParameterName", featureParameterName) .addKeyValue("ProductionValue", defaultValue) .addKeyValue("OverrideValue", value) .log("Override Feature Parameter \"{}\"=\"{}\" via {}", featureParameterName, value, @@ -106,8 +108,9 @@ public static TestFeatureParameterInterface internalDisableTestingFeatureParamet /** * Register a callback to notify when the handler is set. - * @param serviceName Name of the service requesting a callback. - * @param callback The callback function. + * + * @param serviceName Name of the service requesting a callback. + * @param callback The callback function. */ public static void registerHandlerCallback(String serviceName, Consumer callback) { handlerRegistrationCallbacks.put(serviceName, callback); @@ -115,7 +118,8 @@ public static void registerHandlerCallback(String serviceName, Consumer /** * Unregister a service from getting handler set notification. - * @param serviceName Name of the service unregistering. + * + * @param serviceName Name of the service unregistering. */ public static void unRegisterHandlerCallback(String serviceName) { handlerRegistrationCallbacks.remove(serviceName); diff --git a/src/main/java/com/aws/greengrass/util/BaseRetryableAccessor.java b/src/main/java/com/aws/greengrass/util/BaseRetryableAccessor.java index f20bf48d99..857b58ce34 100644 --- a/src/main/java/com/aws/greengrass/util/BaseRetryableAccessor.java +++ b/src/main/java/com/aws/greengrass/util/BaseRetryableAccessor.java @@ -13,18 +13,20 @@ public class BaseRetryableAccessor { /** * Execute with retries. * - * @param tries no of retries + * @param tries no of retries * @param initialBackoffMillis backoff in milliseconds - * @param func executable action - * @param retryableExceptions exceptions to retry on - * @param response - * @param exception + * @param func executable action + * @param retryableExceptions exceptions to retry on + * @param response + * @param exception * @return response/exception * @throws E exception while talking via AWS SDK */ - @SuppressWarnings({"PMD.AssignmentInOperand", "PMD.AvoidCatchingThrowable"}) + @SuppressWarnings({ + "PMD.AssignmentInOperand", "PMD.AvoidCatchingThrowable" + }) public T retry(int tries, int initialBackoffMillis, CrashableSupplier func, - Iterable> retryableExceptions) throws E { + Iterable> retryableExceptions) throws E { E lastException = null; int tryCount = 0; while (tryCount++ < tries) { diff --git a/src/main/java/com/aws/greengrass/util/BatchedSubscriber.java b/src/main/java/com/aws/greengrass/util/BatchedSubscriber.java index 99cbc65ebb..5661e3eac7 100644 --- a/src/main/java/com/aws/greengrass/util/BatchedSubscriber.java +++ b/src/main/java/com/aws/greengrass/util/BatchedSubscriber.java @@ -18,27 +18,36 @@ import java.util.function.Consumer; /** - * {@link BatchedSubscriber} is a subscriber that fires once for a batch of changes - * (and on subscription initialization). + * {@link BatchedSubscriber} is a subscriber that fires once for a batch of changes (and on subscription + * initialization). * - *

A batch is defined as all the elements in a {@link Topic} or {@link Topics}' publish queue, - * with the last batch element being the most recent topic change. + *
+ *
+ *

+ * A batch is defined as all the elements in a {@link Topic} or {@link Topics}' publish queue, with the last + * batch element being the most recent topic change. * - *

By default, commonly ignored changes, like {@link WhatHappened#timestampUpdated} and - * {@link WhatHappened#interiorAdded}, will NOT be added to a batch - * (see {@link BatchedSubscriber#BASE_EXCLUSION}). + *
+ *
+ *

+ * By default, commonly ignored changes, like {@link WhatHappened#timestampUpdated} and + * {@link WhatHappened#interiorAdded}, will NOT be added to a batch (see + * {@link BatchedSubscriber#BASE_EXCLUSION}). * - *

To be precise, a {@link BatchedSubscriber} will trigger its {@link BatchedSubscriber#callback} - * after the following events: + *
+ *
+ *

+ * To be precise, a {@link BatchedSubscriber} will trigger its {@link BatchedSubscriber#callback} after the following + * events: *

    - *
  • when {@link WhatHappened#initialized} is fired on initial subscription
  • - *
  • when the last batch element is popped from the topic's publish queue
  • + *
  • when {@link WhatHappened#initialized} is fired on initial subscription
  • + *
  • when the last batch element is popped from the topic's publish queue
  • *
*/ public final class BatchedSubscriber implements ChildChanged, Subscriber { - public static final BiPredicate BASE_EXCLUSION = (what, child) -> - what == WhatHappened.timestampUpdated || what == WhatHappened.interiorAdded; + public static final BiPredicate BASE_EXCLUSION = + (what, child) -> what == WhatHappened.timestampUpdated || what == WhatHappened.interiorAdded; private final AtomicInteger numRequestedChanges = new AtomicInteger(); @@ -49,9 +58,10 @@ public final class BatchedSubscriber implements ChildChanged, Subscriber { /** * Constructs a new BatchedSubscriber. * - *

Defaults to using {@link BatchedSubscriber#BASE_EXCLUSION} for excluding changes from a batch. + *

+ * Defaults to using {@link BatchedSubscriber#BASE_EXCLUSION} for excluding changes from a batch. * - * @param topic topic to subscribe to + * @param topic topic to subscribe to * @param callback action to perform after a batch of changes and on initialization */ public BatchedSubscriber(Topic topic, Consumer callback) { @@ -61,22 +71,21 @@ public BatchedSubscriber(Topic topic, Consumer callback) { /** * Constructs a new BatchedSubscriber. * - * @param topic topic to subscribe to + * @param topic topic to subscribe to * @param exclusions predicate for ignoring a subset topic changes - * @param callback action to perform after a batch of changes and on initialization + * @param callback action to perform after a batch of changes and on initialization */ - public BatchedSubscriber(Topic topic, - BiPredicate exclusions, - Consumer callback) { + public BatchedSubscriber(Topic topic, BiPredicate exclusions, Consumer callback) { this((Node) topic, exclusions, callback); } /** * Constructs a new BatchedSubscriber. * - *

Defaults to using {@link BatchedSubscriber#BASE_EXCLUSION} for excluding changes from a batch. + *

+ * Defaults to using {@link BatchedSubscriber#BASE_EXCLUSION} for excluding changes from a batch. * - * @param topics topics to subscribe to + * @param topics topics to subscribe to * @param callback action to perform after a batch of changes and on initialization */ public BatchedSubscriber(Topics topics, Consumer callback) { @@ -86,13 +95,12 @@ public BatchedSubscriber(Topics topics, Consumer callback) { /** * Constructs a new BatchedSubscriber. * - * @param topics topics to subscribe to + * @param topics topics to subscribe to * @param exclusions predicate for ignoring a subset topics changes - * @param callback action to perform after a batch of changes and on initialization + * @param callback action to perform after a batch of changes and on initialization */ - public BatchedSubscriber(Topics topics, - BiPredicate exclusions, - Consumer callback) { + public BatchedSubscriber(Topics topics, BiPredicate exclusions, + Consumer callback) { this((Node) topics, exclusions, callback); } @@ -100,23 +108,21 @@ public BatchedSubscriber(Topics topics, * Constructs a new BatchedSubscriber. * * @param exclusions predicate for ignoring a subset topic(s) changes - * @param callback action to perform after a batch of changes and on initialization + * @param callback action to perform after a batch of changes and on initialization */ - public BatchedSubscriber(BiPredicate exclusions, - Consumer callback) { + public BatchedSubscriber(BiPredicate exclusions, Consumer callback) { this((Node) null, exclusions, callback); } /** * Constructs a new BatchedSubscriber. * - * @param node topic or topics to subscribe to + * @param node topic or topics to subscribe to * @param exclusions predicate for ignoring a subset topic(s) changes - * @param callback action to perform after a batch of changes and on initialization + * @param callback action to perform after a batch of changes and on initialization */ - private BatchedSubscriber(Node node, - BiPredicate exclusions, - @NonNull Consumer callback) { + private BatchedSubscriber(Node node, BiPredicate exclusions, + @NonNull Consumer callback) { this.node = node; this.exclusions = exclusions; this.callback = callback; diff --git a/src/main/java/com/aws/greengrass/util/Coerce.java b/src/main/java/com/aws/greengrass/util/Coerce.java index aa93450fc5..7d3f0217a6 100644 --- a/src/main/java/com/aws/greengrass/util/Coerce.java +++ b/src/main/java/com/aws/greengrass/util/Coerce.java @@ -46,15 +46,15 @@ public static boolean toBoolean(Object o) { } if (o != null) { switch (o.toString()) { - case "true": - case "yes": - case "on": - case "t": - case "y": - case "Y": - return true; - default: - return false; + case "true": + case "yes": + case "on": + case "t": + case "y": + case "Y": + return true; + default: + return false; } } return false; @@ -136,7 +136,6 @@ public static long toLong(Object o) { return 0; } - /** * Convert an object to string or null if it is null. * @@ -203,8 +202,7 @@ public static > T toEnum(Class cl, Object o) { } /** - * Convert an object to an enum of class clazz with a default value of - * dflt. + * Convert an object to an enum of class clazz with a default value of dflt. * * @param clazz enum class to convert into. * @param o object to be converted. @@ -272,7 +270,8 @@ public static Object toObject(String s) throws JsonProcessingException { if (isEmpty(s)) { return ""; } - return toObject(s, new TypeReference() {}); + return toObject(s, new TypeReference() { + }); } /** diff --git a/src/main/java/com/aws/greengrass/util/CommitableFile.java b/src/main/java/com/aws/greengrass/util/CommitableFile.java index 7c27c4b20e..3ad35f3ad7 100644 --- a/src/main/java/com/aws/greengrass/util/CommitableFile.java +++ b/src/main/java/com/aws/greengrass/util/CommitableFile.java @@ -17,9 +17,8 @@ import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; /** - * Equivalent to OutputStream except that it has to be committed in order to be - * made permanent. If it is closed or the process exits before the commit, the old - * version of the file remains. + * Equivalent to OutputStream except that it has to be committed in order to be made permanent. If it is closed or the + * process exits before the commit, the old version of the file remains. */ public final class CommitableFile extends OutputStream implements Commitable { private static final Logger logger = LogManager.getLogger(CommitableFile.class); @@ -44,9 +43,8 @@ private CommitableFile(Path n, Path b, Path t, boolean commitOnClose) throws IOE } /** - * Strangely enough, abandonOnClose is usually the best choice: it interacts - * well with the implicit close() that happens in a try-with-resources where - * files are closed if an exception is tossed. + * Strangely enough, abandonOnClose is usually the best choice: it interacts well with the implicit close() that + * happens in a try-with-resources where files are closed if an exception is tossed. * * @param t Path to write to * @throws IOException if writing fails @@ -62,7 +60,7 @@ public static CommitableFile commitOnClose(Path t) throws IOException { /** * Get a CommitableFile for the given path. * - * @param path path of the new file. + * @param path path of the new file. * @param commitOnClose true if the file should be automatically committed when closed. * @return CommitableFile. * @throws IOException if unable to create/delete the files. @@ -102,7 +100,7 @@ public void close() throws IOException { } /** - * Close and discard the file. The original file remains untouched. + * Close and discard the file. The original file remains untouched. */ @Override public void abandon() { diff --git a/src/main/java/com/aws/greengrass/util/CommitableReader.java b/src/main/java/com/aws/greengrass/util/CommitableReader.java index 0858f60c7d..a569851c3f 100644 --- a/src/main/java/com/aws/greengrass/util/CommitableReader.java +++ b/src/main/java/com/aws/greengrass/util/CommitableReader.java @@ -41,7 +41,9 @@ public void read(CrashableFunction validator) th if (!Files.exists(backup)) { throw e1; } - logger.atWarn().kv("file", target).kv("backup", backup) + logger.atWarn() + .kv("file", target) + .kv("backup", backup) .log("Failed to read file. Try with backup next", e1); try (InputStream b = Files.newInputStream(backup)) { validator.apply(b); diff --git a/src/main/java/com/aws/greengrass/util/CommitableWriter.java b/src/main/java/com/aws/greengrass/util/CommitableWriter.java index ac15fe7d30..b4a8dba78a 100644 --- a/src/main/java/com/aws/greengrass/util/CommitableWriter.java +++ b/src/main/java/com/aws/greengrass/util/CommitableWriter.java @@ -22,9 +22,8 @@ private CommitableWriter(CommitableFile f) { } /** - * Strangely enough, abandonOnClose is usually the best choice: it interacts - * well with the implicit close() that happens in a try-with-resources where - * files are closed if an exception is tossed. + * Strangely enough, abandonOnClose is usually the best choice: it interacts well with the implicit close() that + * happens in a try-with-resources where files are closed if an exception is tossed. * * @param p Path to write to * @throws IOException if writing fails diff --git a/src/main/java/com/aws/greengrass/util/CrashableFunction.java b/src/main/java/com/aws/greengrass/util/CrashableFunction.java index a6135672d0..df9acd41f3 100644 --- a/src/main/java/com/aws/greengrass/util/CrashableFunction.java +++ b/src/main/java/com/aws/greengrass/util/CrashableFunction.java @@ -5,10 +5,9 @@ package com.aws.greengrass.util; - /** - * Like Function, but exceptions pass through. It is normally used in situations where - * the caller is prepared to take corrective action on the exception. + * Like Function, but exceptions pass through. It is normally used in situations where the caller is prepared to take + * corrective action on the exception. */ @FunctionalInterface public interface CrashableFunction { diff --git a/src/main/java/com/aws/greengrass/util/CrashableSupplier.java b/src/main/java/com/aws/greengrass/util/CrashableSupplier.java index 658860f5a1..d2fb7f5053 100644 --- a/src/main/java/com/aws/greengrass/util/CrashableSupplier.java +++ b/src/main/java/com/aws/greengrass/util/CrashableSupplier.java @@ -6,8 +6,8 @@ package com.aws.greengrass.util; /** - * Like Supplier, but exceptions pass through. It is normally used in situations where - * the caller is prepared to take corrective action on the exception. + * Like Supplier, but exceptions pass through. It is normally used in situations where the caller is prepared to take + * corrective action on the exception. */ @FunctionalInterface public interface CrashableSupplier { diff --git a/src/main/java/com/aws/greengrass/util/DefaultConcurrentHashMap.java b/src/main/java/com/aws/greengrass/util/DefaultConcurrentHashMap.java index 27bea2bbf2..9e3e0b54ed 100644 --- a/src/main/java/com/aws/greengrass/util/DefaultConcurrentHashMap.java +++ b/src/main/java/com/aws/greengrass/util/DefaultConcurrentHashMap.java @@ -11,8 +11,7 @@ import java.util.function.Supplier; /** - * A ConcurrentHashMap with default values when using {@code get()}. - * Similar to DefaultDict in Python. + * A ConcurrentHashMap with default values when using {@code get()}. Similar to DefaultDict in Python. */ @SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS") public class DefaultConcurrentHashMap extends ConcurrentHashMap { diff --git a/src/main/java/com/aws/greengrass/util/DependencyOrder.java b/src/main/java/com/aws/greengrass/util/DependencyOrder.java index d63807a6ea..32818f7c9d 100644 --- a/src/main/java/com/aws/greengrass/util/DependencyOrder.java +++ b/src/main/java/com/aws/greengrass/util/DependencyOrder.java @@ -28,7 +28,7 @@ public interface DependencyGetter { */ @SuppressWarnings("PMD.LooseCoupling") public LinkedHashSet computeOrderedDependencies(Set pendingDependencies, - DependencyGetter dependencyGetter) { + DependencyGetter dependencyGetter) { final LinkedHashSet dependencyFound = new LinkedHashSet<>(); while (!pendingDependencies.isEmpty()) { int sz = pendingDependencies.size(); @@ -41,8 +41,9 @@ public LinkedHashSet computeOrderedDependencies(Set pendingDependencies, }); if (sz == pendingDependencies.size()) { // didn't find anything to remove, there must be a cycle - logger.atError().kv("pendingItems", pendingDependencies).log( - "Found potential circular dependencies. Ignoring all pending items"); + logger.atError() + .kv("pendingItems", pendingDependencies) + .log("Found potential circular dependencies. Ignoring all pending items"); break; } } diff --git a/src/main/java/com/aws/greengrass/util/Digest.java b/src/main/java/com/aws/greengrass/util/Digest.java index 0d57a0270c..cbc041c8a6 100644 --- a/src/main/java/com/aws/greengrass/util/Digest.java +++ b/src/main/java/com/aws/greengrass/util/Digest.java @@ -32,9 +32,9 @@ public static String calculate(String utfInput) throws NoSuchAlgorithmException return calculate(SHA_256, utfInput); } - /** * Calculate digest for a UTF_8 encoded string input. + * * @param algorithm the name of the algorithm requested. * @param utfInput String to calculate digest for * @return the base64 encoded digest value for the string @@ -49,10 +49,9 @@ public static String calculate(String algorithm, String utfInput) throws NoSuchA return Base64.getEncoder().encodeToString(messageDigest.digest(utfInput.getBytes(StandardCharsets.UTF_8))); } - - /** * Calculate digest for a UTF_8 encoded string input. + * * @param utfInput String to calculate digest for * @return the base64 encoded digest value for the string * @throws NoSuchAlgorithmException when no implementation for message digest is available @@ -63,12 +62,14 @@ public static String calculateWithUrlEncoderNoPadding(String utfInput) throws No throw new IllegalArgumentException("Input is blank for calculating digest"); } MessageDigest messageDigest = MessageDigest.getInstance(SHA_256); - return Base64.getUrlEncoder().withoutPadding() + return Base64.getUrlEncoder() + .withoutPadding() .encodeToString(messageDigest.digest(utfInput.getBytes(StandardCharsets.UTF_8))); } /** * Compare two utf8 encoded digest strings. + * * @param digest1 first digest to compare * @param digest2 second digest to compare * @return whether two digests are equal diff --git a/src/main/java/com/aws/greengrass/util/EncryptionUtils.java b/src/main/java/com/aws/greengrass/util/EncryptionUtils.java index c8ed8c8c65..8392d06edb 100644 --- a/src/main/java/com/aws/greengrass/util/EncryptionUtils.java +++ b/src/main/java/com/aws/greengrass/util/EncryptionUtils.java @@ -54,7 +54,7 @@ private EncryptionUtils() { * * @param certificatePath certificate file path * @return a list of X590 certificate objects - * @throws IOException file IO error + * @throws IOException file IO error * @throws CertificateException can't populate certificates */ public static List loadX509Certificates(Path certificatePath) @@ -75,7 +75,7 @@ public static PrivateKey loadPrivateKey(Path keyPath) throws IOException, Genera * * @param keyPath key file path * @return an RSA keypair - * @throws IOException file IO error + * @throws IOException file IO error * @throws GeneralSecurityException can't load private key */ public static KeyPair loadPrivateKeyPair(Path keyPath) throws IOException, GeneralSecurityException { @@ -109,8 +109,8 @@ private static KeyPair readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecu KeyFactory keyFactory = KeyFactory.getInstance(RSA_TYPE); KeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec); - RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(privateKey.getModulus(), - privateKey.getPublicExponent()); + RSAPublicKeySpec publicKeySpec = + new RSAPublicKeySpec(privateKey.getModulus(), privateKey.getPublicExponent()); return new KeyPair(keyFactory.generatePublic(publicKeySpec), privateKey); } catch (InvalidKeySpecException e) { exception = e; @@ -119,8 +119,8 @@ private static KeyPair readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecu KeyFactory keyFactory = KeyFactory.getInstance(EC_TYPE); KeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); ECPrivateKey privateKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec); - ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(privateKey.getParams().getGenerator(), - privateKey.getParams()); + ECPublicKeySpec publicKeySpec = + new ECPublicKeySpec(privateKey.getParams().getGenerator(), privateKey.getParams()); return new KeyPair(keyFactory.generatePublic(publicKeySpec), privateKey); } catch (InvalidKeySpecException e) { exception.addSuppressed(e); @@ -132,14 +132,38 @@ private static KeyPair readPkcs1PrivateKey(byte[] pkcs1Bytes) throws GeneralSecu // We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand int pkcs1Length = pkcs1Bytes.length; int totalLength = pkcs1Length + 22; - // reference to https://github.com/Mastercard/client-encryption-java/blob/master/src/main/java/com/mastercard/developer/utils/EncryptionUtils.java#L95-L100 + // reference to + // https://github.com/Mastercard/client-encryption-java/blob/master/src/main/java/com/mastercard/developer/utils/EncryptionUtils.java#L95-L100 // this method can save us from importing BouncyCastle as dependency - byte[] pkcs8Header = {0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte) (totalLength & 0xff), + byte[] pkcs8Header = { + 0x30, + (byte) 0x82, + (byte) ((totalLength >> 8) & 0xff), + (byte) (totalLength & 0xff), // Sequence + total length - 0x2, 0x1, 0x0, // Integer (0) - 0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, + 0x2, + 0x1, + 0x0, // Integer (0) + 0x30, + 0xD, + 0x6, + 0x9, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0xD, + 0x1, + 0x1, + 0x1, + 0x5, + 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL - 0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte) (pkcs1Length & 0xff) + 0x4, + (byte) 0x82, + (byte) ((pkcs1Length >> 8) & 0xff), + (byte) (pkcs1Length & 0xff) // Octet string + length }; byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes); @@ -156,14 +180,13 @@ private static byte[] join(byte[] byteArray1, byte[] byteArray2) { /** * Converts given encoded object to a PEM string. * - * @param encodedObject encoded entity + * @param encodedObject encoded entity * @param pemBoundaryType encoding boundary of pem * @return a PEM string * @throws IOException IOException */ public static String encodeToPem(String pemBoundaryType, byte[] encodedObject) throws IOException { - try (StringWriter str = new StringWriter(); - PemWriter pemWriter = new PemWriter(str)) { + try (StringWriter str = new StringWriter(); PemWriter pemWriter = new PemWriter(str)) { pemWriter.writeObject(pemBoundaryType, encodedObject); pemWriter.close(); // Need to explicitly close this as it is a buffered writer return str.toString(); @@ -174,8 +197,10 @@ public static String encodeToPem(String pemBoundaryType, byte[] encodedObject) t * Copyright (c) 2000 - 2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) * SPDX-License-Identifier: MIT * - *

A generic PEM writer, based on RFC 1421 - * From: https://javadoc.io/static/org.bouncycastle/bcprov-jdk15on/1.62/org/bouncycastle/util/io/pem/PemWriter.html

+ *

+ * A generic PEM writer, based on RFC 1421 From: + * https://javadoc.io/static/org.bouncycastle/bcprov-jdk15on/1.62/org/bouncycastle/util/io/pem/PemWriter.html + *

*/ public static class PemWriter extends BufferedWriter { private static final int LINE_LENGTH = 64; @@ -193,19 +218,17 @@ public PemWriter(Writer out) { /** * Writes a pem encoded string. * - * @param type key type. + * @param type key type. * @param bytes encoded string * @throws IOException IO Exception */ - public void writeObject(String type, byte[] bytes) - throws IOException { + public void writeObject(String type, byte[] bytes) throws IOException { writePreEncapsulationBoundary(type); writeEncoded(bytes); writePostEncapsulationBoundary(type); } - private void writeEncoded(byte[] bytes) - throws IOException { + private void writeEncoded(byte[] bytes) throws IOException { bytes = Base64.getEncoder().encode(bytes); for (int i = 0; i < bytes.length; i += buf.length) { @@ -223,16 +246,12 @@ private void writeEncoded(byte[] bytes) } } - private void writePreEncapsulationBoundary( - String type) - throws IOException { + private void writePreEncapsulationBoundary(String type) throws IOException { this.write("-----BEGIN " + type + "-----"); this.newLine(); } - private void writePostEncapsulationBoundary( - String type) - throws IOException { + private void writePostEncapsulationBoundary(String type) throws IOException { this.write("-----END " + type + "-----"); this.newLine(); } diff --git a/src/main/java/com/aws/greengrass/util/Exec.java b/src/main/java/com/aws/greengrass/util/Exec.java index f17b3914fb..0e1b08172b 100644 --- a/src/main/java/com/aws/greengrass/util/Exec.java +++ b/src/main/java/com/aws/greengrass/util/Exec.java @@ -38,19 +38,17 @@ /** * Vaguely like ProcessBuilder, but more flexible and lambda-friendly. + * *
  * // set wd to current working directory
  * String wd = Exec.sh("pwd");
  *
  * // run a shell in the background, and print "Yahoo!"
  * // when "wifi" appears in the system log
- * Platform.getInstance().createNewProcessRunner()
- * .withShell("tail -F /var/log/system.log")
- * .withOut(str->{
- * if(str.toString().contains("wifi"))
- * System.out.println("Yahoo!");
- * })
- * .background(exc -> System.out.println("exit "+exc));
+ * Platform.getInstance().createNewProcessRunner().withShell("tail -F /var/log/system.log").withOut(str -> {
+ *     if (str.toString().contains("wifi"))
+ *         System.out.println("Yahoo!");
+ * }).background(exc -> System.out.println("exit " + exc));
  * 
*/ public abstract class Exec implements Closeable { @@ -140,7 +138,7 @@ public boolean successful(boolean ignoreStderr) throws InterruptedException, IOE * @return the Path of the command, or null if not found. */ @Nullable - public abstract Path which(String fn); // mirrors shell command + public abstract Path which(String fn); // mirrors shell command protected static String deTilde(String s) { if (s.startsWith("~/")) { @@ -192,6 +190,7 @@ public File cwd() { /** * Set the command to execute. + * * @param c a command. * @return this. */ @@ -361,7 +360,7 @@ public Optional exec() throws InterruptedException, IOException { * * @return String of output. * @throws InterruptedException if thread is interrupted while executing - * @throws IOException if execution of the process fails to start + * @throws IOException if execution of the process fails to start */ public String execAndGetStringOutput() throws InterruptedException, IOException { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/com/aws/greengrass/util/GreengrassServiceClientFactory.java b/src/main/java/com/aws/greengrass/util/GreengrassServiceClientFactory.java index 9320b966de..fc28162af3 100644 --- a/src/main/java/com/aws/greengrass/util/GreengrassServiceClientFactory.java +++ b/src/main/java/com/aws/greengrass/util/GreengrassServiceClientFactory.java @@ -60,7 +60,7 @@ public class GreengrassServiceClientFactory { /** * Constructor with custom endpoint/region configuration. * - * @param deviceConfiguration Device configuration + * @param deviceConfiguration Device configuration */ @Inject public GreengrassServiceClientFactory(DeviceConfiguration deviceConfiguration) { @@ -71,15 +71,18 @@ public GreengrassServiceClientFactory(DeviceConfiguration deviceConfiguration) { } if (validString(node, DEVICE_PARAM_ROOT_CA_PATH) || validString(node, DEVICE_PARAM_CERTIFICATE_FILE_PATH) || validString(node, DEVICE_PARAM_PRIVATE_KEY_PATH)) { - logger.atInfo().kv("node", node.getFullName()).log("Closing cached http client for Greengrass v2 " - + "data client due to device config change"); + logger.atInfo() + .kv("node", node.getFullName()) + .log("Closing cached http client for Greengrass v2 " + + "data client due to device config change"); cleanHttpClient(); } if (validString(node, DEVICE_PARAM_AWS_REGION) || validString(node, DEVICE_PARAM_ROOT_CA_PATH) - || validString(node, DEVICE_PARAM_CERTIFICATE_FILE_PATH) || validString(node, - DEVICE_PARAM_PRIVATE_KEY_PATH) || validString(node, DEVICE_PARAM_GG_DATA_PLANE_PORT) - || validString(node, DEVICE_PARAM_IOT_CRED_ENDPOINT) || validString(node, - DEVICE_PARAM_IOT_DATA_ENDPOINT)) { + || validString(node, DEVICE_PARAM_CERTIFICATE_FILE_PATH) + || validString(node, DEVICE_PARAM_PRIVATE_KEY_PATH) + || validString(node, DEVICE_PARAM_GG_DATA_PLANE_PORT) + || validString(node, DEVICE_PARAM_IOT_CRED_ENDPOINT) + || validString(node, DEVICE_PARAM_IOT_DATA_ENDPOINT)) { logger.atTrace().kv("what", what).kv("node", node.getFullName()).log(); if (deviceConfigChanged.compareAndSet(false, true)) { logger.atDebug().log("Queued re-validation of Greengrass v2 data client"); @@ -124,8 +127,7 @@ private boolean validString(Node node, String key) { } /** - * Retrieve configValidationError. - * Validate again if the device config has changed. + * Retrieve configValidationError. Validate again if the device config has changed. * */ public String getConfigValidationError() { @@ -136,8 +138,9 @@ public String getConfigValidationError() { } /** - * Initializes and returns GreengrassV2DataClient. - * Note that this method can return null if there is a config validation error. + * Initializes and returns GreengrassV2DataClient. Note that this method can return null if there is a config + * validation error. + * * @deprecated use fetchGreengrassV2DataClient instead. * @throws TLSAuthException if the client is not configured properly. */ @@ -159,6 +162,7 @@ public GreengrassV2DataClient getGreengrassV2DataClient() throws TLSAuthExceptio /** * Initializes and returns GreengrassV2DataClient. + * * @throws DeviceConfigurationException when fails to validate configs. * @throws TLSAuthException when fails to configure the client */ @@ -191,24 +195,21 @@ private void configureClient(DeviceConfiguration deviceConfiguration) throws TLS configureHttpClient(deviceConfiguration); } logger.atDebug().log(CONFIGURING_GGV2_INFO_MESSAGE); - String greengrassServiceEndpoint = ClientConfigurationUtils - .getGreengrassServiceEndpoint(deviceConfiguration); + String greengrassServiceEndpoint = ClientConfigurationUtils.getGreengrassServiceEndpoint(deviceConfiguration); GreengrassV2DataEndpointProvider endpointProvider = new GreengrassV2DataEndpointProvider() { @Override public CompletableFuture resolveEndpoint(GreengrassV2DataEndpointParams endpointParams) { - return CompletableFuture.supplyAsync(() -> Endpoint.builder() - .url(URI.create(greengrassServiceEndpoint)) - .build()); + return CompletableFuture + .supplyAsync(() -> Endpoint.builder().url(URI.create(greengrassServiceEndpoint)).build()); } }; clientBuilder = GreengrassV2DataClient.builder() - // Use an empty credential provider because our requests don't need SigV4 - // signing, as they are going through IoT Core instead - .credentialsProvider(AnonymousCredentialsProvider.create()) - .endpointProvider(endpointProvider) - .httpClient(cachedHttpClient) - .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RetryMode.STANDARD).build()); - + // Use an empty credential provider because our requests don't need SigV4 + // signing, as they are going through IoT Core instead + .credentialsProvider(AnonymousCredentialsProvider.create()) + .endpointProvider(endpointProvider) + .httpClient(cachedHttpClient) + .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RetryMode.STANDARD).build()); String region = Coerce.toString(deviceConfiguration.getAWSRegion()); @@ -217,13 +218,13 @@ public CompletableFuture resolveEndpoint(GreengrassV2DataEndpointParam // Region and endpoint are both required when updating endpoint config logger.atDebug("initialize-greengrass-client") .kv("service-endpoint", greengrassServiceEndpoint) - .kv("service-region", region).log(); + .kv("service-region", region) + .log(); clientBuilder.endpointOverride(URI.create(greengrassServiceEndpoint)); clientBuilder.region(Region.of(region)); } else { // This section is to override default region if needed - logger.atDebug("initialize-greengrass-client") - .kv("service-region", region).log(); + logger.atDebug("initialize-greengrass-client").kv("service-region", region).log(); clientBuilder.region(Region.of(region)); } } diff --git a/src/main/java/com/aws/greengrass/util/IamSdkClientFactory.java b/src/main/java/com/aws/greengrass/util/IamSdkClientFactory.java index b5d4fbda2e..2a4568d352 100644 --- a/src/main/java/com/aws/greengrass/util/IamSdkClientFactory.java +++ b/src/main/java/com/aws/greengrass/util/IamSdkClientFactory.java @@ -28,25 +28,30 @@ public final class IamSdkClientFactory { private static final Set> retryableIamExceptions = new HashSet<>( Arrays.asList(IamException.class, LimitExceededException.class, ServiceFailureException.class)); - private static final RetryCondition retryCondition = OrRetryCondition - .create(RetryCondition.defaultRetryCondition(), RetryOnExceptionsCondition.create(retryableIamExceptions)); + private static final RetryCondition retryCondition = OrRetryCondition.create(RetryCondition.defaultRetryCondition(), + RetryOnExceptionsCondition.create(retryableIamExceptions)); - private static final RetryPolicy retryPolicy = - RetryPolicy.builder().numRetries(5).backoffStrategy(BackoffStrategy.defaultThrottlingStrategy()) - .retryCondition(retryCondition).build(); + private static final RetryPolicy retryPolicy = RetryPolicy.builder() + .numRetries(5) + .backoffStrategy(BackoffStrategy.defaultThrottlingStrategy()) + .retryCondition(retryCondition) + .build(); private IamSdkClientFactory() { } /** * Build IamClient. + * * @param awsRegion aws region * @return IamClient instance */ public static IamClient getIamClient(String awsRegion) { Region globalRegionByPartition = RegionUtils.getGlobalRegion(awsRegion); - return IamClient.builder().region(globalRegionByPartition) + return IamClient.builder() + .region(globalRegionByPartition) .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) - .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(retryPolicy).build()).build(); + .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(retryPolicy).build()) + .build(); } } diff --git a/src/main/java/com/aws/greengrass/util/IotSdkClientFactory.java b/src/main/java/com/aws/greengrass/util/IotSdkClientFactory.java index f0c0604b78..f15d52acab 100644 --- a/src/main/java/com/aws/greengrass/util/IotSdkClientFactory.java +++ b/src/main/java/com/aws/greengrass/util/IotSdkClientFactory.java @@ -33,9 +33,9 @@ * Accessor for AWS IoT SDK. */ public final class IotSdkClientFactory { - private static final Set> retryableIoTExceptions = new HashSet<>( - Arrays.asList(ThrottlingException.class, InternalException.class, InternalFailureException.class, - LimitExceededException.class)); + private static final Set> retryableIoTExceptions = + new HashSet<>(Arrays.asList(ThrottlingException.class, InternalException.class, + InternalFailureException.class, LimitExceededException.class)); private IotSdkClientFactory() { } @@ -55,7 +55,7 @@ public static IotClient getIotClient(String awsRegion, EnvironmentStage stage) t /** * Build IotClient for desired region and credentials. * - * @param awsRegion aws region + * @param awsRegion aws region * @param credentialsProvider credentials provider * @return IotClient instance * @throws URISyntaxException when Iot endpoint is malformed @@ -68,44 +68,43 @@ public static IotClient getIotClient(Region awsRegion, AwsCredentialsProvider cr /** * Build IotClient for desired region, stage and credentials. * - * @param awsRegion aws region - * @param stage {@link EnvironmentStage} + * @param awsRegion aws region + * @param stage {@link EnvironmentStage} * @param credentialsProvider credentials provider * @return IotClient instance * @throws URISyntaxException when Iot endpoint is malformed */ public static IotClient getIotClient(Region awsRegion, EnvironmentStage stage, - AwsCredentialsProvider credentialsProvider) throws URISyntaxException { + AwsCredentialsProvider credentialsProvider) throws URISyntaxException { return getIotClient(awsRegion, stage, credentialsProvider, Collections.emptySet()); } /** * Build IotClient for tests with custom retry logic. * - * @param awsRegion aws region + * @param awsRegion aws region * @param additionalRetryableExceptions additional exceptions to retry on * @param stage {@link EnvironmentStage} * @return IotClient instance * @throws URISyntaxException when Iot endpoint is malformed */ public static IotClient getIotClient(String awsRegion, EnvironmentStage stage, - Set> additionalRetryableExceptions) - throws URISyntaxException { + Set> additionalRetryableExceptions) throws URISyntaxException { return getIotClient(Region.of(awsRegion), stage, null, additionalRetryableExceptions); } /** * Build IotClient for desired region, stage and credentials with custom retry logic. - * @param awsRegion aws region - * @param stage {@link EnvironmentStage} - * @param credentialsProvider credentials provider + * + * @param awsRegion aws region + * @param stage {@link EnvironmentStage} + * @param credentialsProvider credentials provider * @param additionalRetryableExceptions additional exceptions to retry on * @return IotClient instance * @throws URISyntaxException when Iot endpoint is malformed */ public static IotClient getIotClient(Region awsRegion, EnvironmentStage stage, - AwsCredentialsProvider credentialsProvider, - Set> additionalRetryableExceptions) + AwsCredentialsProvider credentialsProvider, Set> additionalRetryableExceptions) throws URISyntaxException { Set> allExceptionsToRetryOn = new HashSet<>(); allExceptionsToRetryOn.addAll(retryableIoTExceptions); @@ -118,12 +117,15 @@ public static IotClient getIotClient(Region awsRegion, EnvironmentStage stage, RetryCondition retryCondition = OrRetryCondition.create(RetryCondition.defaultRetryCondition(), RetryOnExceptionsCondition.create(allExceptionsToRetryOn)); - RetryPolicy retryPolicy = RetryPolicy.builder().numRetries(numRetries) - .backoffStrategy(BackoffStrategy.defaultThrottlingStrategy()).retryCondition(retryCondition).build(); - IotClientBuilder iotClientBuilder = - IotClient.builder().region(awsRegion) - .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) - .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(retryPolicy).build()); + RetryPolicy retryPolicy = RetryPolicy.builder() + .numRetries(numRetries) + .backoffStrategy(BackoffStrategy.defaultThrottlingStrategy()) + .retryCondition(retryCondition) + .build(); + IotClientBuilder iotClientBuilder = IotClient.builder() + .region(awsRegion) + .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) + .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(retryPolicy).build()); if (credentialsProvider != null) { iotClientBuilder.credentialsProvider(credentialsProvider); @@ -139,14 +141,13 @@ public static IotClient getIotClient(Region awsRegion, EnvironmentStage stage, @AllArgsConstructor public enum EnvironmentStage { - PROD("prod"), - GAMMA("gamma"), - BETA("beta"); + PROD("prod"), GAMMA("gamma"), BETA("beta"); String value; /** * Convert string to {@link EnvironmentStage}. + * * @param stage The string representation of the environment stage * @return {@link EnvironmentStage} * @throws InvalidEnvironmentStageException when the given stage is invalid diff --git a/src/main/java/com/aws/greengrass/util/LoaderLogsSummarizer.java b/src/main/java/com/aws/greengrass/util/LoaderLogsSummarizer.java index 50ffe886a0..3bb799835b 100644 --- a/src/main/java/com/aws/greengrass/util/LoaderLogsSummarizer.java +++ b/src/main/java/com/aws/greengrass/util/LoaderLogsSummarizer.java @@ -20,8 +20,8 @@ private LoaderLogsSummarizer() { * Summarizes loader logs that can be published as part of the deployment status FSS message when deployment fails * with NRF. * - * @param blob string blob containing loader logs - * @return string containing summarized logs + * @param blob string blob containing loader logs + * @return string containing summarized logs */ public static String summarizeLogs(String blob) { try (Scanner scanner = new Scanner(blob)) { diff --git a/src/main/java/com/aws/greengrass/util/LockFactory.java b/src/main/java/com/aws/greengrass/util/LockFactory.java index c67002f957..bc88778027 100644 --- a/src/main/java/com/aws/greengrass/util/LockFactory.java +++ b/src/main/java/com/aws/greengrass/util/LockFactory.java @@ -23,8 +23,7 @@ public final class LockFactory { } } - private static final CycleDetectingLockFactory factory = - CycleDetectingLockFactory.newInstance(policy); + private static final CycleDetectingLockFactory factory = CycleDetectingLockFactory.newInstance(policy); private LockFactory() { } diff --git a/src/main/java/com/aws/greengrass/util/MqttChunkedPayloadPublisher.java b/src/main/java/com/aws/greengrass/util/MqttChunkedPayloadPublisher.java index 7279694846..761395d74f 100644 --- a/src/main/java/com/aws/greengrass/util/MqttChunkedPayloadPublisher.java +++ b/src/main/java/com/aws/greengrass/util/MqttChunkedPayloadPublisher.java @@ -45,16 +45,20 @@ public void publish(Chunkable chunkablePayload, List variablePayloads) { try { payloadCommonInformationSize = SERIALIZER.writeValueAsBytes(chunkablePayload).length; } catch (JsonProcessingException e) { - logger.atError().cause(e).kv(topicKey, updateTopic) + logger.atError() + .cause(e) + .kv(topicKey, updateTopic) .log("Unable to write common payload as bytes. Dropping the message"); return; } // if common info already exceeds limit, drop the publish request if (payloadCommonInformationSize > maxPayloadLengthBytes) { - logger.atError().kv(topicKey, updateTopic).log("Failed to publish payload via " - + "MqttChunkedPayloadPublisher because the common information payload size " - + "exceeded the max limit allowed"); + logger.atError() + .kv(topicKey, updateTopic) + .log("Failed to publish payload via " + + "MqttChunkedPayloadPublisher because the common information payload size " + + "exceeded the max limit allowed"); return; } @@ -69,8 +73,8 @@ public void publish(Chunkable chunkablePayload, List variablePayloads) { this.mqttClient.publish(PublishRequest.builder() .qos(QualityOfService.AT_LEAST_ONCE) .topic(this.updateTopic) - .payload(payloadInBytes).build()) - .whenComplete((r, t) -> { + .payload(payloadInBytes) + .build()).whenComplete((r, t) -> { if (t == null) { logger.atDebug().kv(topicKey, updateTopic).log("MQTT publish succeeded"); } else { @@ -78,8 +82,11 @@ public void publish(Chunkable chunkablePayload, List variablePayloads) { } }); } catch (JsonProcessingException e) { - logger.atError().cause(e).kv(topicKey, updateTopic).log("Failed to publish message via " - + "MqttChunkedPayloadPublisher. Unable to write message as bytes"); + logger.atError() + .cause(e) + .kv(topicKey, updateTopic) + .log("Failed to publish message via " + + "MqttChunkedPayloadPublisher. Unable to write message as bytes"); } } } @@ -101,23 +108,28 @@ private List> chunkVariablePayloads(Chunkable chunkablePayload, List< return chunkedVariablePayloadList; } } catch (JsonProcessingException e) { - logger.atError().cause(e).kv(topicKey, updateTopic) + logger.atError() + .cause(e) + .kv(topicKey, updateTopic) .log("Unable to write chunkable payload as bytes. Will continue with chunking"); } - chunkedVariablePayloadList.add(new ArrayList<>()); for (T payload : variablePayloads) { // if the single payload size plus common info size exceeds the max limit, drop the payload try { - if (getUpdatedChunkablePayloadSize(chunkablePayload, Collections.singletonList(payload)) - > maxPayloadLengthBytes) { - logger.atWarn().kv(topicKey, updateTopic).log("Dropping a variable payload in " - + "chunkable payload publish because its size exceed the max limit allowed"); + if (getUpdatedChunkablePayloadSize(chunkablePayload, + Collections.singletonList(payload)) > maxPayloadLengthBytes) { + logger.atWarn() + .kv(topicKey, updateTopic) + .log("Dropping a variable payload in " + + "chunkable payload publish because its size exceed the max limit allowed"); continue; } } catch (JsonProcessingException e) { - logger.atError().cause(e).kv(topicKey, updateTopic) + logger.atError() + .cause(e) + .kv(topicKey, updateTopic) .log("Unable to write chunkable payload as bytes. Dropping the variable payload"); continue; } @@ -136,7 +148,9 @@ private List> chunkVariablePayloads(Chunkable chunkablePayload, List< chunk.remove(chunk.size() - 1); } } catch (JsonProcessingException e) { - logger.atError().cause(e).kv(topicKey, updateTopic) + logger.atError() + .cause(e) + .kv(topicKey, updateTopic) .log("Unable to write chunkable payload as bytes. Dropping the variable payload"); chunk.remove(chunk.size() - 1); break; diff --git a/src/main/java/com/aws/greengrass/util/NucleusPaths.java b/src/main/java/com/aws/greengrass/util/NucleusPaths.java index 27485431c1..508854a9a8 100644 --- a/src/main/java/com/aws/greengrass/util/NucleusPaths.java +++ b/src/main/java/com/aws/greengrass/util/NucleusPaths.java @@ -32,7 +32,7 @@ public NucleusPaths(String loaderLogFileName) { } public void initPaths(Path root, Path workPath, Path componentStorePath, Path configPath, Path kernelAlts, - Path deployment, Path cliIpcInfo, Path binPath) throws IOException { + Path deployment, Path cliIpcInfo, Path binPath) throws IOException { setRootPath(root); setConfigPath(configPath); setDeploymentPath(deployment); @@ -199,7 +199,9 @@ public static void setLoggerPath(Path p) throws IOException { } public Path loaderLogsPath() { - return LogManager.getRootLogConfiguration().getStoreDirectory() - .resolve(this.loaderLogFileName).toAbsolutePath(); + return LogManager.getRootLogConfiguration() + .getStoreDirectory() + .resolve(this.loaderLogFileName) + .toAbsolutePath(); } } diff --git a/src/main/java/com/aws/greengrass/util/OrderedExecutorService.java b/src/main/java/com/aws/greengrass/util/OrderedExecutorService.java index 026117aba2..7ad9893927 100644 --- a/src/main/java/com/aws/greengrass/util/OrderedExecutorService.java +++ b/src/main/java/com/aws/greengrass/util/OrderedExecutorService.java @@ -40,13 +40,12 @@ public void execute(Runnable task) { } /** - * Executes the given command at some time in the future. The command may execute in a new thread, - * in a pooled thread, or in the calling thread, at the discretion of the {@code Executor} implementation. - * The tasks with the same key will run sequentially. If no key is provided, the task will executed without - * any ordering. + * Executes the given command at some time in the future. The command may execute in a new thread, in a pooled + * thread, or in the calling thread, at the discretion of the {@code Executor} implementation. The tasks with the + * same key will run sequentially. If no key is provided, the task will executed without any ordering. * - * @param task the runnable task - * @param key The key by which to order the tasks. + * @param task the runnable task + * @param key The key by which to order the tasks. */ public void execute(Runnable task, Object key) { if (key == null) { // if key is null, execute without ordering diff --git a/src/main/java/com/aws/greengrass/util/Permissions.java b/src/main/java/com/aws/greengrass/util/Permissions.java index 7757f1683a..10a856bc71 100644 --- a/src/main/java/com/aws/greengrass/util/Permissions.java +++ b/src/main/java/com/aws/greengrass/util/Permissions.java @@ -19,14 +19,18 @@ public final class Permissions { static Platform platform = Platform.getInstance(); - static final FileSystemPermission OWNER_RWX_ONLY = FileSystemPermission.builder() - .ownerRead(true).ownerWrite(true).ownerExecute(true).build(); - static final FileSystemPermission OWNER_RW_ONLY = FileSystemPermission.builder() - .ownerRead(true).ownerWrite(true).build(); + static final FileSystemPermission OWNER_RWX_ONLY = + FileSystemPermission.builder().ownerRead(true).ownerWrite(true).ownerExecute(true).build(); + static final FileSystemPermission OWNER_RW_ONLY = + FileSystemPermission.builder().ownerRead(true).ownerWrite(true).build(); public static final FileSystemPermission OWNER_RWX_EVERYONE_RX = FileSystemPermission.builder() - .ownerRead(true).ownerWrite(true).ownerExecute(true) - .groupRead(true).groupExecute(true) - .otherRead(true).otherExecute(true) + .ownerRead(true) + .ownerWrite(true) + .ownerExecute(true) + .groupRead(true) + .groupExecute(true) + .otherRead(true) + .otherExecute(true) .build(); private Permissions() { @@ -48,7 +52,7 @@ public static void setArtifactPermission(Path p, FileSystemPermission permission if (Files.isDirectory(p)) { platform.setPermissions(OWNER_RWX_EVERYONE_RX, p); try (Stream files = Files.list(p)) { - for (Iterator it = files.iterator(); it.hasNext(); ) { + for (Iterator it = files.iterator(); it.hasNext();) { setArtifactPermission(it.next(), permission); } } @@ -139,8 +143,7 @@ public static void setPrivateKeyPermission(Path p) throws IOException { */ public static void setIpcSocketPermission(Path p) throws IOException { // note this uses File#set methods as using posix permissions fails. - boolean succeeded = p.toFile().setReadable(true, false) - && p.toFile().setWritable(true, false) + boolean succeeded = p.toFile().setReadable(true, false) && p.toFile().setWritable(true, false) && p.toFile().setExecutable(false, false); if (!succeeded) { throw new IOException("Could not set permissions on " + p.toString()); diff --git a/src/main/java/com/aws/greengrass/util/ProxyUtils.java b/src/main/java/com/aws/greengrass/util/ProxyUtils.java index e379814212..38efb9bb25 100644 --- a/src/main/java/com/aws/greengrass/util/ProxyUtils.java +++ b/src/main/java/com/aws/greengrass/util/ProxyUtils.java @@ -50,11 +50,14 @@ private ProxyUtils() { } /** - *

Returns scheme from the user provided proxy url of the format - * scheme://user:pass@host:port.

+ *

+ * Returns scheme from the user provided proxy url of the format + * scheme://user:pass@host:port. + *

* - *

scheme is required and must be one of http, https, or - * socks5

+ *

+ * scheme is required and must be one of http, https, or socks5 + *

* * @param url User provided URL value from config * @return scheme in scheme://user:pass@host:port @@ -64,10 +67,14 @@ public static String getSchemeFromProxyUrl(String url) { } /** - *

Returns user:pass from the user provided proxy url of the format - * scheme://user:pass@host:port.

+ *

+ * Returns user:pass from the user provided proxy url of the format + * scheme://user:pass@host:port. + *

* - *

user:pass are optional

+ *

+ * user:pass are optional + *

* * @param url User provided URL value from config * @return user:pass in scheme://user:pass@host:port or null if absent @@ -77,10 +84,14 @@ public static String getAuthFromProxyUrl(String url) { } /** - *

Returns host from the user provided proxy url of the format - * scheme://user:pass@host:port.

+ *

+ * Returns host from the user provided proxy url of the format + * scheme://user:pass@host:port. + *

* - *

host is required

+ *

+ * host is required + *

* * @param url User provided URL value from config * @return host in scheme://user:pass@host:port @@ -92,27 +103,31 @@ public static String getHostFromProxyUrl(String url) { private static int getDefaultPortForSchemeFromProxyUrl(String url) { String scheme = getSchemeFromProxyUrl(url); switch (scheme) { - case "http": - return 80; - case "https": - return 443; - case "socks5": - return 1080; - default: - return -1; + case "http": + return 80; + case "https": + return 443; + case "socks5": + return 1080; + default: + return -1; } } /** - *

Returns port from the user provided proxy url of the format - * scheme://user:pass@host:port.

+ *

+ * Returns port from the user provided proxy url of the format + * scheme://user:pass@host:port. + *

* - *

port is optional. If not provided, returns 80 for http, 443 for https, 1080 for socks5, or -1 - * for any other scheme.

+ *

+ * port is optional. If not provided, returns 80 for http, 443 for https, 1080 for socks5, or -1 for + * any other scheme. + *

* * @param url User provided URL value from config * @return port in scheme://user:pass@host:port or the default for the - * scheme, -1 if scheme isn't recognized + * scheme, -1 if scheme isn't recognized */ public static int getPortFromProxyUrl(String url) { int userProvidedPort = URI.create(url).getPort(); @@ -123,10 +138,14 @@ public static int getPortFromProxyUrl(String url) { } /** - *

If the username is provided in the proxy url (i.e. user in - * scheme://user:pass@host:port), it is always returned.

+ *

+ * If the username is provided in the proxy url (i.e. user in + * scheme://user:pass@host:port), it is always returned. + *

* - *

If the username is not provided in the proxy url, then the username config property is returned.

+ *

+ * If the username is not provided in the proxy url, then the username config property is returned. + *

* * @param proxyUrl User specified proxy url * @param proxyUsername User specified proxy username @@ -147,10 +166,14 @@ public static String getProxyUsername(String proxyUrl, String proxyUsername) { } /** - *

If the password is provided in the proxy url (i.e. pass in - * scheme://user:pass@host:port), it is always returned.

+ *

+ * If the password is provided in the proxy url (i.e. pass in + * scheme://user:pass@host:port), it is always returned. + *

* - *

If the password is not provided in the proxy url, then the password config property is returned.

+ *

+ * If the password is not provided in the proxy url, then the password config property is returned. + *

* * @param proxyUrl User specified proxy url * @param proxyPassword User specified proxy password @@ -173,7 +196,9 @@ public static String getProxyPassword(String proxyUrl, String proxyPassword) { } /** - *

Returns whether a proxy is configured in the nucleus device configuration.

+ *

+ * Returns whether a proxy is configured in the nucleus device configuration. + *

* * @param deviceConfiguration contains user specified device values * @return true if a proxy is configured, false otherwise @@ -183,8 +208,8 @@ public static boolean isProxyConfigured(DeviceConfiguration deviceConfiguration) } /** - * Provides a software.amazon.awssdk.crt.http.HttpProxyOptions object that can be used when building various - * CRT library clients (like mqtt and http) + * Provides a software.amazon.awssdk.crt.http.HttpProxyOptions object that can be used when building various CRT + * library clients (like mqtt and http) * * @param deviceConfiguration contains user specified system proxy values * @param tlsContext contains TLS options for proxy connection if an HTTPS proxy is used @@ -192,7 +217,7 @@ public static boolean isProxyConfigured(DeviceConfiguration deviceConfiguration) */ @Nullable public static HttpProxyOptions getHttpProxyOptions(DeviceConfiguration deviceConfiguration, - @NonNull ClientTlsContext tlsContext) { + @NonNull ClientTlsContext tlsContext) { String proxyUrl = deviceConfiguration.getProxyUrl(); if (Utils.isEmpty(proxyUrl)) { return null; @@ -219,7 +244,9 @@ public static HttpProxyOptions getHttpProxyOptions(DeviceConfiguration deviceCon } /** - *

Sets static proxy values to support easy client construction.

+ *

+ * Sets static proxy values to support easy client construction. + *

* * @param deviceConfiguration contains user specified system proxy values */ @@ -228,17 +255,20 @@ public static void setDeviceConfiguration(DeviceConfiguration deviceConfiguratio } /** - *

Boilerplate for providing a proxy configured ApacheHttpClient to AWS SDK v2 client builders.

+ *

+ * Boilerplate for providing a proxy configured ApacheHttpClient to AWS SDK v2 client builders. + *

* - *

If you need to customize the HttpClient, but still need proxy support, use ProxyUtils - * .getProxyConfiguration()

+ *

+ * If you need to customize the HttpClient, but still need proxy support, use ProxyUtils + * .getProxyConfiguration() + *

* - * @return httpClient built with a ProxyConfiguration, if a proxy is configured, otherwise - * a default httpClient + * @return httpClient built with a ProxyConfiguration, if a proxy is configured, otherwise a default httpClient * * @deprecated Using this method in an SDK client builder would create a non-managed HTTP client, which does not - * close when the SDK client is closed. Recommend to use ProxyUtils.getSdkHttpClientBuilder - * instead. + * close when the SDK client is closed. Recommend to use ProxyUtils.getSdkHttpClientBuilder + * instead. * * @see depreacted reason * @@ -249,16 +279,22 @@ public static SdkHttpClient getSdkHttpClient() { } /** - *

Boilerplate for providing a proxy configured ApacheHttpClient builder to AWS SDK v2 client builders.

+ *

+ * Boilerplate for providing a proxy configured ApacheHttpClient builder to AWS SDK v2 client builders. + *

* - *

To support HTTPS proxies and other scenarios, the HttpClient is configured with a trust manager - * containing the root CAs from the nucleus configuration and the JVM's default root CAs.

+ *

+ * To support HTTPS proxies and other scenarios, the HttpClient is configured with a trust manager containing the + * root CAs from the nucleus configuration and the JVM's default root CAs. + *

* - *

If you need to customize the HttpClient, but still need proxy support, use ProxyUtils - * .getProxyConfiguration()

+ *

+ * If you need to customize the HttpClient, but still need proxy support, use ProxyUtils + * .getProxyConfiguration() + *

* - * @return httpClient builder with a ProxyConfiguration, if a proxy is configured, otherwise - * a default httpClient builder + * @return httpClient builder with a ProxyConfiguration, if a proxy is configured, otherwise a default httpClient + * builder */ public static ApacheHttpClient.Builder getSdkHttpClientBuilder() { ProxyConfiguration proxyConfiguration = getProxyConfiguration(); @@ -269,8 +305,7 @@ public static ApacheHttpClient.Builder getSdkHttpClientBuilder() { .proxyConfiguration(proxyConfiguration); } - return withClientSettings(ApacheHttpClient.builder()) - .tlsTrustManagersProvider(ProxyUtils::createTrustManagers); + return withClientSettings(ApacheHttpClient.builder()).tlsTrustManagersProvider(ProxyUtils::createTrustManagers); } private static ApacheHttpClient.Builder withClientSettings(ApacheHttpClient.Builder builder) { @@ -322,8 +357,8 @@ private static TrustManager[] createTrustManagers() { } private static X509Certificate[] getDefaultRootCertificates() throws NoSuchAlgorithmException, KeyStoreException { - TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory defaultTrustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); defaultTrustManagerFactory.init((KeyStore) null); for (TrustManager tm : defaultTrustManagerFactory.getTrustManagers()) { @@ -365,10 +400,12 @@ private static String addAuthToProxyUrl(String proxyUrl, String username, String } /** - *

Boilerplate for providing a ProxyConfiguration to AWS SDK v2 ApacheHttpClients.

+ *

+ * Boilerplate for providing a ProxyConfiguration to AWS SDK v2 ApacheHttpClients. + *

* - * @return ProxyConfiguration built with user proxy values or null if no proxy is configured (null is ignored in - * the SDK) + * @return ProxyConfiguration built with user proxy values or null if no proxy is configured (null is ignored in the + * SDK) */ @SuppressWarnings("PMD.PrematureDeclaration") public static ProxyConfiguration getProxyConfiguration() { @@ -404,9 +441,13 @@ public static ProxyConfiguration getProxyConfiguration() { } /** - *

Provides a url for use in the ALL_PROXY, HTTP_PROXY, and HTTPS_PROXY environment variables.

+ *

+ * Provides a url for use in the ALL_PROXY, HTTP_PROXY, and HTTPS_PROXY environment variables. + *

* - *

If auth info is provided in both the url and username/password fields, then the url value is used.

+ *

+ * If auth info is provided in both the url and username/password fields, then the url value is used. + *

* * @param deviceConfiguration contains user specified system proxy values * @return the proxy url value or an empty string if no proxy is configured @@ -426,7 +467,9 @@ public static String getProxyEnvVarValue(DeviceConfiguration deviceConfiguration } /** - *

Provides a value for use in the NO_PROXY environment variable.

+ *

+ * Provides a value for use in the NO_PROXY environment variable. + *

* * @param deviceConfiguration contains user specified system proxy values * @return localhost plus user provided values or an empty string if no proxy is configured diff --git a/src/main/java/com/aws/greengrass/util/RegionUtils.java b/src/main/java/com/aws/greengrass/util/RegionUtils.java index 9a284d2a36..09c133a640 100644 --- a/src/main/java/com/aws/greengrass/util/RegionUtils.java +++ b/src/main/java/com/aws/greengrass/util/RegionUtils.java @@ -12,73 +12,69 @@ public final class RegionUtils { private static final String IOT_CORE_CONTROL_PLANE_ENDPOINT_FORMAT = "https://%s.%s.iot.%s"; - private static final Map - GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT = ImmutableMap.of( - IotSdkClientFactory.EnvironmentStage.PROD, "https://greengrass-ats.iot.%s.%s:%s", - IotSdkClientFactory.EnvironmentStage.GAMMA, "https://greengrass-ats.gamma.%s.iot.%s:%s", - IotSdkClientFactory.EnvironmentStage.BETA, "https://greengrass-ats.beta.%s.iot.%s:%s" - ); - private static final Map - GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT_CN_NORTH_1 = ImmutableMap.of( - IotSdkClientFactory.EnvironmentStage.PROD, "https://greengrass.ats.iot.%s.%s:%s", - IotSdkClientFactory.EnvironmentStage.GAMMA, "https://greengrass.ats.gamma.%s.iot.%s:%s", - IotSdkClientFactory.EnvironmentStage.BETA, "https://greengrass.ats.beta.%s.iot.%s:%s" - ); - private static final Map - GREENGRASS_CONTROL_PLANE_STAGE_TO_ENDPOINT_FORMAT = ImmutableMap.of( - IotSdkClientFactory.EnvironmentStage.PROD, "https://greengrass.%s.%s", - IotSdkClientFactory.EnvironmentStage.GAMMA, "https://greengrass-gamma.%s.%s", - IotSdkClientFactory.EnvironmentStage.BETA, "https://greengrass-beta2.%s.%s" - ); + private static final Map GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT = + ImmutableMap.of(IotSdkClientFactory.EnvironmentStage.PROD, "https://greengrass-ats.iot.%s.%s:%s", + IotSdkClientFactory.EnvironmentStage.GAMMA, "https://greengrass-ats.gamma.%s.iot.%s:%s", + IotSdkClientFactory.EnvironmentStage.BETA, "https://greengrass-ats.beta.%s.iot.%s:%s"); + private static final Map GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT_CN_NORTH_1 = + ImmutableMap.of(IotSdkClientFactory.EnvironmentStage.PROD, "https://greengrass.ats.iot.%s.%s:%s", + IotSdkClientFactory.EnvironmentStage.GAMMA, "https://greengrass.ats.gamma.%s.iot.%s:%s", + IotSdkClientFactory.EnvironmentStage.BETA, "https://greengrass.ats.beta.%s.iot.%s:%s"); + private static final Map GREENGRASS_CONTROL_PLANE_STAGE_TO_ENDPOINT_FORMAT = + ImmutableMap.of(IotSdkClientFactory.EnvironmentStage.PROD, "https://greengrass.%s.%s", + IotSdkClientFactory.EnvironmentStage.GAMMA, "https://greengrass-gamma.%s.%s", + IotSdkClientFactory.EnvironmentStage.BETA, "https://greengrass-beta2.%s.%s"); private RegionUtils() { } /** * Get Greengrass Control Plane Endpoint by region and stage. + * * @param awsRegion aws region * @param stage environment stage * @return Greengrass control plane endpoint */ public static String getGreengrassControlPlaneEndpoint(String awsRegion, - IotSdkClientFactory.EnvironmentStage stage) { + IotSdkClientFactory.EnvironmentStage stage) { String dnsSuffix = Region.of(awsRegion).metadata().partition().dnsSuffix(); return String.format(GREENGRASS_CONTROL_PLANE_STAGE_TO_ENDPOINT_FORMAT.get(stage), awsRegion, dnsSuffix); } /** * Get Greengrass Data Plane Endpoint by region and stage. + * * @param awsRegion aws region * @param stage environment stage * @param port endpoint port * @return Greengrass ServiceEndpoint */ public static String getGreengrassDataPlaneEndpoint(String awsRegion, IotSdkClientFactory.EnvironmentStage stage, - int port) { + int port) { String dnsSuffix = Region.of(awsRegion).metadata().partition().dnsSuffix(); if (Region.CN_NORTH_1.equals(Region.of(awsRegion))) { // CN_NORTH_1 has a special endpoint format - return String - .format(GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT_CN_NORTH_1.get(stage), awsRegion, dnsSuffix, - port); + return String.format(GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT_CN_NORTH_1.get(stage), awsRegion, + dnsSuffix, port); } return String.format(GREENGRASS_DATA_PLANE_STAGE_TO_ENDPOINT_FORMAT.get(stage), awsRegion, dnsSuffix, port); } /** * Get Iot Core Control Plane Endpoint by region and stage. + * * @param awsRegion aws region * @param stage environment stage * @return Iot Control Plane Endpoint */ - public static String getIotCoreControlPlaneEndpoint(Region awsRegion, - IotSdkClientFactory.EnvironmentStage stage) { + public static String getIotCoreControlPlaneEndpoint(Region awsRegion, IotSdkClientFactory.EnvironmentStage stage) { String dnsSuffix = awsRegion.metadata().partition().dnsSuffix(); return String.format(IOT_CORE_CONTROL_PLANE_ENDPOINT_FORMAT, stage.value, awsRegion, dnsSuffix); } /** * Get global region based on the region partition ID. + * * @param awsRegion aws region * @return Region */ diff --git a/src/main/java/com/aws/greengrass/util/RetryUtils.java b/src/main/java/com/aws/greengrass/util/RetryUtils.java index 63ae1fe30d..d0aaa05500 100644 --- a/src/main/java/com/aws/greengrass/util/RetryUtils.java +++ b/src/main/java/com/aws/greengrass/util/RetryUtils.java @@ -30,16 +30,19 @@ private RetryUtils() { * Run a task with retry. Only exceptions in the retryable exception list are retried. Stop the retry when * interrupted. * - * @param retryConfig retry configuration - * @param task task to run + * @param retryConfig retry configuration + * @param task task to run * @param taskDescription task description - * @param logger logger - * @param return type + * @param logger logger + * @param return type * @return return value * @throws Exception Exception */ - @SuppressWarnings({"PMD.SignatureDeclareThrowsException", "PMD.AvoidCatchingGenericException", - "PMD.AvoidInstanceofChecksInCatchClause"}) + @SuppressWarnings({ + "PMD.SignatureDeclareThrowsException", + "PMD.AvoidCatchingGenericException", + "PMD.AvoidInstanceofChecksInCatchClause" + }) public static T runWithRetry(RetryConfig retryConfig, CrashableSupplier task, String taskDescription, Logger logger) throws Exception { return runWithRetry(DifferentiatedRetryConfig.fromRetryConfig(retryConfig), task, taskDescription, logger); @@ -48,25 +51,27 @@ public static T runWithRetry(RetryConfig retryConfig, CrashableSupplier return type + * @param logger logger + * @param return type * @return return value * @throws Exception Exception */ - @SuppressWarnings({"PMD.SignatureDeclareThrowsException", "PMD.AvoidCatchingGenericException", - "PMD.AvoidInstanceofChecksInCatchClause"}) + @SuppressWarnings({ + "PMD.SignatureDeclareThrowsException", + "PMD.AvoidCatchingGenericException", + "PMD.AvoidInstanceofChecksInCatchClause" + }) public static T runWithRetry(DifferentiatedRetryConfig differentiatedRetryConfig, - CrashableSupplier task, String taskDescription, Logger logger) - throws Exception { + CrashableSupplier task, String taskDescription, Logger logger) throws Exception { long retryInterval = 0; long totalAttempts = 0; long totalMaxAttempts = calculateTotalMaxAttempts(differentiatedRetryConfig); Map attemptMap = new HashMap<>(); - differentiatedRetryConfig.getRetryConfigList() - .forEach(retryConfig -> attemptMap.put(retryConfig, 1)); + differentiatedRetryConfig.getRetryConfigList().forEach(retryConfig -> attemptMap.put(retryConfig, 1)); while (totalAttempts < totalMaxAttempts) { if (Thread.currentThread().isInterrupted()) { @@ -119,12 +124,9 @@ public static T runWithRetry(DifferentiatedRetryConfig differentiatedRetryCo // Use long to avoid integer overflow private static long calculateTotalMaxAttempts(DifferentiatedRetryConfig config) { - return config.getRetryConfigList().stream() - .mapToLong(RetryConfig::getMaxAttempt) - .sum(); + return config.getRetryConfigList().stream().mapToLong(RetryConfig::getMaxAttempt).sum(); } - @Builder(toBuilder = true) @Getter public static class RetryConfig { @@ -146,13 +148,12 @@ public static class DifferentiatedRetryConfig { /** * Create a DifferentiatedRetryConfig from RetryConfig. + * * @param retryConfig retryConfig * @return differentiatedRetryConfig */ public static DifferentiatedRetryConfig fromRetryConfig(RetryConfig retryConfig) { - return DifferentiatedRetryConfig.builder() - .retryConfigList(Collections.singletonList(retryConfig)) - .build(); + return DifferentiatedRetryConfig.builder().retryConfigList(Collections.singletonList(retryConfig)).build(); } // Used for unit test @@ -161,12 +162,10 @@ public void setInitialRetryIntervalForAll(Duration duration) { } } - - /** * Check if given error code qualifies for triggering retry mechanism. * - * @param errorCode retry configuration + * @param errorCode retry configuration * @return boolean */ public static boolean retryErrorCodes(int errorCode) { diff --git a/src/main/java/com/aws/greengrass/util/RootCAUtils.java b/src/main/java/com/aws/greengrass/util/RootCAUtils.java index 8fa599b48e..acb0e130ae 100644 --- a/src/main/java/com/aws/greengrass/util/RootCAUtils.java +++ b/src/main/java/com/aws/greengrass/util/RootCAUtils.java @@ -42,8 +42,9 @@ private RootCAUtils() { } /** - * Download root CA to a local file. - * To support HTTPS proxies and other custom truststore configurations, append to the file if it exists. + * Download root CA to a local file. To support HTTPS proxies and other custom truststore configurations, append to + * the file if it exists. + * * @param f destination file * @param urls list of URLs needs to be downloaded * @throws IOException if download failed @@ -66,10 +67,9 @@ public static void downloadRootCAToFile(File f, String... urls) throws IOExcepti private static void removeDuplicateCertificates(File f) { try { String certificates = new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8); - Set uniqueCertificates = - Arrays.stream(certificates.split(EncryptionUtils.CERTIFICATE_PEM_HEADER)) - .map(s -> s.trim()) - .collect(Collectors.toSet()); + Set uniqueCertificates = Arrays.stream(certificates.split(EncryptionUtils.CERTIFICATE_PEM_HEADER)) + .map(s -> s.trim()) + .collect(Collectors.toSet()); try (BufferedWriter bw = Files.newBufferedWriter(f.toPath(), StandardCharsets.UTF_8)) { for (String certificate : uniqueCertificates) { @@ -88,20 +88,17 @@ private static void removeDuplicateCertificates(File f) { /** * Download content from a URL to a local file. + * * @param url the URL from which the content needs to be downloaded * @param f destination local file * @throws IOException if download failed */ @SuppressWarnings("PMD.AvoidFileStream") public static void downloadFileFromURL(String url, File f) throws IOException { - SdkHttpFullRequest request = SdkHttpFullRequest.builder() - .uri(URI.create(url)) - .method(SdkHttpMethod.GET) - .build(); + SdkHttpFullRequest request = + SdkHttpFullRequest.builder().uri(URI.create(url)).method(SdkHttpMethod.GET).build(); - HttpExecuteRequest executeRequest = HttpExecuteRequest.builder() - .request(request) - .build(); + HttpExecuteRequest executeRequest = HttpExecuteRequest.builder().request(request).build(); try (SdkHttpClient client = ProxyUtils.getSdkHttpClientBuilder().build()) { HttpExecuteResponse executeResponse = client.prepareRequest(executeRequest).call(); @@ -112,8 +109,8 @@ public static void downloadFileFromURL(String url, File f) throws IOException { } try (InputStream inputStream = executeResponse.responseBody().get(); - OutputStream outputStream = Files.newOutputStream(f.toPath(), StandardOpenOption.CREATE, - StandardOpenOption.APPEND, StandardOpenOption.SYNC)) { + OutputStream outputStream = Files.newOutputStream(f.toPath(), StandardOpenOption.CREATE, + StandardOpenOption.APPEND, StandardOpenOption.SYNC)) { IoUtils.copy(inputStream, outputStream); } } @@ -121,6 +118,7 @@ public static void downloadFileFromURL(String url, File f) throws IOException { /** * Download rootCA 3 to root path. + * * @param rootCAPath the root path for CAs * @param urls the CA url array * @return if CA downloaded diff --git a/src/main/java/com/aws/greengrass/util/S3SdkClientFactory.java b/src/main/java/com/aws/greengrass/util/S3SdkClientFactory.java index bb4151af65..79df20043e 100644 --- a/src/main/java/com/aws/greengrass/util/S3SdkClientFactory.java +++ b/src/main/java/com/aws/greengrass/util/S3SdkClientFactory.java @@ -90,10 +90,13 @@ public S3Client getS3Client() throws DeviceConfigurationException { */ public S3Client getClientForRegion(Region r) { handleS3EndpointType(Coerce.toString(deviceConfiguration.gets3EndpointType())); - return clientCache.computeIfAbsent(r, (region) -> S3Client.builder() - .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) - .serviceConfiguration(S3Configuration.builder().useArnRegionEnabled(true).build()) - .credentialsProvider(credentialsProvider).region(r).build()); + return clientCache.computeIfAbsent(r, + (region) -> S3Client.builder() + .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) + .serviceConfiguration(S3Configuration.builder().useArnRegionEnabled(true).build()) + .credentialsProvider(credentialsProvider) + .region(r) + .build()); } /** @@ -102,8 +105,8 @@ public S3Client getClientForRegion(Region r) { * @param type s3EndpointType */ private void handleS3EndpointType(String type) { - //Check if system property and device config are consistent - //If not consistent, set system property according to device config value + // Check if system property and device config are consistent + // If not consistent, set system property according to device config value String s3EndpointSystemProp = System.getProperty(S3_ENDPOINT_PROP_NAME); boolean isGlobal = S3EndpointType.GLOBAL.name().equals(type); @@ -122,7 +125,9 @@ private void handleS3EndpointType(String type) { * Remove the cached client and close it. * */ - @SuppressWarnings({"PMD.CloseResource"}) + @SuppressWarnings({ + "PMD.CloseResource" + }) private void refreshClientCache() { S3Client clientToRemove = clientCache.remove(region); if (clientToRemove != null) { diff --git a/src/main/java/com/aws/greengrass/util/StsSdkClientFactory.java b/src/main/java/com/aws/greengrass/util/StsSdkClientFactory.java index 5da70cb0e1..5bda8e0c93 100644 --- a/src/main/java/com/aws/greengrass/util/StsSdkClientFactory.java +++ b/src/main/java/com/aws/greengrass/util/StsSdkClientFactory.java @@ -28,12 +28,14 @@ public final class StsSdkClientFactory { private static final Set> retryableIamExceptions = new HashSet<>( Arrays.asList(StsException.class, LimitExceededException.class, ServiceFailureException.class)); - private static final RetryCondition retryCondition = OrRetryCondition - .create(RetryCondition.defaultRetryCondition(), RetryOnExceptionsCondition.create(retryableIamExceptions)); + private static final RetryCondition retryCondition = OrRetryCondition.create(RetryCondition.defaultRetryCondition(), + RetryOnExceptionsCondition.create(retryableIamExceptions)); - private static final RetryPolicy retryPolicy = - RetryPolicy.builder().numRetries(5).backoffStrategy(BackoffStrategy.defaultThrottlingStrategy()) - .retryCondition(retryCondition).build(); + private static final RetryPolicy retryPolicy = RetryPolicy.builder() + .numRetries(5) + .backoffStrategy(BackoffStrategy.defaultThrottlingStrategy()) + .retryCondition(retryCondition) + .build(); private StsSdkClientFactory() { } @@ -45,8 +47,10 @@ private StsSdkClientFactory() { * @return StsClient instance */ public static StsClient getStsClient(String awsRegion) { - return StsClient.builder().region(Region.of(awsRegion)) + return StsClient.builder() + .region(Region.of(awsRegion)) .httpClientBuilder(ProxyUtils.getSdkHttpClientBuilder()) - .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(retryPolicy).build()).build(); + .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(retryPolicy).build()) + .build(); } } diff --git a/src/main/java/com/aws/greengrass/util/Utils.java b/src/main/java/com/aws/greengrass/util/Utils.java index 8168b1352c..d43245c17f 100644 --- a/src/main/java/com/aws/greengrass/util/Utils.java +++ b/src/main/java/com/aws/greengrass/util/Utils.java @@ -42,7 +42,9 @@ import java.util.function.BiFunction; import java.util.stream.Collectors; -@SuppressWarnings({"checkstyle:overloadmethodsdeclarationorder", "PMD.AssignmentInOperand"}) +@SuppressWarnings({ + "checkstyle:overloadmethodsdeclarationorder", "PMD.AssignmentInOperand" +}) public final class Utils { public static final Path HOME_PATH = Paths.get(System.getProperty("user.home")); private static final char[] rsChars = "abcdefghjklmnpqrstuvwxyz0123456789".toCharArray(); @@ -63,7 +65,9 @@ private Utils() { * @param closeable object to be closed. * @return error if any. */ - @SuppressWarnings({"PMD.UnnecessaryLocalBeforeReturn", "PMD.AvoidCatchingThrowable"}) + @SuppressWarnings({ + "PMD.UnnecessaryLocalBeforeReturn", "PMD.AvoidCatchingThrowable" + }) public static Throwable close(Object closeable) { if (closeable instanceof Closeable) { try { @@ -83,7 +87,9 @@ public static Throwable close(Object closeable) { * @param flushable object to be flushed. * @return error if any. */ - @SuppressWarnings({"PMD.UnnecessaryLocalBeforeReturn", "PMD.AvoidCatchingThrowable"}) + @SuppressWarnings({ + "PMD.UnnecessaryLocalBeforeReturn", "PMD.AvoidCatchingThrowable" + }) public static Throwable flush(Object flushable) { if (flushable instanceof Flushable) { try { @@ -100,7 +106,9 @@ public static Throwable flush(Object flushable) { /** * Returns true if the two strings are different, including null. * - *

A change from null to "", or vice versa, is not considered a change.

+ *

+ * A change from null to "", or vice versa, is not considered a change. + *

* * @param oldValue first string * @param newValue second string @@ -259,7 +267,7 @@ public static CharSequence deepToString(Object o) { /** * Tries to convert an object into a string with a max length. * - * @param o object to convert to a string. + * @param o object to convert to a string. * @param maxLength maximum length of the returned string. * @return string representation of the given object. */ @@ -290,25 +298,25 @@ public static String dequote(CharSequence cs) { if (c == '\\') { try { switch (c = cs.charAt(++i)) { - case 'b': - sb.append('\b'); - break; - case 'n': - sb.append('\n'); - break; - case 'r': - sb.append('\r'); - break; - case 't': - sb.append('\t'); - break; - case 'u': - sb.append((char) Utils.parseLongChecked(cs, i + 1, i + 5, 16)); - i += 4; - break; - default: - sb.append(c); - break; + case 'b': + sb.append('\b'); + break; + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 't': + sb.append('\t'); + break; + case 'u': + sb.append((char) Utils.parseLongChecked(cs, i + 1, i + 5, 16)); + i += 4; + break; + default: + sb.append(c); + break; } } catch (NumberFormatException t) { break; // bogus string format: ignore quietly @@ -322,11 +330,10 @@ public static String dequote(CharSequence cs) { } /** - * Same as deepToString, but output string as JSON encoded, escaping - * special characters. + * Same as deepToString, but output string as JSON encoded, escaping special characters. * - * @param o object to encode. - * @param sb Appendable object to write the output to. + * @param o object to encode. + * @param sb Appendable object to write the output to. * @param maxLength maximum length of the output string. * @return output length. * @throws IOException if the append fails. @@ -341,24 +348,24 @@ public static int deepToStringQuoted(Object o, Appendable sb, int maxLength) thr for (int i = 0; i < len; i++) { char c = s.charAt(i); switch (c) { - case '\n': - sb.append("\\n"); - olen += 2; - break; - case '\t': - sb.append("\\t"); - olen += 2; - break; - default: - if (c < ' ' || c >= 0xFF || c == 0x7F || c == '"') { - sb.append("\\u"); - appendHex(c, 4, sb); - olen += 6; - } else { - sb.append(c); - olen++; - } - break; + case '\n': + sb.append("\\n"); + olen += 2; + break; + case '\t': + sb.append("\\t"); + olen += 2; + break; + default: + if (c < ' ' || c >= 0xFF || c == 0x7F || c == '"') { + sb.append("\\u"); + appendHex(c, 4, sb); + olen += 6; + } else { + sb.append(c); + olen++; + } + break; } } if (len < l0) { @@ -374,8 +381,8 @@ public static int deepToStringQuoted(Object o, Appendable sb, int maxLength) thr /** * Convert an object to a string representation for human readability. * - * @param o object to convert to string. - * @param sb Appendable to write the string into. + * @param o object to convert to string. + * @param sb Appendable to write the string into. * @param maxLength maximum length of the output. * @return actual output length. * @throws IOException if the append fails. @@ -452,7 +459,7 @@ public static int deepToString(Object o, Appendable sb, int maxLength) throws IO * * @param value value to write as hex. * @param width number of hex characters required in the output. - * @param out Appendable to write to. + * @param out Appendable to write to. * @throws IOException if the append fails. */ public static void appendHex(long value, int width, Appendable out) throws IOException { @@ -465,14 +472,14 @@ public static void appendHex(long value, int width, Appendable out) throws IOExc * Write the given long to the appendable. * * @param value value to write to the appendable. - * @param out Appendable. + * @param out Appendable. * @throws IOException if the append fails. */ public static void appendLong(long value, Appendable out) throws IOException { if (value < 0) { out.append('-'); value = -value; - if (value < 0) { // only one number is its own negative + if (value < 0) { // only one number is its own negative out.append("9223372036854775808"); return; } @@ -496,11 +503,10 @@ public static long parseLong(CharSequence str, int pos, int limit, int radix) { } /** - * Parse an input string as a long. Throws NumberFormatException if the input - * is not a long. + * Parse an input string as a long. Throws NumberFormatException if the input is not a long. * - * @param str input string. - * @param pos starting position in the string. + * @param str input string. + * @param pos starting position in the string. * @param limit stopping position in the string. * @param radix the base of the long. * @return the parsed long. @@ -526,43 +532,42 @@ public static long parseLong(CharBuffer str) { boolean neg = false; int radix = 10; char c; - scanPrefix: - while (true) { + scanPrefix: while (true) { if (str.remaining() <= 0) { return 0; } c = str.get(); switch (c) { - case ' ': - case '+': - break; - case '-': - neg = !neg; - break; - case '0': - radix = 8; - break; - default: - if (radix == OCTAL_RADIX) { - switch (c) { - case 'x': - case 'X': - radix = 16; - break; - case 'b': - case 'B': - radix = 2; - break; - default: - // Stupid cast for jdk 9+ - ((Buffer) str).position(str.position() - 1); - break; - } - } else { + case ' ': + case '+': + break; + case '-': + neg = !neg; + break; + case '0': + radix = 8; + break; + default: + if (radix == OCTAL_RADIX) { + switch (c) { + case 'x': + case 'X': + radix = 16; + break; + case 'b': + case 'B': + radix = 2; + break; + default: // Stupid cast for jdk 9+ ((Buffer) str).position(str.position() - 1); + break; } - break scanPrefix; + } else { + // Stupid cast for jdk 9+ + ((Buffer) str).position(str.position() - 1); + } + break scanPrefix; } } long ret = parseLong(str, radix); @@ -572,7 +577,7 @@ public static long parseLong(CharBuffer str) { /** * Parse long from string with a given base. * - * @param str input string. + * @param str input string. * @param radix base of the long. * @return resulting long. */ @@ -603,11 +608,11 @@ public static long parseLong(CharBuffer str, int radix) { /** * Make an immutable map from provided keys and values. * - * @param k1 first key - * @param v1 first value + * @param k1 first key + * @param v1 first value * @param keyValuePairs remaining keys and values - * @param Map key type - * @param Map value type + * @param Map key type + * @param Map value type * @return immutable map with the provided key-values * @throws IllegalArgumentException if the key-value pairs are not evenly matched */ @@ -670,21 +675,24 @@ public static void deleteFileRecursively(File filePath) throws IOException { * @return inverted map */ public static Map> inverseMap(Map sourceMap) { - return sourceMap.entrySet().stream().collect(Collectors - .toMap(Map.Entry::getValue, t -> new ArrayList<>(Collections.singletonList(t.getKey())), (a, b) -> { - a.addAll(b); - return a; - })); + return sourceMap.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getValue, + t -> new ArrayList<>(Collections.singletonList(t.getKey())), (a, b) -> { + a.addAll(b); + return a; + })); } /** * Read InputStream to a string. + * * @param is input stream * @return string or null if there was an exception */ public static String inputStreamToString(InputStream is) { try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); - BufferedReader sr = new BufferedReader(isr)) { + BufferedReader sr = new BufferedReader(isr)) { return sr.lines().collect(Collectors.joining("\n")); } catch (IOException e) { return null; @@ -698,14 +706,14 @@ public static void copyFolderRecursively(Path src, Path des, CopyOption... optio /** * Copy directory tree recursively. * - * @param src source path - * @param des destination path + * @param src source path + * @param des destination path * @param shouldCopyFile function called for each path to determine if the copy should be attempted - * @param options options specifying how the copy should be done + * @param options options specifying how the copy should be done * @throws IOException on I/O error */ public static void copyFolderRecursively(Path src, Path des, BiFunction shouldCopyFile, - CopyOption... options) throws IOException { + CopyOption... options) throws IOException { Files.walkFileTree(src, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { @@ -724,20 +732,23 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } /** - * CLI params may support file URI. Detect the file URL prefix and read the file content if needed - * Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-parameters-file.html + * CLI params may support file URI. Detect the file URL prefix and read the file content if needed Reference: + * https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-parameters-file.html + * * @param param a value or a file URL * @return the same value or the file content if param is a file URI * @throws URISyntaxException if provided file URI has syntax error * @throws IOException on I/O Error */ public static String loadParamMaybeFile(String param) throws URISyntaxException, IOException { - return param.startsWith(FILE_URL_PREFIX) ? new String(Files.readAllBytes(Paths.get(new URI(param))), - StandardCharsets.UTF_8) : param; + return param.startsWith(FILE_URL_PREFIX) + ? new String(Files.readAllBytes(Paths.get(new URI(param))), StandardCharsets.UTF_8) + : param; } /** * Ensures runnable will be run only once. + * * @param r runnable to be closed. */ public static void once(Runnable r) { diff --git a/src/main/java/com/aws/greengrass/util/orchestration/ProcdUtils.java b/src/main/java/com/aws/greengrass/util/orchestration/ProcdUtils.java index 769f8454e0..fb14240245 100755 --- a/src/main/java/com/aws/greengrass/util/orchestration/ProcdUtils.java +++ b/src/main/java/com/aws/greengrass/util/orchestration/ProcdUtils.java @@ -54,11 +54,11 @@ public boolean setupSystemService(KernelAlternatives kernelAlternatives, Nucleus // The "service" command of procd is a function instead of an executable daemon, and it's not default // configured in "/bin/sh". So we launch this service through System V style. - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,SERVICE_CONFIG_FILE_PATH + " reload", false); - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,SERVICE_CONFIG_FILE_PATH + " stop", false); - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,SERVICE_CONFIG_FILE_PATH + " enable", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, SERVICE_CONFIG_FILE_PATH + " reload", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, SERVICE_CONFIG_FILE_PATH + " stop", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, SERVICE_CONFIG_FILE_PATH + " enable", false); if (start) { - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,SERVICE_CONFIG_FILE_PATH + " start", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, SERVICE_CONFIG_FILE_PATH + " start", false); } logger.atInfo(LOG_EVENT_NAME).log("Successfully set up procd service"); @@ -75,8 +75,7 @@ public boolean setupSystemService(KernelAlternatives kernelAlternatives, Nucleus private void interpolateServiceTemplate(Path src, Path dst, KernelAlternatives kernelAlternatives) throws IOException { String javaHome = System.getProperty("java.home"); - try (BufferedReader r = Files.newBufferedReader(src); - BufferedWriter w = Files.newBufferedWriter(dst)) { + try (BufferedReader r = Files.newBufferedReader(src); BufferedWriter w = Files.newBufferedWriter(dst)) { String line = r.readLine(); while (line != null) { w.write(line.replace(PID_FILE_PARAM, kernelAlternatives.getLoaderPidPath().toString()) diff --git a/src/main/java/com/aws/greengrass/util/orchestration/SystemdUtils.java b/src/main/java/com/aws/greengrass/util/orchestration/SystemdUtils.java index 1ca1b8e320..f4f81539f6 100644 --- a/src/main/java/com/aws/greengrass/util/orchestration/SystemdUtils.java +++ b/src/main/java/com/aws/greengrass/util/orchestration/SystemdUtils.java @@ -48,13 +48,13 @@ public boolean setupSystemService(KernelAlternatives kernelAlternatives, Nucleus interpolateServiceTemplate(serviceTemplate, serviceConfig, kernelAlternatives); Files.copy(serviceConfig, Paths.get(SERVICE_CONFIG_FILE_PATH), REPLACE_EXISTING); - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,"systemctl daemon-reload", false); - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,"systemctl unmask greengrass.service", false); - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,"systemctl stop greengrass.service", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, "systemctl daemon-reload", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, "systemctl unmask greengrass.service", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, "systemctl stop greengrass.service", false); if (start) { - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,"systemctl start greengrass.service", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, "systemctl start greengrass.service", false); } - SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME,"systemctl enable greengrass.service", false); + SystemServiceUtils.runCommand(logger, LOG_EVENT_NAME, "systemctl enable greengrass.service", false); logger.atInfo(LOG_EVENT_NAME).log("Successfully set up systemd service"); return true; @@ -69,8 +69,7 @@ public boolean setupSystemService(KernelAlternatives kernelAlternatives, Nucleus private void interpolateServiceTemplate(Path src, Path dst, KernelAlternatives kernelAlternatives) throws IOException { - try (BufferedReader r = Files.newBufferedReader(src); - BufferedWriter w = Files.newBufferedWriter(dst)) { + try (BufferedReader r = Files.newBufferedReader(src); BufferedWriter w = Files.newBufferedWriter(dst)) { String line = r.readLine(); while (line != null) { w.write(line.replace(PID_FILE_PARAM, kernelAlternatives.getLoaderPidPath().toString()) diff --git a/src/main/java/com/aws/greengrass/util/orchestration/WinswUtils.java b/src/main/java/com/aws/greengrass/util/orchestration/WinswUtils.java index d735375959..047bf3eb72 100644 --- a/src/main/java/com/aws/greengrass/util/orchestration/WinswUtils.java +++ b/src/main/java/com/aws/greengrass/util/orchestration/WinswUtils.java @@ -89,8 +89,7 @@ private void interpolateServiceTemplate(Path src, Path dst, KernelAlternatives k } @SuppressWarnings("PMD.CloseResource") - void runCommand(boolean ignoreError, String... command) - throws IOException, InterruptedException { + void runCommand(boolean ignoreError, String... command) throws IOException, InterruptedException { logger.atDebug(LOG_EVENT_NAME).log("{}", (Object) command); Exec exec = Platform.getInstance().createNewProcessRunner().withExec(command); if (Platform.getInstance().getPrivilegedUser() != null) { @@ -100,11 +99,12 @@ void runCommand(boolean ignoreError, String... command) exec.withGroup(Platform.getInstance().getPrivilegedGroup()); } String commandStr = Arrays.toString(command); - boolean success = exec - .withOut(s -> logger.atWarn(LOG_EVENT_NAME).kv("command", commandStr) - .kv("stdout", s.toString().trim()).log()) - .withErr(s -> logger.atError(LOG_EVENT_NAME).kv("command", commandStr) - .kv("stderr", s.toString().trim()).log()) + boolean success = exec.withOut( + s -> logger.atWarn(LOG_EVENT_NAME).kv("command", commandStr).kv("stdout", s.toString().trim()).log()) + .withErr(s -> logger.atError(LOG_EVENT_NAME) + .kv("command", commandStr) + .kv("stderr", s.toString().trim()) + .log()) .successful(true); if (!success && !ignoreError) { throw new IOException(String.format("Command %s failed", commandStr)); diff --git a/src/main/java/com/aws/greengrass/util/platforms/Platform.java b/src/main/java/com/aws/greengrass/util/platforms/Platform.java index 3e89412371..26fb02c37d 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/Platform.java +++ b/src/main/java/com/aws/greengrass/util/platforms/Platform.java @@ -93,8 +93,7 @@ public static String getPlatformLoaderLogsFileName() { } public abstract Set killProcessAndChildren(Process process, boolean force, Set additionalPids, - UserDecorator decorator) - throws IOException, InterruptedException; + UserDecorator decorator) throws IOException, InterruptedException; public abstract ShellDecorator getShellDecorator(); @@ -128,15 +127,13 @@ public UserPrincipal lookupUserByName(Path path, String name) throws IOException * Set permissions on a path. * * @param permission permissions to set - * @param path path to apply to - * @param options options for how to apply the permission to the path - if none, then the mode is set + * @param path path to apply to + * @param options options for how to apply the permission to the path - if none, then the mode is set * @throws IOException if any exception occurs while changing permissions */ - public void setPermissions(FileSystemPermission permission, Path path, - Option... options) throws IOException { + public void setPermissions(FileSystemPermission permission, Path path, Option... options) throws IOException { // convert to set for easier checking of set options - EnumSet