com.eucalyptus.vm.VmInstanceMetadata.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.vm.VmInstanceMetadata.java

Source

/*************************************************************************
 * Copyright 2009-2015 Eucalyptus Systems, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 *
 * This file may incorporate work covered under the following copyright
 * and permission notice:
 *
 *   Software License Agreement (BSD License)
 *
 *   Copyright (c) 2008, Regents of the University of California
 *   All rights reserved.
 *
 *   Redistribution and use of this software in source and binary forms,
 *   with or without modification, are permitted provided that the
 *   following conditions are met:
 *
 *     Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *     Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer
 *     in the documentation and/or other materials provided with the
 *     distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *   COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *   POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
 *   THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
 *   COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
 *   AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *   IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
 *   SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
 *   WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
 *   REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
 *   IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
 *   NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
 ************************************************************************/
package com.eucalyptus.vm;

import static com.eucalyptus.util.Strings.isPrefixOf;
import static com.eucalyptus.util.Strings.upper;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.policy.ern.Ern;
import com.eucalyptus.auth.policy.ern.EuareResourceName;
import com.eucalyptus.auth.principal.BaseInstanceProfile;
import com.eucalyptus.auth.principal.BaseRole;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.component.id.Dns;
import com.eucalyptus.component.id.Tokens;
import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.compute.common.internal.images.BlockStorageImageInfo;
import com.eucalyptus.compute.common.internal.images.MachineImageInfo;
import com.eucalyptus.compute.common.internal.vm.VmEphemeralAttachment;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vm.VmVolumeAttachment;
import com.eucalyptus.crypto.Crypto;
import com.eucalyptus.crypto.util.Timestamps;
import com.eucalyptus.images.ImageManager;
import com.eucalyptus.tokens.AssumeRoleResponseType;
import com.eucalyptus.tokens.AssumeRoleType;
import com.eucalyptus.tokens.CredentialsType;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.ws.StackConfiguration;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.TreeMultimap;
import net.sf.json.JSONObject;

/**
 *
 */
public class VmInstanceMetadata {

    private static final Logger LOG = Logger.getLogger(VmInstanceMetadata.class);

    private static final com.google.common.cache.Cache<MetadataKey, ImmutableMap<String, String>> metadataCache = CacheBuilder
            .newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(1000).build();

    public static String getByKey(final VmInstance vm, final String pathArg) {
        final String path = Objects.firstNonNull(pathArg, "");
        final String pathNoSlash;
        LOG.debug("Servicing metadata request:" + path);
        if (path.endsWith("/")) {
            pathNoSlash = path.substring(0, path.length() - 1);
        } else {
            pathNoSlash = path;
        }

        Optional<MetadataGroup> groupOption = Optional.absent();
        for (final MetadataGroup metadataGroup : MetadataGroup.values()) {
            if (metadataGroup.providesPath(pathNoSlash) || metadataGroup.providesPath(path)) {
                groupOption = Optional.of(metadataGroup);
            }
        }
        final MetadataGroup group = groupOption.or(MetadataGroup.Core);
        final Map<String, String> metadataMap = Optional.fromNullable(group.apply(vm))
                .or(Collections.<String, String>emptyMap());
        final String value = metadataMap.get(path);
        return value == null ? metadataMap.get(pathNoSlash) : value;
    }

