public Object handleRequest()

in resources/custom-resources/rds-options/src/main/java/com/amazon/aws/partners/saasfactory/saasboost/RdsOptions.java [63:253]


    public Object handleRequest(Map<String, Object> event, Context context) {
        Utils.logRequestEvent(event);

        final String requestType = (String) event.get("RequestType");
        Map<String, Object> resourceProperties = (Map<String, Object>) event.get("ResourceProperties");
        final String table = (String) resourceProperties.get("Table");

        ExecutorService service = Executors.newSingleThreadExecutor();
        ObjectNode responseData = JsonNodeFactory.instance.objectNode();
        try {
            Runnable r = () -> {
                if ("Create".equalsIgnoreCase(requestType)) {
                    LOGGER.info("CREATE");

                    // The same RDS engine version will be available for multiple EC2 instance types so we'll
                    // keep a working set of them to limit the number of describeDBEngineVersions calls


                    LOGGER.info("Building options for {}", AWS_REGION);

                    // For every RDS engine
                    for (Database.RDS_ENGINE engine : Database.RDS_ENGINE.values()) {
                        Map<String, Map<String, Object>> engineVersionCache = new HashMap<>();
                    //for (Database.RDS_ENGINE engine : new Database.RDS_ENGINE[] {Database.RDS_ENGINE.AURORA_PG}) {
                        LOGGER.info("RDS engine {}", engine.name());

                        // Each engine needs its own copy of the list of instance types because
                        // they can be different by engine by region
                        EnumMap<Database.RDS_INSTANCE, Map<String, Object>> instanceMap = new EnumMap<>(Database.RDS_INSTANCE.class);
                        for (Database.RDS_INSTANCE inst : Database.RDS_INSTANCE.values()) {
                        //for (Database.RDS_INSTANCE inst : new Database.RDS_INSTANCE[] {Database.RDS_INSTANCE.T3_MEDIUM}) {
                            Map<String, Object> instanceDetails = new HashMap<>();
                            instanceDetails.put("class", inst.getInstanceClass());
                            instanceDetails.put("description", inst.getDescription());
                            instanceDetails.put("versions", new ArrayList<Map<String, Object>>());
                            instanceMap.put(inst, instanceDetails);
                        }

                        Map<String, Object> engineDetails = new HashMap<>();
                        engineDetails.put("name", engine.getEngine());
                        engineDetails.put("description", engine.getDescription());
                        engineDetails.put("instances", instanceMap);

                        // For every RDS database instance type
                        for (Map.Entry<Database.RDS_INSTANCE, Map<String, Object>> instance : instanceMap.entrySet()) {
                            String instanceClass = (String) instance.getValue().get("class");
                            LOGGER.info("RDS instance class {}", instanceClass);

                            List<Map<String, Object>> versions = new ArrayList<>();
                            Integer maxRecords = 100;
                            String marker = null;
                            do {
                                try {
                                    DescribeOrderableDbInstanceOptionsResponse orderableResponse = rds.describeOrderableDBInstanceOptions(DescribeOrderableDbInstanceOptionsRequest.builder()
                                            .engine(engine.getEngine())
                                            .dbInstanceClass(instanceClass)
                                            .vpc(Boolean.TRUE)
                                            .marker(marker)
                                            .maxRecords(maxRecords)
                                            .build()
                                    );
                                    LOGGER.info("{} {} {} has {} orderable instance options", AWS_REGION.toString(), engine.getEngine(), instanceClass, orderableResponse.orderableDBInstanceOptions().size());
                                    marker = orderableResponse.marker();

                                    // We can get more orderable instances than versions due to storage, IOPs,
                                    // security, networking, and other options. We are just interested in the
                                    // unique set of engine versions per instance type.
                                    //LOGGER.info("Processing {} instance options", orderableResponse.orderableDBInstanceOptions().size());
                                    for (OrderableDBInstanceOption orderable : orderableResponse.orderableDBInstanceOptions()) {
                                        String orderableVersion = orderable.engineVersion();

                                        // Have we already seen this version for this instance type?
                                        boolean newVersion = versions.stream()
                                                .filter(m -> orderableVersion.equals(m.get("version")))
                                                .collect(Collectors.toList())
                                                .isEmpty();

                                        // If we haven't seen this version yet for this instance type
                                        if (newVersion) {
                                            // Maybe we've seen this same version for other instance types
                                            boolean cachedVersion = engineVersionCache.containsKey(orderableVersion);

                                            if (!cachedVersion) {
                                                // Nope... make the call to get the details for this version
                                                //LOGGER.info("Engine version {}", orderableVersion);
                                                DescribeDbEngineVersionsResponse versionsResponse = rds.describeDBEngineVersions(DescribeDbEngineVersionsRequest.builder()
                                                        .engine(engine.getEngine())
                                                        .engineVersion(orderableVersion)
                                                        .build()
                                                );
                                                //LOGGER.info("Processing {} engine versions", versionsResponse.dbEngineVersions().size());
                                                for (DBEngineVersion dbVersion : versionsResponse.dbEngineVersions()) {
                                                    // describeDBEngineVersions brings back multiple results with the same
                                                    // version for at least db.t3.medium aurora-postgresql (bug?)
                                                    boolean uniqueVersionResponse = versions.stream()
                                                            .filter(m -> dbVersion.engineVersion().equals(m.get("version")))
                                                            .collect(Collectors.toList())
                                                            .isEmpty();
                                                    if (uniqueVersionResponse) {
                                                        Map<String, Object> version = new HashMap<>();
                                                        version.put("version", dbVersion.engineVersion());
                                                        version.put("description", dbVersion.dbEngineVersionDescription());
                                                        version.put("family", dbVersion.dbParameterGroupFamily());
                                                        //LOGGER.info("Adding version {}", version.toString());
                                                        versions.add(version);

                                                        // Be sure to cache the results
                                                        //LOGGER.info("Caching version {}", dbVersion.engineVersion());
                                                        engineVersionCache.put(dbVersion.engineVersion(), version);
                                                    } else {
                                                        //LOGGER.info("Skipping duplicate db version response");
                                                    }
                                                }
                                            } else {
                                                // We haven't seen this version for this instance type, but we have for
                                                // other instance types for this RDS engine, so we can use our cached copy
                                                // of the version response.
                                                //LOGGER.info("Engine version {} (cached)", orderableVersion);
                                                versions.add(engineVersionCache.get(orderableVersion));
                                            }
                                        }
                                    }
                                } catch (SdkServiceException rdsError) {
                                    LOGGER.error("rds:describeOrderableDBInstanceOptions | rds:DescribeDbEngineVersions error {}", rdsError.getMessage());
                                    String stackTrace = Utils.getFullStackTrace(rdsError);
                                    LOGGER.error(stackTrace);
                                    responseData.put("Reason", stackTrace);
                                    sendResponse(event, context, "FAILED", responseData);
                                }
                            } while (marker != null && !marker.isEmpty());

                            // Now we have all the unique versions for this instance type
                            LOGGER.info("{} {} {} has {} available versions", AWS_REGION.toString(), engine.getEngine(), instanceClass, versions.size());
                            instanceMap.get(instance.getKey()).put("versions", versions);
                        }

                        // Remove instances that don't have any orderable versions
                        for (Database.RDS_INSTANCE instance : instanceMap.keySet()) {
                            if (((List) instanceMap.get(instance).get("versions")).isEmpty()) {
                                LOGGER.info("Removing unavailable instance class {} for {} in {}", instance.getInstanceClass(), engine.getEngine(), AWS_REGION.toString());
                                instanceMap.remove(instance);
                            }
                        }

                        if (((Map) engineDetails.get("instances")).isEmpty()) {
                            LOGGER.info("Removing unavailable engine {} in {}", engine.getEngine(), AWS_REGION.toString());
                        } else {
                            // Save this engine's options for this region to our database for fast lookup
                            try {
                                Map<String, AttributeValue> item = new HashMap<>();
                                item.put("region", AttributeValue.builder().s(AWS_REGION).build());
                                item.put("engine", AttributeValue.builder().s(engine.name()).build());
                                item.put("options", AttributeValue.builder().m(toAttributeValueMap(engineDetails)).build());
                                PutItemResponse response = ddb.putItem(request -> request.tableName(table).item(item));
                            } catch(SdkServiceException ddbError) {
                                LOGGER.error("dynamodb:putItem error {}", ddbError.getMessage());
                                String stackTrace = Utils.getFullStackTrace(ddbError);
                                LOGGER.error(stackTrace);
                                responseData.put("Reason", stackTrace);
                                sendResponse(event, context, "FAILED", responseData);
                            }
                        }
                    }
                    // Tell CloudFormation we're done
                    sendResponse(event, context, "SUCCESS", responseData);
                } else if ("Update".equalsIgnoreCase(requestType)) {
                    LOGGER.info("UPDATE");
                    sendResponse(event, context, "SUCCESS", responseData);
                } else if ("Delete".equalsIgnoreCase(requestType)) {
                    LOGGER.info("DELETE");
                    sendResponse(event, context, "SUCCESS", responseData);
                } else {
                    LOGGER.error("FAILED unknown requestType " + requestType);
                    responseData.put("Reason", "Unknown RequestType " + requestType);
                    sendResponse(event, context, "FAILED", responseData);
                }
            };
            Future<?> f = service.submit(r);
            f.get(context.getRemainingTimeInMillis() - 1000, TimeUnit.MILLISECONDS);
        } catch (final TimeoutException | InterruptedException | ExecutionException e) {
            // Timed out
            LOGGER.error("FAILED unexpected error or request timed out " + e.getMessage());
            String stackTrace = Utils.getFullStackTrace(e);
            LOGGER.error(stackTrace);
            responseData.put("Reason", stackTrace);
            sendResponse(event, context, "FAILED", responseData);
        } finally {
            service.shutdown();
        }
        return null;
    }