org.dasein.cloud.aws.platform.RDS.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.aws.platform.RDS.java

Source

/**
 * Copyright (C) 2009-2015 Dell, Inc.
 * See annotations for authorship information
 *
 * ====================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ====================================================================
 */

package org.dasein.cloud.aws.platform;

import java.util.*;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.http.HttpStatus;
import org.apache.log4j.Logger;
import org.dasein.cloud.*;
import org.dasein.cloud.aws.AWSCloud;
import org.dasein.cloud.aws.compute.EC2Exception;
import org.dasein.cloud.aws.compute.EC2Method;
import org.dasein.cloud.aws.model.DatabaseProductDefinition;
import org.dasein.cloud.aws.model.DatabaseProvider;
import org.dasein.cloud.aws.model.DatabaseRegion;
import org.dasein.cloud.identity.ServiceAction;
import org.dasein.cloud.network.Direction;
import org.dasein.cloud.network.FirewallRule;
import org.dasein.cloud.network.FirewallSupport;
import org.dasein.cloud.network.Protocol;
import org.dasein.cloud.network.RuleTargetType;
import org.dasein.cloud.platform.*;
import static org.dasein.cloud.platform.DatabaseEngine.*;
import org.dasein.cloud.util.APITrace;
import org.dasein.util.CalendarWrapper;
import org.dasein.util.Jiterator;
import org.dasein.util.JiteratorPopulator;
import org.dasein.util.PopulatorThread;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import static org.dasein.cloud.platform.DatabaseLicenseModel.*;

/**
 * AWS RDS Support
 *
 * @author George Reese
 * @author Stas Maksimov
 * @version 2014.08 deprecated methods moved to capabilities
 * @since ?
 */
public class RDS extends AbstractRelationalDatabaseSupport<AWSCloud> {
    static private final Logger logger = AWSCloud.getLogger(RDS.class);

    static public final String SERVICE_ID = "rds";

    static public final String AUTHORIZE_DB_SECURITY_GROUP_INGRESS = "AuthorizeDBSecurityGroupIngress";
    static public final String CREATE_DB_INSTANCE = "CreateDBInstance";
    static public final String CREATE_DB_PARAMETER_GROUP = "CreateDBParameterGroup";
    static public final String CREATE_DB_SECURITY_GROUP = "CreateDBSecurityGroup";
    static public final String CREATE_SECURITY_GROUP = "CreateSecurityGroup";
    static public final String CREATE_DB_SNAPSHOT = "CreateDBSnapshot";
    static public final String DELETE_DB_INSTANCE = "DeleteDBInstance";
    static public final String DELETE_DB_PARAMETER_GROUP = "DeleteDBParameterGroup";
    static public final String DELETE_DB_SECURITY_GROUP = "DeleteDBSecurityGroup";
    static public final String DELETE_DB_SNAPSHOT = "DeleteDBSnapshot";
    static public final String DESCRIBE_DB_ENGINE_VERSIONS = "DescribeDBEngineVersions";
    static public final String DESCRIBE_DB_INSTANCES = "DescribeDBInstances";
    static public final String DESCRIBE_ENGINE_DEFAULT_PARAMETERS = "DescribeEngineDefaultParameters";
    static public final String DESCRIBE_DB_PARAMETER_GROUPS = "DescribeDBParameterGroups";
    static public final String DESCRIBE_DB_PARAMETERS = "DescribeDBParameters";
    static public final String DESCRIBE_DB_SECURITY_GROUPS = "DescribeDBSecurityGroups";
    static public final String DESCRIBE_DB_SNAPSHOTS = "DescribeDBSnapshots";
    static public final String DESCRIBE_DB_EVENTS = "DescribeDBEvents";
    static public final String MODIFY_DB_INSTANCE = "ModifyDBInstance";
    static public final String MODIFY_DB_PARAMETER_GROUP = "ModifyDBParameterGroup";
    static public final String REBOOT_DB_INSTANCE = "RebootDBInstance";
    static public final String RESET_DB_PARAMETER_GROUP = "ResetDBParameterGroup";
    static public final String RESTORE_DB_INSTANCE_FROM_SNAPSHOT = "RestoreDBInstanceFromDBSnapshot";
    static public final String RESTORE_DB_INSTANCE_TO_TIME = "RestoreDBInstanceToPointInTime";
    static public final String REVOKE_DB_SECURITY_GROUP_INGRESS = "RevokeDBSecurityGroupIngress";

    static private final String AWS_ENGINE_MYSQL = "MySQL";
    static private final String AWS_ENGINE_POSTGRES = "postgres";
    static private final String AWS_ENGINE_ORACLE_SE1 = "oracle-se1";
    static private final String AWS_ENGINE_ORACLE_SE = "oracle-se";
    static private final String AWS_ENGINE_ORACLE_EE = "oracle-ee";
    static private final String AWS_ENGINE_SQLSERVER_EE = "sqlserver-ee";
    static private final String AWS_ENGINE_SQLSERVER_SE = "sqlserver-se";
    static private final String AWS_ENGINE_SQLSERVER_EX = "sqlserver-ex";
    static private final String AWS_ENGINE_SQLSERVER_WEB = "sqlserver-web";

    static public @Nonnull ServiceAction[] asRDSServiceAction(@Nonnull String action) {
        return null; // TODO: implement me
    }

    private volatile transient RDSCapabilities capabilities;

    RDS(AWSCloud provider) {
        super(provider);
    }