    private static Map<String, String> getCoreMetadataMap(final VmInstance vm) {
        final boolean dns = StackConfiguration.USE_INSTANCE_DNS
                && !ComponentIds.lookup(Dns.class).runLimitedServices();
        final Map<String, String> m = Maps.newHashMap();
        m.put("ami-id", vm.getImageId());
        if (vm.getBootRecord().getMachine() != null
                && !vm.getBootRecord().getMachine().getProductCodes().isEmpty()) {
            m.put("product-codes", Joiner.on('\n').join(vm.getBootRecord().getMachine().getProductCodes()));
        }
        m.put("ami-launch-index", "" + vm.getLaunchIndex());
        //ASAP: FIXME: GRZE:
        //    m.put( "ancestor-ami-ids", this.getImageInfo( ).getAncestorIds( ).toString( ).replaceAll( "[\\Q[]\\E]", "" ).replaceAll( ", ", "\n" ) );
        if (vm.getBootRecord().getMachine() instanceof MachineImageInfo) {
            m.put("ami-manifest-path", ((MachineImageInfo) vm.getBootRecord().getMachine()).getManifestLocation());
        }
        if (dns) {
            m.put("hostname", vm.getPrivateDnsName());
        } else {
            m.put("hostname", vm.getPrivateAddress());
        }
        m.put("instance-id", vm.getInstanceId());
        m.put("instance-type", vm.getVmType().getName());
        if (dns) {
            m.put("local-hostname", vm.getPrivateDnsName());
        } else {
            m.put("local-hostname", vm.getPrivateAddress());
        }
        m.put("local-ipv4", vm.getPrivateAddress());
        m.put("mac", upper().apply(vm.getMacAddress()));
        if (dns) {
            m.put("public-hostname", vm.getPublicDnsName());
        } else {
            m.put("public-hostname", vm.getPublicAddress());
        }
        m.put("public-ipv4", vm.getPublicAddress());
        m.put("reservation-id", vm.getReservationId());
        if (vm.getKernelId() != null) {
            m.put("kernel-id", vm.getKernelId());
        }
        if (vm.getRamdiskId() != null) {
            m.put("ramdisk-id", vm.getRamdiskId());
        }
        m.put("security-groups", Joiner.on('\n')
                .join(Sets.newTreeSet(Iterables.transform(vm.getNetworkGroups(), CloudMetadatas.toDisplayName()))));
        m.put("placement/availability-zone", vm.getPartition());
        return m;
    }

    private static Map<String, String> getBlockDeviceMappingMetadataMap(final VmInstance vm) {
        final Map<String, String> m = Maps.newHashMap();
        // Metadata should accurately reflect all the ebs mappings and ephemeral mappings if any.
        // Fixes EUCA-4081, EUCA-3954 and implements EUCA-4786
        if (vm.getBootRecord().getMachine() instanceof BlockStorageImageInfo) {
            // Get all the volume attachments and order them in some way (by device name for now)
            Set<VmVolumeAttachment> volAttachments = new TreeSet<VmVolumeAttachment>(
                    VolumeAttachmentComparator.INSTANCE);
            volAttachments.addAll(vm.getBootRecord().getPersistentVolumes());

            // Keep track of all ebs keys for populating block-device-mapping list
            int ebsCount = 0;

            // Iterate through the list of volume attachments and populate ebs mappings
            for (VmVolumeAttachment attachment : volAttachments) {
                if (attachment.getIsRootDevice()) {
                    m.put("block-device-mapping/ami", attachment.getShortDeviceName());
                    m.put("block-device-mapping/emi", attachment.getShortDeviceName());
                    m.put("block-device-mapping/root", attachment.getDevice());
                }
                // add only volumes added at start up time and don't list root see EUCA-8636
                if (attachment.getAttachedAtStartup() && !attachment.getIsRootDevice())
                    m.put("block-device-mapping/ebs" + String.valueOf(++ebsCount), attachment.getShortDeviceName());
            }

            // Using ephemeral attachments for bfebs instances only, can be extended to be used by all other instances
            // Get all the ephemeral attachments and order them in some way (by device name for now)
            Set<VmEphemeralAttachment> ephemeralAttachments = new TreeSet<VmEphemeralAttachment>(
                    vm.getBootRecord().getEphemeralStorage());

            // Iterate through the list of ephemeral attachments and populate ephemeral mappings
            if (!ephemeralAttachments.isEmpty()) {
                for (VmEphemeralAttachment attachment : ephemeralAttachments) {
                    m.put("block-device-mapping/" + attachment.getEphemeralId(), attachment.getShortDeviceName());
                }
            }
        } else if (vm.getBootRecord().getMachine() instanceof MachineImageInfo) {
            MachineImageInfo mii = (MachineImageInfo) vm.getBootRecord().getMachine();
            String s = mii.getRootDeviceName();
            m.put("block-device-mapping/emi", mii.getShortRootDeviceName());
            m.put("block-device-mapping/ami", mii.getShortRootDeviceName());
            m.put("block-device-mapping/root", s);
            if (ImageManager.isPathAPartition(s)) {
                m.put("block-device-mapping/ephemeral0", "sda2");
                m.put("block-device-mapping/swap", "sda3");
            } else {
                m.put("block-device-mapping/ephemeral0", "sdb");
            }
        }
        return m;
    }