    /**
     * Use this to authorize with security groups in EC2-Classic
     * @param groupName
     * @param sourceCidr
     * @throws CloudException
     * @throws InternalException
     */
    private void authorizeClassicDbSecurityGroup(String groupName, String sourceCidr)
            throws CloudException, InternalException {
        Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                AUTHORIZE_DB_SECURITY_GROUP_INGRESS);
        parameters.put("DBSecurityGroupName", groupName);
        parameters.put("CIDRIP", sourceCidr);
        EC2Method method = new EC2Method(SERVICE_ID, getProvider(), parameters);
        try {
            method.invoke();
        } catch (EC2Exception e) {
            String code = e.getCode();
            if (code != null && code.equals("AuthorizationAlreadyExists")) {
                return;
            }
            throw new CloudException(e);
        }
    }

    public void addAccess(String providerDatabaseId, String sourceCidr) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.addAccess");
        try {
            Database db = getDatabase(providerDatabaseId);
            Iterator<String> securityGroups = getSecurityGroups(providerDatabaseId).iterator();
            String groupName;

            if (!securityGroups.hasNext()) {
                // FIXME: not sure we need to do this. a db should always have a security group right from the start
                groupName = createSecurityGroup(providerDatabaseId);
                setSecurityGroup(providerDatabaseId, groupName);
            } else {
                groupName = securityGroups.next();
            }
            String ec2Type = getProvider().getDataCenterServices()
                    .isRegionEC2VPC(getProvider().getContext().getRegionId());
            if (ec2Type.equals(AWSCloud.PLATFORM_EC2)) {
                authorizeClassicDbSecurityGroup(groupName, sourceCidr);
            } else if (getProvider().hasNetworkServices()
                    && getProvider().getNetworkServices().hasFirewallSupport()) {
                FirewallSupport firewallSupport = getProvider().getNetworkServices().getFirewallSupport();
                firewallSupport.authorize(groupName, sourceCidr, Protocol.TCP, db.getHostPort(), db.getHostPort());
            }
        } finally {
            APITrace.end();
        }
    }

    /**
     * Formats a time window as hh24:mi-hh24:mi or ddd:hh24:mi-ddd:hh24:mi
     * @param window
     * @param includeDays must be false for PreferredBackupWindow parameter
     * @return formatted time window text representation
     */
    private String getWindowString(TimeWindow window, boolean includeDays) {
        StringBuilder str = new StringBuilder();
        if (includeDays) {
            if (window.getStartDayOfWeek() == null) {
                str.append("*");
            } else {
                str.append(window.getStartDayOfWeek().getShortString());
            }
            str.append(":");
        }
        str.append(String.format("%02d:%02d", window.getStartHour(), window.getStartMinute()));
        str.append("-");
        if (includeDays) {
            if (window.getEndDayOfWeek() == null) {
                str.append("*");
            } else {
                str.append(window.getEndDayOfWeek().getShortString());
            }
            str.append(":");
        }
        str.append(String.format("%02d:%02d", window.getEndHour(), window.getEndMinute()));
        return str.toString();
    }

    @Override
    public void alterDatabase(String providerDatabaseId, boolean applyImmediately, String productSize,
            int storageInGigabytes, String configurationId, String newAdminUser, String newAdminPassword,
            int newPort, int snapshotRetentionInDays, TimeWindow preferredMaintenanceWindow,
            TimeWindow preferredBackupWindow) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.alterDatabase");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    MODIFY_DB_INSTANCE);
            EC2Method method;

            parameters.put("DBInstanceIdentifier", providerDatabaseId);
            parameters.put("ApplyImmediately", String.valueOf(applyImmediately));
            if (configurationId != null) {
                parameters.put("DBParameterGroupName", configurationId);
            }
            if (preferredMaintenanceWindow != null) {
                String window = getWindowString(preferredMaintenanceWindow, true);

                parameters.put("PreferredMaintenanceWindow", window);
            }
            if (preferredBackupWindow != null) {
                String window = getWindowString(preferredBackupWindow, false);

                parameters.put("PreferredBackupWindow", window);
            }
            if (newAdminPassword != null) {
                parameters.put("MasterUserPassword", newAdminPassword);
            }
            if (storageInGigabytes > 0) {
                parameters.put("AllocatedStorage", String.valueOf(storageInGigabytes));
            }
            if (productSize != null) {
                parameters.put("DBInstanceClass", productSize);
            }
            if (snapshotRetentionInDays > -1) {
                parameters.put("BackupRetentionPeriod", String.valueOf(snapshotRetentionInDays));
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    /**
     *
     * @param groupName
     * @return groupName if successful
     * @throws CloudException
     * @throws InternalException
     */
    private String createSecurityGroup(String groupName) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.createSecurityGroup");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    CREATE_DB_SECURITY_GROUP);
            EC2Method method;

            parameters.put("DBSecurityGroupName", groupName);
            parameters.put("DBSecurityGroupDescription", "Auto-generated DB security group for " + groupName);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                String code = e.getCode();

                if (code != null && code.equals("DBSecurityGroupAlreadyExists")) {
                    return groupName;
                }
                throw new CloudException(e);
            }
            return groupName;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public String createFromScratch(String databaseName, DatabaseProduct product, String engineVersion,
            String withAdminUser, String withAdminPassword, int hostPort) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.createFromScratch");
        try {
            Map<String, String> parameters;
            String id = toIdentifier(databaseName);
            EC2Method method;
            NodeList blocks;
            Document doc;

            if (engineVersion == null) {
                engineVersion = getDefaultVersion(product.getEngine());
            }
            int size = product.getStorageInGigabytes();

            if (size < 5) {
                size = 5;
            }

            parameters = getProvider().getStandardRdsParameters(getProvider().getContext(), CREATE_DB_INSTANCE);
            parameters.put("DBInstanceIdentifier", id);
            parameters.put("AllocatedStorage", String.valueOf(size));
            parameters.put("DBInstanceClass", product.getProductSize());
            parameters.put("Engine", getEngineString(product.getEngine()));
            parameters.put("EngineVersion", engineVersion);
            parameters.put("MasterUsername", withAdminUser);
            parameters.put("MasterUserPassword", withAdminPassword);
            parameters.put("Port", String.valueOf(hostPort));
            if (!isSQLServer(product.getEngine())) {
                String instanceName = toInstanceName(databaseName, product.getEngine());
                parameters.put("DBName", instanceName);
            }

            String ec2Type = getProvider().getDataCenterServices()
                    .isRegionEC2VPC(getProvider().getContext().getRegionId());
            if (ec2Type.equals(AWSCloud.PLATFORM_EC2)) {
                String securityGroupId = createSecurityGroup(id);
                parameters.put("DBSecurityGroups.member.1", securityGroupId);
            }

            // TODO: refactor into a toLicense() method
            String license = "general-public-license";
            switch (product.getLicenseModel()) {
            case BRING_YOUR_OWN_LICENSE:
                license = "bring-your-own-license";
                break;
            case LICENSE_INCLUDED:
                license = "license-included";
                break;
            case POSTGRESQL_LICENSE:
                license = "postgresql-license";
                break;
            }
            parameters.put("LicenseModel", license);

            if (product.isHighAvailability()) {
                parameters.put("MultiAZ", "true");
            } else if (product.getProviderDataCenterId() != null) {
                // set az if not empty, otherwise the region's default is used
                parameters.put("AvailabilityZone", product.getProviderDataCenterId());
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                doc = method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            blocks = doc.getElementsByTagName("DBInstanceIdentifier");
            if (blocks.getLength() > 0) {
                String dbId = blocks.item(0).getFirstChild().getNodeValue().trim();

                // Set tags
                ArrayList<Tag> tags = new ArrayList<Tag>();
                tags.add(new Tag("Name", databaseName));
                getProvider().createTags(SERVICE_ID,
                        "arn:aws:rds:" + getProvider().getContext().getRegionId() + ":"
                                + getProvider().getContext().getAccountNumber().replace("-", "") + ":db:" + dbId,
                        tags.toArray(new Tag[tags.size()]));
                return dbId;
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    public String createFromLatest(String databaseName, String providerDatabaseId, String productSize,
            String providerDataCenterId, int hostPort) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "RDBMS.createFromLatest");
        try {
            Map<String, String> parameters;
            String id = toIdentifier(databaseName);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    RESTORE_DB_INSTANCE_TO_TIME);
            parameters.put("SourceDBInstanceIdentifier", providerDatabaseId);
            parameters.put("UseLatestRestorableTime", "true"); // booleans should be lowercase, as per xsd1.1
            parameters.put("TargetDBInstanceIdentifier", id);
            parameters.put("DBInstanceClass", productSize);
            parameters.put("Port", String.valueOf(hostPort));
            if (providerDataCenterId == null) {
                parameters.put("MultiAZ", "true");
            } else {
                parameters.put("AvailabilityZone", providerDataCenterId);
                parameters.put("MultiAZ", "false");
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                doc = method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            blocks = doc.getElementsByTagName("DBInstanceIdentifier");
            if (blocks.getLength() > 0) {
                ArrayList<Tag> tags = new ArrayList<Tag>();
                tags.add(new Tag("Name", databaseName));
                getProvider().createTags(SERVICE_ID,
                        "arn:aws:rds:" + getProvider().getContext().getRegionId() + ":"
                                + getProvider().getContext().getAccountNumber().replace("-", "") + ":db:" + id,
                        tags.toArray(new Tag[tags.size()]));
                return id;
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    public String createFromSnapshot(String databaseName, String providerDatabaseId, String providerDbSnapshotId,
            String productSize, String providerDataCenterId, int hostPort)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.createFromSnapshot");
        try {
            Map<String, String> parameters;
            String id = toIdentifier(databaseName);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    RESTORE_DB_INSTANCE_FROM_SNAPSHOT);
            parameters.put("DBSnapshotIdentifier", providerDbSnapshotId);
            parameters.put("DBInstanceIdentifier", id);
            parameters.put("DBInstanceClass", productSize);
            parameters.put("Port", String.valueOf(hostPort));
            if (providerDataCenterId == null) {
                parameters.put("MultiAZ", "true");
            } else {
                parameters.put("AvailabilityZone", providerDataCenterId);
                parameters.put("MultiAZ", "false");
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                doc = method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            blocks = doc.getElementsByTagName("DBInstanceIdentifier");
            if (blocks.getLength() > 0) {
                ArrayList<Tag> tags = new ArrayList<Tag>();
                tags.add(new Tag("Name", databaseName));
                getProvider().createTags(SERVICE_ID,
                        "arn:aws:rds:" + getProvider().getContext().getRegionId() + ":"
                                + getProvider().getContext().getAccountNumber().replace("-", "") + ":db:" + id,
                        tags.toArray(new Tag[tags.size()]));
                return id;
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    public String createFromTimestamp(String databaseName, String providerDatabaseId, long beforeTimestamp,
            String productSize, String providerDataCenterId, int hostPort)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "RDBMS.createFromTimestamp");
        try {
            Map<String, String> parameters;
            String id = toIdentifier(databaseName);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    RESTORE_DB_INSTANCE_TO_TIME);
            parameters.put("SourceDBInstanceIdentifier", providerDatabaseId);
            parameters.put("RestoreTime", getProvider().getTimestamp(beforeTimestamp, false));
            parameters.put("TargetDBInstanceIdentifier", id);
            parameters.put("DBInstanceClass", productSize);
            parameters.put("Port", String.valueOf(hostPort));
            if (providerDataCenterId == null) {
                parameters.put("MultiAZ", "true");
            } else {
                parameters.put("AvailabilityZone", providerDataCenterId);
                parameters.put("MultiAZ", "false");
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                doc = method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            blocks = doc.getElementsByTagName("DBInstanceIdentifier");
            if (blocks.getLength() > 0) {
                ArrayList<Tag> tags = new ArrayList<Tag>();
                tags.add(new Tag("Name", databaseName));
                getProvider().createTags(SERVICE_ID,
                        "arn:aws:rds:" + getProvider().getContext().getRegionId() + ":"
                                + getProvider().getContext().getAccountNumber().replace("-", "") + ":db:" + id,
                        tags.toArray(new Tag[tags.size()]));
                return id;
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull RelationalDatabaseCapabilities getCapabilities() throws InternalException, CloudException {
        if (capabilities == null) {
            capabilities = new RDSCapabilities(getProvider());
        }
        return capabilities;
    }

    public DatabaseConfiguration getConfiguration(String providerConfigurationId)
            throws CloudException, InternalException {
        if (providerConfigurationId == null) {
            return null;
        }
        PopulatorThread<DatabaseConfiguration> populator;
        final String id = providerConfigurationId;

        getProvider().hold();
        populator = new PopulatorThread<DatabaseConfiguration>(new JiteratorPopulator<DatabaseConfiguration>() {
            public void populate(Jiterator<DatabaseConfiguration> iterator)
                    throws CloudException, InternalException {
                try {
                    populateConfigurationList(id, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        populator.populate();
        Iterator<DatabaseConfiguration> it = populator.getResult().iterator();

        if (it.hasNext()) {
            return it.next();
        }
        return null;
    }

    public Database getDatabase(String providerDatabaseId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.getDatabase");
        try {
            if (providerDatabaseId == null) {
                return null;
            }
            for (Database database : listDatabases(providerDatabaseId)) {
                if (database.getProviderDatabaseId().equals(providerDatabaseId)) {
                    return database;
                }
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    static private volatile List<DatabaseEngine> engines = null;

    @Override
    public Iterable<DatabaseEngine> getDatabaseEngines() {
        if (engines == null) {
            engines = Arrays.asList(DatabaseEngine.MYSQL, DatabaseEngine.ORACLE_EE, DatabaseEngine.ORACLE_SE,
                    DatabaseEngine.ORACLE_SE1, DatabaseEngine.POSTGRES, DatabaseEngine.SQLSERVER_EE,
                    DatabaseEngine.SQLSERVER_EX, DatabaseEngine.SQLSERVER_SE, DatabaseEngine.SQLSERVER_WEB);
        }
        return engines;
    }

    static private volatile Map<DatabaseEngine, String> defaultVersions = new HashMap<DatabaseEngine, String>();
    static private volatile Map<DatabaseEngine, Collection<String>> engineVersions = new HashMap<DatabaseEngine, Collection<String>>();

    @Override
    public String getDefaultVersion(DatabaseEngine forEngine) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.getDefaultVersion");
        try {
            String version = defaultVersions.get(forEngine);

            if (version == null) {
                Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                        DESCRIBE_DB_ENGINE_VERSIONS);
                EC2Method method;
                Document doc;

                parameters.put("Engine", getEngineString(forEngine));
                parameters.put("DefaultOnly", "true");
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    doc = method.invoke();
                } catch (EC2Exception e) {
                    throw new CloudException(e);
                }
                ;
                NodeList blocks = doc.getElementsByTagName("DBEngineVersions");
                for (int i = 0; i < blocks.getLength(); i++) {
                    NodeList items = blocks.item(i).getChildNodes();

                    for (int j = 0; j < items.getLength(); j++) {
                        Node item = items.item(j);

                        if (item.getNodeName().equals("DBEngineVersion")) {
                            NodeList attrs = item.getChildNodes();

                            for (int k = 0; k < attrs.getLength(); k++) {
                                Node attr = attrs.item(k);

                                if (attr.getNodeName().equals("EngineVersion")) {
                                    version = attr.getFirstChild().getNodeValue().trim();
                                    defaultVersions.put(forEngine, version);
                                    return version;
                                }
                            }
                        }
                    }
                }
            }
            if (version == null) {
                for (String v : getSupportedVersions(forEngine)) {
                    return v;
                }
            }
            return version;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public Iterable<String> getSupportedVersions(DatabaseEngine forEngine)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.getSupportedVersions");
        try {
            Collection<String> versions = engineVersions.get(forEngine);

            if (versions == null) {
                ArrayList<String> list = new ArrayList<String>();
                String marker = null;

                do {
                    Map<String, String> parameters = getProvider()
                            .getStandardRdsParameters(getProvider().getContext(), DESCRIBE_DB_ENGINE_VERSIONS);
                    EC2Method method;
                    Document doc;

                    parameters.put("Engine", getEngineString(forEngine));
                    method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                    try {
                        doc = method.invoke();
                    } catch (EC2Exception e) {
                        throw new CloudException(e);
                    }
                    marker = null;
                    NodeList blocks;
                    blocks = doc.getElementsByTagName("Marker");
                    if (blocks.getLength() > 0) {
                        for (int i = 0; i < blocks.getLength(); i++) {
                            Node item = blocks.item(i);

                            if (item.hasChildNodes()) {
                                marker = item.getFirstChild().getNodeValue().trim();
                            }
                        }
                        if (marker != null) {
                            break;
                        }
                    }
                    blocks = doc.getElementsByTagName("DBEngineVersions");
                    for (int i = 0; i < blocks.getLength(); i++) {
                        NodeList items = blocks.item(i).getChildNodes();

                        for (int j = 0; j < items.getLength(); j++) {
                            Node item = items.item(j);

                            if (item.getNodeName().equals("DBEngineVersion")) {
                                NodeList attrs = item.getChildNodes();

                                for (int k = 0; k < attrs.getLength(); k++) {
                                    Node attr = attrs.item(k);

                                    if (attr.getNodeName().equals("EngineVersion")) {
                                        list.add(attr.getFirstChild().getNodeValue().trim());
                                    }
                                }
                            }
                        }
                    }
                } while (marker != null);
                if (list.isEmpty()) {
                    return Collections.emptyList();
                }
                versions = list;
                engineVersions.put(forEngine, versions);
            }
            return versions;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public Iterable<DatabaseProduct> listDatabaseProducts(DatabaseEngine engine)
            throws CloudException, InternalException {
        List<DatabaseProduct> products = new ArrayList<DatabaseProduct>();
        DatabaseProvider databaseProvider = DatabaseProvider.fromFile("/org/dasein/cloud/aws/dbproducts.json",
                "AWS");

        org.dasein.cloud.aws.model.DatabaseEngine databaseEngine = databaseProvider
                .findEngine(getEngineString(engine));

        if (databaseEngine != null) {
            for (DatabaseRegion region : databaseEngine.getRegions()) {
                if (region.getName().equalsIgnoreCase(getProvider().getContext().getRegionId())) {
                    for (org.dasein.cloud.aws.model.DatabaseProduct databaseProduct : region.getProducts()) {
                        DatabaseProduct product = new DatabaseProduct(databaseProduct.getName());
                        product.setEngine(engine);
                        product.setHighAvailability(databaseProduct.isHighAvailability());
                        product.setStandardHourlyRate(databaseProduct.getHourlyRate());
                        product.setStandardIoRate(databaseProduct.getIoRate());
                        product.setStandardStorageRate(databaseProduct.getStorageRate());
                        DatabaseLicenseModel lic = GENERAL_PUBLIC_LICENSE;
                        if ("included".equalsIgnoreCase(databaseProduct.getLicense())) {
                            lic = LICENSE_INCLUDED;
                        } else if ("byol".equalsIgnoreCase(databaseProduct.getLicense())) {
                            lic = BRING_YOUR_OWN_LICENSE;
                        } else if ("postgres".equalsIgnoreCase(databaseProduct.getLicense())) {
                            lic = POSTGRESQL_LICENSE;
                        }
                        product.setLicenseModel(lic);
                        product.setCurrency(databaseProduct.getCurrency());
                        DatabaseProductDefinition def = databaseProvider
                                .findProductDefinition(databaseProduct.getName());
                        if (def != null) {
                            product.setName(String.format("%.2fGB RAM, %d CPU, %s Network Performance",
                                    def.getMemory(), def.getvCpus(), def.getNetworkPerformance()));
                        }
                        product.setStorageInGigabytes(databaseProduct.getMinStorage());
                        products.add(product);
                    }
                }
            }
        }
        return products;
    }

    private DayOfWeek getDay(String str) {
        if (str.equalsIgnoreCase("Sun")) {
            return DayOfWeek.SUNDAY;
        } else if (str.equalsIgnoreCase("Mon")) {
            return DayOfWeek.MONDAY;
        } else if (str.equalsIgnoreCase("Tue")) {
            return DayOfWeek.TUESDAY;
        } else if (str.equalsIgnoreCase("Wed")) {
            return DayOfWeek.WEDNESDAY;
        } else if (str.equalsIgnoreCase("Thu")) {
            return DayOfWeek.THURSDAY;
        } else if (str.equalsIgnoreCase("Fri")) {
            return DayOfWeek.FRIDAY;
        } else if (str.equalsIgnoreCase("Sat")) {
            return DayOfWeek.SATURDAY;
        }
        return null;
    }

    private TimeWindow getTimeWindow(String start, String end) {
        String[] parts = start.split(":");
        int startHour = 0, endHour = 0;
        int startMinute = 0, endMinute = 0;
        DayOfWeek startDay = null, endDay = null;

        if (parts.length < 3) {
            if (parts.length < 2) {
                try {
                    startHour = Integer.parseInt(start);
                } catch (NumberFormatException ignore) {
                    // ignore
                }
            } else {
                try {
                    startHour = Integer.parseInt(parts[0]);
                } catch (NumberFormatException ignore) {
                    // ignore
                }
                try {
                    startMinute = Integer.parseInt(parts[1]);
                } catch (NumberFormatException e) {
                    // ignore
                }
            }
        } else {
            startDay = getDay(parts[0]);
            try {
                startHour = Integer.parseInt(parts[1]);
            } catch (NumberFormatException ignore) {
                // ignore
            }
            try {
                startMinute = Integer.parseInt(parts[2]);
            } catch (NumberFormatException e) {
                // ignore
            }
        }
        parts = end.split(":");
        if (parts.length < 3) {
            if (parts.length < 2) {
                try {
                    endHour = Integer.parseInt(start);
                } catch (NumberFormatException ignore) {
                    // ignore
                }
            } else {
                try {
                    endHour = Integer.parseInt(parts[0]);
                } catch (NumberFormatException ignore) {
                    // ignore
                }
                try {
                    endMinute = Integer.parseInt(parts[1]);
                } catch (NumberFormatException e) {
                    // ignore
                }
            }
        } else {
            endDay = getDay(parts[0]);
            try {
                endHour = Integer.parseInt(parts[1]);
            } catch (NumberFormatException ignore) {
                // ignore
            }
            try {
                endMinute = Integer.parseInt(parts[2]);
            } catch (NumberFormatException e) {
                // ignore
            }
        }
        TimeWindow window = new TimeWindow();

        window.setStartDayOfWeek(startDay);
        window.setStartHour(startHour);
        window.setStartMinute(startMinute);
        window.setEndDayOfWeek(endDay);
        window.setEndHour(endHour);
        window.setEndMinute(endMinute);
        return window;
    }

    /**
     * Get Amazon-specific engine name
     * @param engine database engine
     * @return Amazon-spefic engine name, returns 'MySQL' if engine was null.
     */
    private String getEngineString(@Nullable DatabaseEngine engine) {
        if (engine == null) {
            return AWS_ENGINE_MYSQL;
        }
        switch (engine) {
        case ORACLE_SE1:
            return AWS_ENGINE_ORACLE_SE1;
        case ORACLE_SE:
            return AWS_ENGINE_ORACLE_SE;
        case ORACLE_EE:
            return AWS_ENGINE_ORACLE_EE;
        case SQLSERVER_SE:
            return AWS_ENGINE_SQLSERVER_SE;
        case SQLSERVER_EE:
            return AWS_ENGINE_SQLSERVER_EE;
        case SQLSERVER_EX:
            return AWS_ENGINE_SQLSERVER_EX;
        case SQLSERVER_WEB:
            return AWS_ENGINE_SQLSERVER_WEB;
        case POSTGRES:
            return AWS_ENGINE_POSTGRES;
        case MYSQL:
        default:
            return AWS_ENGINE_MYSQL;
        }
    }

    @Deprecated
    public String getProviderTermForDatabase(Locale locale) {
        try {
            return getCapabilities().getProviderTermForDatabase(locale);
        } catch (InternalException e) {
        } catch (CloudException e) {
        }
        return "database"; // legacy
    }

    @Deprecated
    public String getProviderTermForSnapshot(Locale locale) {
        try {
            return getCapabilities().getProviderTermForSnapshot(locale);
        } catch (InternalException e) {
        } catch (CloudException e) {
        }
        return "snapshot"; // legacy
    }

    private String getRDSUrl() throws InternalException, CloudException {
        //noinspection ConstantConditions
        return ("https://rds." + getProvider().getContext().getRegionId() + ".amazonaws.com");
    }

    private Iterable<String> getSecurityGroups(String databaseId) throws CloudException, InternalException {
        PopulatorThread<String> populator;
        final String dbId = databaseId;

        getProvider().hold();
        populator = new PopulatorThread<String>(new JiteratorPopulator<String>() {
            public void populate(Jiterator<String> iterator) throws CloudException, InternalException {
                try {
                    populateSecurityGroupIds(dbId, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        populator.populate();
        return populator.getResult();
    }

    public DatabaseSnapshot getSnapshot(String providerDbSnapshotId) throws CloudException, InternalException {
        if (providerDbSnapshotId == null) {
            return null;
        }
        PopulatorThread<DatabaseSnapshot> populator;
        final String id = providerDbSnapshotId;

        getProvider().hold();
        populator = new PopulatorThread<DatabaseSnapshot>(new JiteratorPopulator<DatabaseSnapshot>() {
            public void populate(Jiterator<DatabaseSnapshot> iterator) throws CloudException, InternalException {
                try {
                    populateSnapshotList(id, null, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        populator.populate();
        Iterator<DatabaseSnapshot> it = populator.getResult().iterator();

        if (it.hasNext()) {
            return it.next();
        }
        return null;
    }

    public boolean isSubscribed() throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.isSubscribed");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DESCRIBE_DB_INSTANCES);
            EC2Method method = new EC2Method(SERVICE_ID, getProvider(), parameters);

            try {
                method.invoke();
                return true;
            } catch (EC2Exception e) {
                if (e.getStatus() == HttpStatus.SC_UNAUTHORIZED || e.getStatus() == HttpStatus.SC_FORBIDDEN) {
                    return false;
                }
                String code = e.getCode();

                if (code != null && (code.equals("SubscriptionCheckFailed") || code.equals("AuthFailure")
                        || code.equals("SignatureDoesNotMatch") || code.equals("InvalidClientTokenId")
                        || code.equals("OptInRequired"))) {
                    return false;
                }
                logger.warn(e.getSummary());
                if (logger.isDebugEnabled()) {
                    e.printStackTrace();
                }
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    public Iterable<String> listAccess(String toProviderDatabaseId) throws CloudException, InternalException {
        PopulatorThread<String> idPopulator, accessPopulator;
        final String dbId = toProviderDatabaseId;

        getProvider().hold();
        idPopulator = new PopulatorThread<String>(new JiteratorPopulator<String>() {
            public void populate(Jiterator<String> iterator) throws CloudException, InternalException {
                try {
                    populateSecurityGroupIds(dbId, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        idPopulator.populate();

        final Iterable<String> ids = idPopulator.getResult();

        String ec2Type = getProvider().getDataCenterServices()
                .isRegionEC2VPC(getProvider().getContext().getRegionId());
        if (ec2Type.equals(AWSCloud.PLATFORM_EC2)) {
            getProvider().hold();
            accessPopulator = new PopulatorThread<String>(new JiteratorPopulator<String>() {
                public void populate(Jiterator<String> iterator) throws CloudException, InternalException {
                    try {
                        for (String id : ids) {
                            populateAccess(id, iterator);
                        }
                    } finally {
                        getProvider().release();
                    }
                }
            });
            accessPopulator.populate();
            return accessPopulator.getResult();
        } else {
            List<String> cidrs = new ArrayList<String>();
            if (getProvider().hasNetworkServices() && getProvider().getNetworkServices().hasFirewallSupport()) {
                for (String id : ids) {
                    for (FirewallRule rule : getProvider().getNetworkServices().getFirewallSupport().getRules(id)) {
                        // FIXME: We need to be able to include GLOBAL rule targets too, but they don't have the CIDR
                        if (rule.getDirection().equals(Direction.INGRESS)
                                && rule.getSourceEndpoint().getRuleTargetType().equals(RuleTargetType.CIDR)) {
                            cidrs.add(rule.getSourceEndpoint().getCidr());
                        }
                    }
                }
            }
            return cidrs;
        }
    }

    public Iterable<DatabaseConfiguration> listConfigurations() throws CloudException, InternalException {
        PopulatorThread<DatabaseConfiguration> populator;

        populator = new PopulatorThread<DatabaseConfiguration>(new JiteratorPopulator<DatabaseConfiguration>() {
            public void populate(Jiterator<DatabaseConfiguration> iterator)
                    throws CloudException, InternalException {
                populateConfigurationList(null, iterator);
            }
        });
        populator.populate();
        return populator.getResult();
    }

    @Override
    public @Nonnull Iterable<ResourceStatus> listDatabaseStatus() throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.listDatabaseStatus");
        try {
            ArrayList<ResourceStatus> list = new ArrayList<ResourceStatus>();
            String marker = null;

            do {
                Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                        DESCRIBE_DB_INSTANCES);
                EC2Method method;
                NodeList blocks;
                Document doc;

                if (marker != null) {
                    parameters.put("Marker", marker);
                }
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    doc = method.invoke();
                } catch (EC2Exception e) {
                    throw new CloudException(e);
                }
                marker = null;
                blocks = doc.getElementsByTagName("Marker");
                if (blocks.getLength() > 0) {
                    for (int i = 0; i < blocks.getLength(); i++) {
                        Node item = blocks.item(i);

                        if (item.hasChildNodes()) {
                            marker = item.getFirstChild().getNodeValue().trim();
                        }
                    }
                    if (marker != null) {
                        break;
                    }
                }
                blocks = doc.getElementsByTagName("DBInstances");
                for (int i = 0; i < blocks.getLength(); i++) {
                    NodeList items = blocks.item(i).getChildNodes();

                    for (int j = 0; j < items.getLength(); j++) {
                        Node item = items.item(j);

                        if (item.getNodeName().equals("DBInstance")) {
                            ResourceStatus status = toDatabaseStatus(item);

                            if (status != null) {
                                list.add(status);
                            }
                        }
                    }
                }
            } while (marker != null);
            return list;
        } finally {
            APITrace.end();
        }
    }

    public Iterable<Database> listDatabases() throws CloudException, InternalException {
        return listDatabases(null);
    }

    private Iterable<Database> listDatabases(String targetId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.listDatabases");
        try {
            ArrayList<Database> list = new ArrayList<Database>();
            String marker = null;

            do {
                Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                        DESCRIBE_DB_INSTANCES);
                EC2Method method;
                NodeList blocks;
                Document doc;

                if (marker != null) {
                    parameters.put("Marker", marker);
                }
                if (targetId != null) {
                    parameters.put("DBInstanceIdentifier", targetId);
                }
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    doc = method.invoke();
                } catch (EC2Exception e) {
                    if (targetId != null) {
                        String code = e.getCode();

                        if (code != null && code.equals("DBInstanceNotFound")
                                || code.equals("InvalidParameterValue")) {
                            return list;
                        }
                    }
                    throw new CloudException(e);
                }
                marker = null;
                blocks = doc.getElementsByTagName("Marker");
                if (blocks.getLength() > 0) {
                    for (int i = 0; i < blocks.getLength(); i++) {
                        Node item = blocks.item(i);

                        if (item.hasChildNodes()) {
                            marker = item.getFirstChild().getNodeValue().trim();
                        }
                    }
                    if (marker != null) {
                        break;
                    }
                }
                blocks = doc.getElementsByTagName("DBInstances");
                for (int i = 0; i < blocks.getLength(); i++) {
                    NodeList items = blocks.item(i).getChildNodes();

                    for (int j = 0; j < items.getLength(); j++) {
                        Node item = items.item(j);

                        if (item.getNodeName().equals("DBInstance")) {
                            Database db = toDatabase(item);

                            if (db != null) {
                                list.add(db);
                            }
                        }
                    }
                }
            } while (marker != null);
            return list;
        } finally {
            APITrace.end();
        }
    }

    public Collection<ConfigurationParameter> listParameters(String forProviderConfigurationId)
            throws CloudException, InternalException {
        PopulatorThread<ConfigurationParameter> populator;
        final String id = forProviderConfigurationId;

        getProvider().hold();
        populator = new PopulatorThread<ConfigurationParameter>(new JiteratorPopulator<ConfigurationParameter>() {
            public void populate(Jiterator<ConfigurationParameter> iterator)
                    throws CloudException, InternalException {
                try {
                    populateParameterList(id, null, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        populator.populate();
        return populator.getResult();
    }

    public Collection<ConfigurationParameter> listDefaultParameters(DatabaseEngine engine)
            throws CloudException, InternalException {
        PopulatorThread<ConfigurationParameter> populator;
        final DatabaseEngine dbEngine = engine;

        getProvider().hold();
        populator = new PopulatorThread<ConfigurationParameter>(new JiteratorPopulator<ConfigurationParameter>() {
            public void populate(Jiterator<ConfigurationParameter> iterator)
                    throws CloudException, InternalException {
                try {
                    populateParameterList(null, dbEngine, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        populator.populate();
        return populator.getResult();
    }

    public Iterable<DatabaseSnapshot> listSnapshots(String forOptionalProviderDatabaseId)
            throws CloudException, InternalException {
        PopulatorThread<DatabaseSnapshot> populator;

        getProvider().hold();
        final String id = (forOptionalProviderDatabaseId == null ? null : forOptionalProviderDatabaseId);
        populator = new PopulatorThread<DatabaseSnapshot>(new JiteratorPopulator<DatabaseSnapshot>() {
            public void populate(Jiterator<DatabaseSnapshot> iterator) throws CloudException, InternalException {
                try {
                    populateSnapshotList(null, id, iterator);
                } finally {
                    getProvider().release();
                }
            }
        });
        populator.populate();
        return populator.getResult();
    }

    @Override
    public @Nonnull String[] mapServiceAction(@Nonnull ServiceAction action) {
        return new String[0]; // TODO: implement me
    }

    private void populateAccess(String securityGroupId, Jiterator<String> iterator)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.populateDBSGAccess");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DESCRIBE_DB_SECURITY_GROUPS);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("DBSecurityGroupName", securityGroupId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                doc = method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            blocks = doc.getElementsByTagName("DBSecurityGroups");
            for (int i = 0; i < blocks.getLength(); i++) {
                NodeList items = blocks.item(i).getChildNodes();

                for (int j = 0; j < items.getLength(); j++) {
                    Node item = items.item(j);

                    if (item.getNodeName().equals("DBSecurityGroup")) {
                        NodeList attrs = item.getChildNodes();

                        for (int k = 0; k < attrs.getLength(); k++) {
                            Node attr = attrs.item(k);
                            String name;

                            name = attr.getNodeName();
                            if (name.equalsIgnoreCase("IPRanges")) {
                                if (attr.hasChildNodes()) {
                                    NodeList ranges = attr.getChildNodes();

                                    for (int l = 0; l < ranges.getLength(); l++) {
                                        Node range = ranges.item(l);

                                        if (range.hasChildNodes()) {
                                            NodeList rangeAttrs = range.getChildNodes();
                                            String cidr = null;
                                            boolean authorized = false;

                                            for (int m = 0; m < rangeAttrs.getLength(); m++) {
                                                Node ra = rangeAttrs.item(m);

                                                if (ra.getNodeName().equalsIgnoreCase("Status")) {
                                                    authorized = ra.getFirstChild().getNodeValue().trim()
                                                            .equalsIgnoreCase("authorized");
                                                } else if (ra.getNodeName().equalsIgnoreCase("CIDRIP")) {
                                                    cidr = ra.getFirstChild().getNodeValue().trim();
                                                }
                                            }
                                            if (cidr != null && authorized) {
                                                iterator.push(cidr);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            APITrace.end();
        }
    }

    private void populateConfigurationList(String targetId, Jiterator<DatabaseConfiguration> iterator)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.populateConfigurationList");
        try {
            String marker = null;

            do {
                Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                        DESCRIBE_DB_PARAMETER_GROUPS);
                EC2Method method;
                NodeList blocks;
                Document doc;

                if (marker != null) {
                    parameters.put("Marker", marker);
                }
                if (targetId != null) {
                    parameters.put("DBParameterGroupName", targetId);
                }
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    doc = method.invoke();
                } catch (EC2Exception e) {
                    throw new CloudException(e);
                }
                marker = null;
                blocks = doc.getElementsByTagName("Marker");
                if (blocks.getLength() > 0) {
                    for (int i = 0; i < blocks.getLength(); i++) {
                        Node item = blocks.item(i);

                        if (item.hasChildNodes()) {
                            marker = item.getFirstChild().getNodeValue().trim();
                        }
                    }
                    if (marker != null) {
                        break;
                    }
                }
                blocks = doc.getElementsByTagName("DBParameterGroups");
                for (int i = 0; i < blocks.getLength(); i++) {
                    NodeList items = blocks.item(i).getChildNodes();

                    for (int j = 0; j < items.getLength(); j++) {
                        Node item = items.item(j);

                        if (item.getNodeName().equals("DBParameterGroup")) {
                            DatabaseConfiguration cfg = toConfiguration(item);

                            if (cfg != null) {
                                iterator.push(cfg);
                            }
                        }
                    }
                }
            } while (marker != null);
        } finally {
            APITrace.end();
        }
    }

    private void populateParameterList(String cfgId, DatabaseEngine engine,
            Jiterator<ConfigurationParameter> iterator) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.populateParameterList");
        try {
            String marker = null;

            do {
                Map<String, String> parameters;
                EC2Method method;
                NodeList blocks;
                Document doc;

                if (cfgId != null) {
                    parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                            DESCRIBE_DB_PARAMETERS);
                    parameters.put("DBParameterGroupName", cfgId);
                } else {
                    parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                            DESCRIBE_ENGINE_DEFAULT_PARAMETERS);
                    parameters.put("Engine", getEngineString(engine));
                }
                if (marker != null) {
                    parameters.put("Marker", marker);
                }
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    doc = method.invoke();
                } catch (EC2Exception e) {
                    throw new CloudException(e);
                }
                marker = null;
                blocks = doc.getElementsByTagName("Marker");
                if (blocks.getLength() > 0) {
                    for (int i = 0; i < blocks.getLength(); i++) {
                        Node item = blocks.item(i);

                        if (item.hasChildNodes()) {
                            marker = item.getFirstChild().getNodeValue().trim();
                        }
                    }
                    if (marker != null) {
                        break;
                    }
                }
                blocks = doc.getElementsByTagName("Parameters");
                for (int i = 0; i < blocks.getLength(); i++) {
                    NodeList items = blocks.item(i).getChildNodes();

                    for (int j = 0; j < items.getLength(); j++) {
                        Node item = items.item(j);

                        if (item.getNodeName().equals("Parameter")) {
                            ConfigurationParameter param = toParameter(item);

                            if (param != null) {
                                iterator.push(param);
                            }
                        }
                    }
                }
            } while (marker != null);
        } finally {
            APITrace.end();
        }
    }

    private void populateSecurityGroupIds(String providerDatabaseId, Jiterator<String> iterator)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.populateDBSecurityGroups");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DESCRIBE_DB_INSTANCES);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("DBInstanceIdentifier", providerDatabaseId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                doc = method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            blocks = doc.getElementsByTagName("DBInstances");
            for (int i = 0; i < blocks.getLength(); i++) {
                NodeList items = blocks.item(i).getChildNodes();

                for (int j = 0; j < items.getLength(); j++) {
                    Node item = items.item(j);

                    if (item.getNodeName().equals("DBInstance")) {
                        NodeList attrs = item.getChildNodes();

                        for (int k = 0; k < attrs.getLength(); k++) {
                            Node attr = attrs.item(k);
                            String name;

                            name = attr.getNodeName();
                            if (name.equalsIgnoreCase("DBSecurityGroups")) {
                                if (attr.hasChildNodes()) {
                                    NodeList groups = attr.getChildNodes();

                                    for (int l = 0; l < groups.getLength(); l++) {
                                        Node group = groups.item(l);

                                        if (group.hasChildNodes()) {
                                            NodeList groupAttrs = group.getChildNodes();
                                            String groupName = null;
                                            boolean active = false;

                                            for (int m = 0; m < groupAttrs.getLength(); m++) {
                                                Node ga = groupAttrs.item(m);

                                                if (ga.getNodeName().equalsIgnoreCase("Status")) {
                                                    active = ga.getFirstChild().getNodeValue().trim()
                                                            .equalsIgnoreCase("active");
                                                } else if (ga.getNodeName()
                                                        .equalsIgnoreCase("DBSecurityGroupName")) {
                                                    groupName = ga.getFirstChild().getNodeValue().trim();
                                                }
                                            }
                                            if (groupName != null && active) {
                                                iterator.push(groupName);
                                            }
                                        }
                                    }
                                }
                            } else if (name.equalsIgnoreCase("VpcSecurityGroups")) {
                                if (attr.hasChildNodes()) {
                                    NodeList groups = attr.getChildNodes(); // VpcSecurityGroupMembership

                                    for (int l = 0; l < groups.getLength(); l++) {
                                        Node group = groups.item(l);

                                        if (group.hasChildNodes()) {
                                            NodeList groupAttrs = group.getChildNodes();
                                            String groupName = null;
                                            boolean active = false;

                                            for (int m = 0; m < groupAttrs.getLength(); m++) {
                                                Node ga = groupAttrs.item(m);

                                                if (ga.getNodeName().equalsIgnoreCase("Status")) {
                                                    active = ga.getFirstChild().getNodeValue().trim()
                                                            .equalsIgnoreCase("active");
                                                } else if (ga.getNodeName()
                                                        .equalsIgnoreCase("VpcSecurityGroupId")) {
                                                    groupName = ga.getFirstChild().getNodeValue().trim();
                                                }
                                            }
                                            if (groupName != null && active) {
                                                iterator.push(groupName);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            APITrace.end();
        }
    }

    private void populateSnapshotList(String snapshotId, String databaseId, Jiterator<DatabaseSnapshot> iterator)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.populateDBSnapshotList");
        try {
            String marker = null;

            do {
                Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                        DESCRIBE_DB_SNAPSHOTS);
                EC2Method method;
                NodeList blocks;
                Document doc;

                if (marker != null) {
                    parameters.put("Marker", marker);
                }
                if (snapshotId != null) {
                    parameters.put("DBSnapshotIdentifier", snapshotId);
                }
                if (databaseId != null) {
                    parameters.put("DBInstanceIdentifier", databaseId);
                }
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    doc = method.invoke();
                } catch (EC2Exception e) {
                    String code = e.getCode();

                    if (code != null && code.equals("DBSnapshotNotFound")) {
                        return;
                    }
                    throw new CloudException(e);
                }
                marker = null;
                blocks = doc.getElementsByTagName("Marker");
                if (blocks.getLength() > 0) {
                    for (int i = 0; i < blocks.getLength(); i++) {
                        Node item = blocks.item(i);

                        if (item.hasChildNodes()) {
                            marker = item.getFirstChild().getNodeValue().trim();
                        }
                    }
                    if (marker != null) {
                        break;
                    }
                }
                blocks = doc.getElementsByTagName("DBSnapshots");
                for (int i = 0; i < blocks.getLength(); i++) {
                    NodeList items = blocks.item(i).getChildNodes();

                    for (int j = 0; j < items.getLength(); j++) {
                        Node item = items.item(j);

                        if (item.getNodeName().equals("DBSnapshot")) {
                            DatabaseSnapshot snapshot = toSnapshot(item);

                            if (snapshot != null) {
                                iterator.push(snapshot);
                            }
                        }
                    }
                }
            } while (marker != null);
        } finally {
            APITrace.end();
        }
    }

    public void removeConfiguration(String providerConfigurationId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.removeConfiguration");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DELETE_DB_PARAMETER_GROUP);
            EC2Method method;

            parameters.put("DBParameterGroupName", providerConfigurationId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    private void removeSecurityGroup(String securityGroupId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.removeSecurityGroup");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DELETE_DB_SECURITY_GROUP);
            EC2Method method;

            parameters.put("DBSecurityGroupName", securityGroupId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw e;
            }
        } finally {
            APITrace.end();
        }
    }

    public void removeDatabase(String providerDatabaseId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.removeDatabase");
        try {
            long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 10L);
            Database db = getDatabase(providerDatabaseId);

            while (timeout > System.currentTimeMillis()) {
                if (db == null || DatabaseState.DELETED.equals(db.getCurrentState())) {
                    return;
                }
                if (DatabaseState.AVAILABLE.equals(db.getCurrentState())) {
                    break;
                }
                try {
                    Thread.sleep(15000L);
                } catch (InterruptedException ignore) {
                }
                try {
                    db = getDatabase(providerDatabaseId);
                } catch (Throwable ignore) {
                }
            }
            Iterable<String> securityGroups = getSecurityGroups(providerDatabaseId);

            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DELETE_DB_INSTANCE);
            EC2Method method;

            parameters.put("DBInstanceIdentifier", providerDatabaseId);
            parameters.put("FinalDBSnapshotIdentifier",
                    providerDatabaseId + "-FINAL-" + System.currentTimeMillis());
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                parameters = getProvider().getStandardRdsParameters(getProvider().getContext(), DELETE_DB_INSTANCE);
                parameters.put("DBInstanceIdentifier", providerDatabaseId);
                parameters.put("SkipFinalSnapshot", "true");
                method = new EC2Method(SERVICE_ID, getProvider(), parameters);
                try {
                    method.invoke();
                } catch (EC2Exception again) {
                    throw new CloudException(e);
                }
            }
            try {
                for (String securityGroupId : securityGroups) {
                    if (securityGroupId.equals(providerDatabaseId)) {
                        for (int i = 0; i < 3; i++) {
                            try {
                                removeSecurityGroup(securityGroupId);
                                break;
                            } catch (EC2Exception e) {
                                if (e.getProviderCode().equalsIgnoreCase("InvalidDBSecurityGroupState")) {
                                    try {
                                        Thread.sleep(6000L);
                                    } catch (InterruptedException ie) {
                                    }
                                    // ignore this because it means it is a shared security group
                                } else
                                    throw new CloudException(e);
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        } finally {
            APITrace.end();
        }
    }

    public void removeSnapshot(String providerSnapshotId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.removeSnapshot");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    DELETE_DB_SNAPSHOT);
            EC2Method method;

            parameters.put("DBSnapshotIdentifier", providerSnapshotId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    public void resetConfiguration(String providerConfigurationId, String... params)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.resetConfiguration");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    RESET_DB_PARAMETER_GROUP);
            EC2Method method;

            parameters.put("DBParameterGroupName", providerConfigurationId);
            if (params == null || params.length < 1) {
                parameters.put("ResetAllParameters", "True");
            } else {
                int i = 0;

                parameters.put("ResetAllParameters", "False");
                for (String param : params) {
                    i++;
                    parameters.put("Parameters.member." + i + ".ParameterName", param);
                    parameters.put("Parameters.member." + i + ".ApplyMethod", "pending-reboot");
                }
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    public void restart(String providerDatabaseId, boolean blockUntilDone)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.restart");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    REBOOT_DB_INSTANCE);
            EC2Method method;

            parameters.put("DBInstanceIdentifier", providerDatabaseId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    /**
     * Use this to revoke access of security groups in EC2-Classic
     * @param groupName
     * @param sourceCidr
     * @throws CloudException
     * @throws InternalException
     */
    private void revokeClassicDbSecurityGroup(String groupName, String sourceCidr)
            throws CloudException, InternalException {
        Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                REVOKE_DB_SECURITY_GROUP_INGRESS);

        parameters.put("CIDRIP", sourceCidr);
        parameters.put("DBSecurityGroupName", groupName);
        EC2Method method = new EC2Method(SERVICE_ID, getProvider(), parameters);
        method.invoke();
    }

    public void revokeAccess(String providerDatabaseId, String sourceCidr)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.revokeAccess");
        try {
            EC2Exception error = null;
            Database db = getDatabase(providerDatabaseId);
            for (String securityGroup : getSecurityGroups(providerDatabaseId)) {
                try {
                    String ec2Type = getProvider().getDataCenterServices()
                            .isRegionEC2VPC(getProvider().getContext().getRegionId());
                    if (ec2Type.equals(AWSCloud.PLATFORM_EC2)) {
                        revokeClassicDbSecurityGroup(securityGroup, sourceCidr);
                    } else if (getProvider().hasNetworkServices()
                            && getProvider().getNetworkServices().hasFirewallSupport()) {
                        FirewallSupport firewallSupport = getProvider().getNetworkServices().getFirewallSupport();
                        firewallSupport.revoke(securityGroup, sourceCidr, Protocol.TCP, db.getHostPort(),
                                db.getHostPort());
                    }
                } catch (EC2Exception e) {
                    error = e;
                }
            }
            if (error != null) {
                throw new CloudException(error);
            }
        } finally {
            APITrace.end();
        }
    }

    public void updateConfiguration(String providerConfigurationId, ConfigurationParameter... params)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.updateConfiguration");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    MODIFY_DB_PARAMETER_GROUP);
            EC2Method method;

            parameters.put("DBParameterGroupName", providerConfigurationId);
            int i = 0;
            for (ConfigurationParameter param : params) {
                i++;
                parameters.put("Parameters.member." + i + ".ParameterName", param.getKey());
                parameters.put("Parameters.member." + i + ".ParameterValue", param.getParameter().toString());
                parameters.put("Parameters.member." + i + ".ApplyMethod",
                        param.isApplyImmediately() ? "immediate" : "pending-reboot");
                if (i >= 20) {
                    break;
                }
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            if (params.length > 20) {
                ConfigurationParameter[] repeat = new ConfigurationParameter[params.length - 20];

                i = 0;
                for (; i < repeat.length; i++) {
                    repeat[i] = params[i + 20];
                }
                updateConfiguration(providerConfigurationId, params);
            }
        } finally {
            APITrace.end();
        }
    }

    private void setSecurityGroup(String id, String securityGroupId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.setSecurityGroup");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    MODIFY_DB_INSTANCE);
            EC2Method method;

            parameters.put("DBInstanceIdentifier", id);
            parameters.put("ApplyImmediately", "true");
            String ec2Type = getProvider().getDataCenterServices()
                    .isRegionEC2VPC(getProvider().getContext().getRegionId());
            if (ec2Type.equals(AWSCloud.PLATFORM_EC2)) {
                parameters.put("DBSecurityGroups.member.1", securityGroupId);
            } else {
                parameters.put("VpcSecurityGroupIds.member.1", securityGroupId);
            }
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    public DatabaseSnapshot snapshot(String providerDatabaseId, String name)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "RDBMS.snapshot");
        try {
            Map<String, String> parameters = getProvider().getStandardRdsParameters(getProvider().getContext(),
                    CREATE_DB_SNAPSHOT);
            String id = toIdentifier(name);
            EC2Method method;

            parameters.put("DBSnapshotIdentifier", id);
            parameters.put("DBInstanceIdentifier", providerDatabaseId);
            method = new EC2Method(SERVICE_ID, getProvider(), parameters);
            try {
                method.invoke();
            } catch (EC2Exception e) {
                throw new CloudException(e);
            }
            return getSnapshot(id);
        } finally {
            APITrace.end();
        }
    }

    private DatabaseConfiguration toConfiguration(Node cfgNode) throws CloudException {
        String id = null, cfgName = null, description = null;
        DatabaseEngine engine = DatabaseEngine.MYSQL; //default
        NodeList attrs = cfgNode.getChildNodes();

        for (int i = 0; i < attrs.getLength(); i++) {
            Node attr = attrs.item(i);
            String name;

            name = attr.getNodeName();
            if (name.equalsIgnoreCase("DBParameterGroupName")) {
                id = attr.getFirstChild().getNodeValue().trim();
                cfgName = id;
            } else if (name.equalsIgnoreCase("Description")) {
                description = attr.getFirstChild().getNodeValue().trim();
            } else if (name.equals("Engine")) {
                String awsEngineString = attr.getFirstChild().getNodeValue().trim().toLowerCase();
                engine = toDatabaseEngine(awsEngineString);
            }
        }
        if (id == null || cfgName == null) {
            return null;
        }
        if (description == null) {
            description = cfgName;
        }
        return new DatabaseConfiguration(this, engine, id, cfgName, description);
    }

    private DatabaseEngine toDatabaseEngine(String awsEngineString) {
        DatabaseEngine engine = DatabaseEngine.MYSQL;
        if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_ORACLE_SE1)) {
            engine = DatabaseEngine.ORACLE_SE1;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_ORACLE_SE)) {
            engine = DatabaseEngine.ORACLE_SE;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_ORACLE_EE)) {
            engine = DatabaseEngine.ORACLE_EE;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_POSTGRES)) {
            engine = DatabaseEngine.POSTGRES;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_SQLSERVER_EE)) {
            engine = DatabaseEngine.SQLSERVER_EE;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_SQLSERVER_EX)) {
            engine = DatabaseEngine.SQLSERVER_EX;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_SQLSERVER_SE)) {
            engine = DatabaseEngine.SQLSERVER_SE;
        } else if (awsEngineString.equalsIgnoreCase(AWS_ENGINE_SQLSERVER_WEB)) {
            engine = DatabaseEngine.SQLSERVER_WEB;
        }
        return engine;
    }

    private Database toDatabase(Node dbNode) throws CloudException {
        NodeList attrs = dbNode.getChildNodes();
        Database db = new Database();
        String engineVersion = null;

        db.setCreationTimestamp(0L);
        db.setProviderRegionId(getProvider().getContext().getRegionId());
        db.setProviderOwnerId(getProvider().getContext().getAccountNumber());
        for (int i = 0; i < attrs.getLength(); i++) {
            Node attr = attrs.item(i);
            String name;

            name = attr.getNodeName();
            if (name.equalsIgnoreCase("EngineVersion")) {
                String val = attr.getFirstChild().getNodeValue().trim();

                if (val != null) {
                    db.setEngineVersion(val.toLowerCase().trim());
                }
            } else if (name.equalsIgnoreCase("Engine")) {
                String val = attr.getFirstChild().getNodeValue().trim();
                DatabaseEngine engine = toDatabaseEngine(val);
                db.setEngine(engine);
            } else if (name.equalsIgnoreCase("DBInstanceIdentifier")) {
                db.setProviderDatabaseId(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("DBName")) {
                db.setName(AWSCloud.getTextValue(attr));
            } else if (name.equalsIgnoreCase("MultiAZ")) {
                db.setHighAvailability(attr.getFirstChild().getNodeValue().equalsIgnoreCase("true"));
            } else if (name.equalsIgnoreCase("DBInstanceClass")) {
                db.setProductSize(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("DBInstanceStatus")) {
                db.setCurrentState(toDatabaseState(attr.getFirstChild().getNodeValue().trim()));
            } else if (name.equalsIgnoreCase("Endpoint")) {
                if (attr.hasChildNodes()) {
                    NodeList nodes = attr.getChildNodes();

                    for (int j = 0; j < nodes.getLength(); j++) {
                        Node child = nodes.item(j);

                        if (child != null) {
                            if (child.getNodeName().equalsIgnoreCase("Port")) {
                                try {
                                    db.setHostPort(Integer.parseInt(child.getFirstChild().getNodeValue().trim()));
                                } catch (NumberFormatException e) {
                                    throw new CloudException(
                                            "Invalid storage value: " + child.getFirstChild().getNodeValue()); // ignore this
                                }
                            } else if (child.getNodeName().equalsIgnoreCase("Address")) {
                                db.setHostName(child.getFirstChild().getNodeValue().trim());
                            }
                        }
                    }
                }
            } else if (name.equalsIgnoreCase("AllocatedStorage")) {
                try {
                    db.setAllocatedStorageInGb(Integer.parseInt(attr.getFirstChild().getNodeValue().trim()));
                } catch (NumberFormatException e) {
                    throw new CloudException("Invalid storage value: " + attr.getFirstChild().getNodeValue());
                }
            } else if (name.equalsIgnoreCase("MasterUsername")) {
                db.setAdminUser(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("PreferredMaintenanceWindow")) {
                if (attr.hasChildNodes()) {
                    String val = attr.getFirstChild().getNodeValue();

                    if (val != null) {
                        String[] parts = val.split("-");

                        if (parts.length == 2) {
                            TimeWindow window = getTimeWindow(parts[0], parts[1]);

                            db.setMaintenanceWindow(window);
                        }
                    }
                }
            } else if (name.equalsIgnoreCase("PreferredBackupWindow")) {
                if (attr.hasChildNodes()) {
                    String val = attr.getFirstChild().getNodeValue();

                    if (val != null) {
                        String[] parts = val.split("-");

                        if (parts.length == 2) {
                            TimeWindow window = getTimeWindow(parts[0], parts[1]);

                            db.setBackupWindow(window);
                        }
                    }
                }
            } else if (name.equalsIgnoreCase("AvailabilityZone")) {
                db.setProviderDataCenterId(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("InstanceCreateTime")) {
                String tstr = attr.getFirstChild().getNodeValue().trim();

                db.setCreationTimestamp(getProvider().parseTime(tstr));
            } else if (name.equalsIgnoreCase("LatestRestorableTime")) {
                db.setRecoveryPointTimestamp(getProvider().parseTime(attr.getFirstChild().getNodeValue().trim()));
            } else if (name.equalsIgnoreCase("BackupRetentionPeriod")) {
                try {
                    db.setSnapshotRetentionInDays(Integer.parseInt(attr.getFirstChild().getNodeValue().trim()));
                } catch (NumberFormatException e) {
                    throw new CloudException(
                            "Invalid backup retention period: " + attr.getFirstChild().getNodeValue());
                }
            } else if (name.equalsIgnoreCase("DBParameterGroups")) {
                NodeList groups = attr.getChildNodes();

                for (int j = 0; j < groups.getLength(); j++) {
                    Node group = groups.item(j);

                    if (group.getNodeName().equalsIgnoreCase("DBParameterGroup") && group.hasChildNodes()) {
                        NodeList nodes = group.getChildNodes();

                        for (int k = 0; k < nodes.getLength(); k++) {
                            Node child = nodes.item(k);

                            if (child != null) {
                                if (child.getNodeName().equalsIgnoreCase("DBParameterGroupName")) {
                                    db.setConfiguration(child.getFirstChild().getNodeValue().trim());
                                }
                            }
                        }
                    }
                }
            }
        }
        if (db.getHostName() == null) {
            if (db.getCurrentState().equals(DatabaseState.PENDING)
                    || db.getCurrentState().equals(DatabaseState.MODIFYING)
                    || db.getCurrentState().equals(DatabaseState.RESTARTING)) {
                db.setHostName("");
            } else {
                System.out.println("DEBUG: null database for " + db.getCurrentState());
                return null;
            }
        }
        if (db.getName() == null) {
            db.setName(db.getProviderDatabaseId());
        }
        return db;
    }

    private @Nullable ResourceStatus toDatabaseStatus(@Nullable Node dbNode) throws CloudException {
        if (dbNode == null) {
            return null;
        }
        NodeList attrs = dbNode.getChildNodes();
        DatabaseState state = DatabaseState.PENDING;
        String dbId = null;

        for (int i = 0; i < attrs.getLength(); i++) {
            Node attr = attrs.item(i);
            String name;

            name = attr.getNodeName();
            if (name.equalsIgnoreCase("DBInstanceIdentifier")) {
                dbId = attr.getFirstChild().getNodeValue().trim();
            } else if (name.equalsIgnoreCase("DBInstanceStatus")) {
                state = toDatabaseState(attr.getFirstChild().getNodeValue().trim());
            }
        }
        if (dbId == null) {
            return null;
        }
        return new ResourceStatus(dbId, state);
    }

    private DatabaseState toDatabaseState(String value) throws CloudException {
        //incompatible-option-group, incompatible-parameters, incompatible-restore, incompatible-network
        if (value == null) {
            System.out.println("DEBUG: Null state value");
            return DatabaseState.PENDING;
        } else if (value.equalsIgnoreCase("available") || value.equals("incompatible-option-group")
                || value.equals("incompatible-parameters") || value.equals("incompatible-restore")
                || value.equals("incompatible-network")) {
            return DatabaseState.AVAILABLE;
        } else if (value.equalsIgnoreCase("storage-full")) {
            return DatabaseState.STORAGE_FULL;
        } else if (value.equalsIgnoreCase("failed")) {
            return DatabaseState.FAILED;
        } else if (value.equalsIgnoreCase("backing-up")) {
            return DatabaseState.BACKUP;
        } else if (value.equalsIgnoreCase("creating")) {
            return DatabaseState.PENDING;
        } else if (value.equalsIgnoreCase("deleted")) {
            return DatabaseState.DELETED;
        } else if (value.equalsIgnoreCase("deleting")) {
            return DatabaseState.DELETING;
        } else if (value.equalsIgnoreCase("modifying")) {
            return DatabaseState.MODIFYING;
        } else if (value.equalsIgnoreCase("rebooting")) {
            return DatabaseState.RESTARTING;
        } else if (value.equalsIgnoreCase("resetting-mastercredentials")) {
            return DatabaseState.MODIFYING;
        } else {
            System.out.println("DEBUG: Unknown database state: " + value);
            return DatabaseState.PENDING;
        }
    }

    private String toInstanceName(String rawName, DatabaseEngine forEngine) {
        StringBuilder str = new StringBuilder();

        if (rawName.length() < 1) {
            return "x";
        }
        if (isMySQL(forEngine) || isOracle(forEngine)) {
            if (!Character.isLetter(rawName.charAt(0))) {
                rawName = "db" + rawName;
            }
        } else {
            if (!Character.isLetter(rawName.charAt(0)) && rawName.charAt(0) != '_') {
                rawName = "db" + rawName;
            }
        }
        char last = '\0';
        for (int i = 0; i < rawName.length(); i++) {
            char c = rawName.charAt(i);
            if (Character.isLetterOrDigit(c)) {
                str.append(c);
            } else if (isPostgres(forEngine) && c == '_') {
                str.append(c);
            }
            last = c;
        }
        rawName = str.toString();
        int maxLength = 64;
        if (isOracle(forEngine)) {
            maxLength = 8;
        } else if (isPostgres(forEngine)) {
            maxLength = 63;
        }
        if (rawName.length() > maxLength) {
            rawName = rawName.substring(0, maxLength);
        }
        if (rawName.length() < 1) {
            return "x";
        }
        return rawName;
    }

    private String toIdentifier(String rawName) {
        StringBuilder str = new StringBuilder();

        if (rawName.length() < 1) {
            return "x";
        }
        if (!Character.isLetter(rawName.charAt(0))) {
            rawName = "db-" + rawName;
        }
        char last = '\0';
        for (int i = 0; i < rawName.length(); i++) {
            char c = rawName.charAt(i);

            if (Character.isLetterOrDigit(c)) {
                str.append(c);
            }
            if (c == '-' && last != '-') {
                str.append(c);
            }
            last = c;
        }
        rawName = str.toString();
        if (rawName.length() > 63) {
            rawName = rawName.substring(0, 63);
        }
        while (rawName.charAt(rawName.length() - 1) == '-') {
            rawName = rawName.substring(0, rawName.length() - 1);
        }
        if (rawName.length() < 1) {
            return "x";
        }
        return rawName;
    }

    private ConfigurationParameter toParameter(Node pNode) throws CloudException {
        ConfigurationParameter param = new ConfigurationParameter();
        NodeList attrs = pNode.getChildNodes();

        param.setModifiable(false);
        param.setApplyImmediately(false);
        for (int i = 0; i < attrs.getLength(); i++) {
            Node attr = attrs.item(i);
            String name;

            name = attr.getNodeName();
            if (name.equalsIgnoreCase("ParameterValue")) {
                param.setParameter(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("DataType")) {
                param.setDataType(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("IsModifiable") && attr.hasChildNodes()) {
                param.setModifiable(attr.getFirstChild().getNodeValue().trim().equalsIgnoreCase("true"));
            } else if (name.equalsIgnoreCase("Description")) {
                param.setDescription(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("AllowedValues") && attr.hasChildNodes()) {
                param.setValidation(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("ParameterName")) {
                param.setKey(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("ApplyType") && attr.hasChildNodes()) {
                param.setApplyImmediately(attr.getFirstChild().getNodeValue().trim().equalsIgnoreCase("static"));
            }
        }
        return param;
    }

    private DatabaseSnapshot toSnapshot(Node snapshotNode) throws CloudException {
        DatabaseSnapshot snapshot = new DatabaseSnapshot();
        NodeList attrs = snapshotNode.getChildNodes();

        snapshot.setProviderRegionId(getProvider().getContext().getRegionId());
        snapshot.setProviderOwnerId(getProvider().getContext().getAccountNumber());
        for (int i = 0; i < attrs.getLength(); i++) {
            Node attr = attrs.item(i);
            String name;

            name = attr.getNodeName();
            if (name.equalsIgnoreCase("SnapshotCreateTime")) {
                snapshot.setSnapshotTimestamp(getProvider().parseTime(attr.getFirstChild().getNodeValue().trim()));
            } else if (name.equalsIgnoreCase("Status")) {
                String value = attr.getFirstChild().getNodeValue().trim();
                DatabaseSnapshotState state;

                if (value.equalsIgnoreCase("available")) {
                    state = DatabaseSnapshotState.AVAILABLE;
                } else if (value.equalsIgnoreCase("creating")) {
                    state = DatabaseSnapshotState.CREATING;
                } else if (value.equalsIgnoreCase("deleting")) {
                    state = DatabaseSnapshotState.DELETING;
                } else {
                    throw new CloudException("Unknown database snapshot state: " + value);
                }
                snapshot.setCurrentState(state);
            } else if (name.equalsIgnoreCase("MasterUsername")) {
                snapshot.setAdminUser(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("DBInstanceIdentifier")) {
                snapshot.setProviderDatabaseId(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("DBSnapshotIdentifier")) {
                snapshot.setProviderSnapshotId(attr.getFirstChild().getNodeValue().trim());
            } else if (name.equalsIgnoreCase("AllocatedStorage")) {
                snapshot.setStorageInGigabytes(Integer.parseInt(attr.getFirstChild().getNodeValue().trim()));
            }
        }
        return snapshot;
    }

    // TODO: to avoid having to have helpers like the ones below, introduce a notion of 'family'
    // in database engines in core. at the very least, move them to core
    private boolean isMySQL(DatabaseEngine engine) {
        return (engine == DatabaseEngine.MYSQL);
    }

    private boolean isOracle(DatabaseEngine engine) {
        return Arrays.asList(ORACLE_EE, ORACLE_SE, ORACLE_SE1).contains(engine);
    }

    private boolean isSQLServer(DatabaseEngine engine) {
        return Arrays.asList(SQLSERVER_WEB, SQLSERVER_SE, SQLSERVER_EX, SQLSERVER_EE).contains(engine);
    }

    private boolean isPostgres(DatabaseEngine engine) {
        return (engine == POSTGRES);
    }

    @Override
    public void removeTags(@Nonnull String providerDatabaseId, @Nonnull Tag... tags)
            throws CloudException, InternalException {
        getProvider().removeTags(SERVICE_ID, "arn:aws:rds:" + getProvider().getContext().getRegionId() + ":"
                + getProvider().getContext().getAccountNumber().replace("-", "") + ":db:" + providerDatabaseId,
                tags);
    }

    @Override
    public void removeTags(@Nonnull String[] providerDatabaseIds, @Nonnull Tag... tags)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "Database.updateTags");
        for (String id : providerDatabaseIds) {
            removeTags(id, tags);
        }
    }

    @Override
    public void updateTags(@Nonnull String providerDatabaseId, @Nonnull Tag... tags)
            throws CloudException, InternalException {
        getProvider().createTags(SERVICE_ID, "arn:aws:rds:" + getProvider().getContext().getRegionId() + ":"
                + getProvider().getContext().getAccountNumber().replace("-", "") + ":db:" + providerDatabaseId,
                tags);
    }

    @Override
    public void updateTags(@Nonnull String[] providerDatabaseIds, @Nonnull Tag... tags)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "Database.updateTags");
        for (String id : providerDatabaseIds) {
            updateTags(id, tags);
        }
    }
}