    private static Map<String, String> getIamMetadataMap(final VmInstance vm) {
        final Map<String, String> m = new HashMap<>();
        final String instanceProfileNameOrArn = vm.getIamInstanceProfileArn();
        if (!Strings.isNullOrEmpty(instanceProfileNameOrArn)) {
            BaseInstanceProfile profile = null;
            String profileArn = null;
            String roleArn = vm.getIamRoleArn();
            String roleName = null;
            if (!Strings.isNullOrEmpty(roleArn)) {
                roleName = roleArn.substring(roleArn.lastIndexOf('/') + 1);
            } else
                try {
                    String profileName;
                    if (instanceProfileNameOrArn.startsWith("arn:")) {
                        profileName = instanceProfileNameOrArn
                                .substring(instanceProfileNameOrArn.lastIndexOf('/') + 1);
                    } else {
                        profileName = instanceProfileNameOrArn;
                    }
                    profile = Accounts.lookupInstanceProfileByName(vm.getOwnerAccountNumber(), profileName);
                    profileArn = Accounts.getInstanceProfileArn(profile);
                    if (roleArn == null) {
                        final BaseRole role = profile.getRole();
                        if (role != null) {
                            roleArn = Accounts.getRoleArn(role);
                            roleName = role.getName();
                        }
                    } else {
                        // Authorized role from instance creation time must be used if present
                        final EuareResourceName ern = (EuareResourceName) Ern.parse(roleArn);
                        roleName = ern.getName();
                    }
                } catch (AuthException e) {
                    LOG.debug(e);
                }

            CredentialsType credentials = null;
            if (roleArn != null) {
                final AssumeRoleType assumeRoleType = new AssumeRoleType();
                assumeRoleType.setRoleArn(roleArn);
                assumeRoleType.setRoleSessionName(Crypto.generateId(vm.getOwner().getUserId()));

                ServiceConfiguration serviceConfiguration = Topology.lookup(Tokens.class);
                try {
                    credentials = ((AssumeRoleResponseType) AsyncRequests.sendSync(serviceConfiguration,
                            assumeRoleType)).getAssumeRoleResult().getCredentials();
                } catch (Exception e) {
                    LOG.debug("Unable to send assume role request to token service", e);
                }
            }

            if (profile != null) {
                m.put("iam/info/last-updated-date", Timestamps.formatIso8601Timestamp(new Date()));
                m.put("iam/info/instance-profile-arn", profileArn);
                m.put("iam/info/instance-profile-id", profile.getInstanceProfileId());
            }

            if (roleName != null && credentials != null) {
                final String jsonCredentials = new JSONObject().element("Code", "Success")
                        .element("LastUpdated", Timestamps.formatIso8601Timestamp(new Date()))
                        .element("Type", "AWS-HMAC").element("AccessKeyId", credentials.getAccessKeyId())
                        .element("SecretAccessKey", credentials.getSecretAccessKey())
                        .element("Token", credentials.getSessionToken())
                        .element("Expiration", Timestamps.formatIso8601Timestamp(credentials.getExpiration()))
                        .toString(2);

                m.put("iam/security-credentials/" + roleName + "/AccessKeyId", credentials.getAccessKeyId());
                m.put("iam/security-credentials/" + roleName + "/Expiration",
                        Timestamps.formatIso8601Timestamp(credentials.getExpiration()));
                m.put("iam/security-credentials/" + roleName + "/SecretAccessKey",
                        credentials.getSecretAccessKey());
                m.put("iam/security-credentials/" + roleName + "/Token", credentials.getSessionToken());
                m.put("iam/security-credentials/" + roleName, jsonCredentials);
                m.put("iam/security-credentials", roleName);
                m.put("iam/security-credentials/", roleName);
            }

        }
        return m;
    }

    private static Map<String, String> getPublicKeysMetadataMap(final VmInstance vm) {
        final Map<String, String> m = Maps.newHashMap();
        if (vm.getBootRecord().getSshKeyPair() != null) {
            m.put("public-keys", "0=" + vm.getBootRecord().getSshKeyPair().getName());
            m.put("public-keys/", "0=" + vm.getBootRecord().getSshKeyPair().getName());
            m.put("public-keys/0/openssh-key", vm.getBootRecord().getSshKeyPair().getPublicKey());
        }
        return m;
    }

    private static enum VolumeAttachmentComparator implements Comparator<VmVolumeAttachment> {
        INSTANCE;

        @Override
        public int compare(VmVolumeAttachment arg0, VmVolumeAttachment arg1) {
            return arg0.getDevice().compareToIgnoreCase(arg1.getDevice());
        }
    }

    private enum MetadataGroup implements Function<VmInstance, Map<String, String>> {
        Core {
            @Override
            public Map<String, String> apply(final VmInstance instance) {
                return addListingEntries(instance, getCoreMetadataMap(instance), true);
            }

        },
        BlockDeviceMapping("block-device-mapping") {
            @Override
            public Map<String, String> apply(final VmInstance instance) {
                return addListingEntries(getBlockDeviceMappingMetadataMap(instance));
            }
        },
        Iam("iam") {
            @Override
            public Map<String, String> apply(final VmInstance instance) {
                try {
                    return metadataCache.get(new MetadataKey(instance.getInstanceUuid(), this),
                            new Callable<ImmutableMap<String, String>>() {
                                @Override
                                public ImmutableMap<String, String> call() throws Exception {
                                    return ImmutableMap.copyOf(addListingEntries(getIamMetadataMap(instance)));
                                }
                            });
                } catch (ExecutionException e) {
                    throw Exceptions.toUndeclared(e); // Cache load exception not expected
                }
            }

            @Override
            protected boolean isPresent(final VmInstance instance) {
                return !Strings.isNullOrEmpty(instance.getIamInstanceProfileArn());
            }
        },
        PublicKeys("public-keys") {
            @Override
            public Map<String, String> apply(final VmInstance instance) {
                return addListingEntries(getPublicKeysMetadataMap(instance));
            }

            @Override
            protected boolean isPresent(final VmInstance instance) {
                return instance.getBootRecord().getSshKeyPair() != null;
            }
        };

        private final Optional<String> prefix;

        private MetadataGroup() {
            prefix = Optional.absent();
        }

        private MetadataGroup(final String path) {
            prefix = Optional.of(path);
        }

        public boolean providesPath(final String path) {
            return prefix.transform(Functions.forPredicate(isPrefixOf(path))).or(Boolean.FALSE);
        }

        protected boolean isPresent(final VmInstance instance) {
            return true;
        }

        private static Map<String, String> addListingEntries(final Map<String, String> metadataMap) {
            return addListingEntries(null, metadataMap, false);
        }

        private static Map<String, String> addListingEntries(@Nullable final VmInstance instance,
                final Map<String, String> metadataMap, final boolean addRoots) {
            final TreeMultimap<String, String> listingMap = TreeMultimap.create();
            final Splitter pathSplitter = Splitter.on('/');
            final Joiner pathJoiner = Joiner.on('/');
            for (final String path : metadataMap.keySet()) {
                final List<String> pathSegments = Lists.newArrayList(pathSplitter.split(path));
                for (int i = 0; i < pathSegments.size(); i++) {
                    listingMap.put(pathJoiner.join(pathSegments.subList(0, i)),
                            pathSegments.get(i) + (i < pathSegments.size() - 1 ? "/" : ""));
                }
            }

            if (addRoots && instance != null) {
                for (MetadataGroup group : MetadataGroup.values()) {
                    if (group.isPresent(instance) && group.prefix.isPresent()) {
                        listingMap.put("", group.prefix.get() + "/");
                    }
                }
            }

            final Joiner listingJoiner = Joiner.on("\n");
            for (final String key : listingMap.keySet()) {
                final Set<String> values = listingMap.get(key);
                final Iterator<String> valueIterator = values.iterator();
                while (valueIterator.hasNext()) {
                    final String value = valueIterator.next();
                    if (values.contains(value + "/"))
                        valueIterator.remove();
                }
                if (!metadataMap.containsKey(key)) {
                    metadataMap.put(key, listingJoiner.join(values));
                } else if (!metadataMap.containsKey(key + "/")) {
                    metadataMap.put(key + "/", listingJoiner.join(values));
                }
            }

            return metadataMap;
        }
    }

    private static final class MetadataKey {
        private final String id; // internal id
        private final MetadataGroup metadataGroup;

        private MetadataKey(final String id, final MetadataGroup metadataGroup) {
            this.id = id;
            this.metadataGroup = metadataGroup;
        }

        @SuppressWarnings("RedundantIfStatement")
        @Override
        public boolean equals(final Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            final MetadataKey that = (MetadataKey) o;

            if (!id.equals(that.id))
                return false;
            if (metadataGroup != that.metadataGroup)
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = id.hashCode();
            result = 31 * result + metadataGroup.hashCode();
            return result;
        }
    }
}