com.eucalyptus.compute.common.internal.vm.VmInstance.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.compute.common.internal.vm.VmInstance.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.compute.common.internal.vm;

import java.lang.Object;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityTransaction;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PersistenceContext;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;

import org.apache.log4j.Logger;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.principal.BaseInstanceProfile;
import com.eucalyptus.compute.common.CloudMetadata.VmInstanceMetadata;
import com.eucalyptus.compute.common.GroupItemType;
import com.eucalyptus.compute.common.GroupSetType;
import com.eucalyptus.compute.common.IamInstanceProfile;
import com.eucalyptus.compute.common.ImageMetadata.Platform;
import com.eucalyptus.compute.common.internal.images.Images;
import com.eucalyptus.compute.common.internal.util.ResourceAllocationException;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.Partition;
import com.eucalyptus.component.id.Dns;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.compute.common.InstanceBlockDeviceMapping;
import com.eucalyptus.compute.common.InstanceNetworkInterfaceAssociationType;
import com.eucalyptus.compute.common.InstanceNetworkInterfaceAttachmentType;
import com.eucalyptus.compute.common.InstanceNetworkInterfaceSetItemType;
import com.eucalyptus.compute.common.InstanceNetworkInterfaceSetType;
import com.eucalyptus.compute.common.InstancePrivateIpAddressesSetItemType;
import com.eucalyptus.compute.common.InstancePrivateIpAddressesSetType;
import com.eucalyptus.compute.common.InstanceStateType;
import com.eucalyptus.compute.common.InstanceStatusDetailsSetItemType;
import com.eucalyptus.compute.common.InstanceStatusDetailsSetType;
import com.eucalyptus.compute.common.InstanceStatusEventType;
import com.eucalyptus.compute.common.InstanceStatusEventsSetType;
import com.eucalyptus.compute.common.InstanceStatusItemType;
import com.eucalyptus.compute.common.InstanceStatusType;
import com.eucalyptus.compute.common.ReservationInfoType;
import com.eucalyptus.compute.common.RunningInstancesItemType;
import com.eucalyptus.compute.common.internal.images.BlockStorageImageInfo;
import com.eucalyptus.compute.common.internal.vpc.NetworkInterface;
import com.eucalyptus.compute.common.internal.vpc.Vpc;
import com.eucalyptus.entities.UserMetadata;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransientEntityException;
import com.eucalyptus.event.ListenerRegistry;
import com.eucalyptus.compute.common.internal.keys.SshKeyPair;
import com.eucalyptus.compute.common.internal.network.NetworkGroup;
import com.eucalyptus.compute.common.internal.network.PrivateNetworkIndex;
import com.eucalyptus.records.Logs;
import com.eucalyptus.reporting.event.InstanceCreationEvent;
import com.eucalyptus.upgrade.Upgrades.EntityUpgrade;
import com.eucalyptus.upgrade.Upgrades.Version;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.auth.principal.FullName;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.util.Pair;
import com.eucalyptus.util.TypeMapper;
import com.eucalyptus.util.TypeMappers;
import com.eucalyptus.compute.common.internal.vm.VmInstance.VmState;
import com.eucalyptus.compute.common.internal.vmtypes.VmType;
import com.eucalyptus.ws.StackConfiguration;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

@Entity
@PersistenceContext(name = "eucalyptus_cloud")
@Table(name = "metadata_instances")
@org.hibernate.annotations.Table(appliesTo = "metadata_instances", indexes = {
        @Index(name = "metadata_instances_user_id_idx", columnNames = "metadata_user_id"),
        @Index(name = "metadata_instances_account_id_idx", columnNames = "metadata_account_id"),
        @Index(name = "metadata_instances_display_name_idx", columnNames = "metadata_display_name"), })
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
public class VmInstance extends UserMetadata<VmState> implements VmInstanceMetadata {
    private static final long serialVersionUID = 1L;
    private static final Logger LOG = Logger.getLogger(VmInstance.class);

    public static final String DEFAULT_TYPE = "m1.small";
    public static final String ROOT_DEVICE_TYPE_EBS = "ebs";
    public static final String ROOT_DEVICE_TYPE_INSTANCE_STORE = "instance-store";
    public static final String ID_PREFIX = "i";
    public static final String VM_NC_HOST_TAG = "euca:node";

    @Embedded
    private VmNetworkConfig networkConfig;
    @Embedded
    private final VmId vmId;
    @Embedded
    private VmBootRecord bootRecord;
    @Embedded
    private final VmUsageStats usageStats;
    @Embedded
    private final VmLaunchRecord launchRecord;
    @Embedded
    private VmRuntimeState runtimeState;
    @Embedded
    private VmVolumeState transientVolumeState;
    @Embedded
    private final VmPlacement placement;

    @Column(name = "metadata_vm_expiration")
    private final Date expiration;
    @Column(name = "metadata_vm_private_networking")
    private final Boolean privateNetwork;
    @NotFound(action = NotFoundAction.IGNORE)
    @ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
    private Set<NetworkGroup> networkGroups = Sets.newHashSet();

    @ElementCollection
    @CollectionTable(name = "metadata_vm_instance_groups")
    @JoinColumn(name = "metadata_vm_instance_id")
    @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
    private Set<NetworkGroupId> networkGroupIds = Sets.newHashSet();

    @NotFound(action = NotFoundAction.IGNORE)
    @OneToOne(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }, orphanRemoval = true, optional = true)
    @JoinColumn(name = "metadata_vm_network_index", nullable = true, insertable = true, updatable = true)
    @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
    private PrivateNetworkIndex networkIndex;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy = "instance")
    private Collection<VmInstanceTag> tags;

    public static Criterion criterion(VmState... state) {
        return Restrictions.in("state", state);
    }

    public static Criterion zoneCriterion(String... zone) {
        return Restrictions.in("placement.partitionName", zone);
    }

    public static Criterion nonNullNodeCriterion() {
        return Restrictions.isNotNull("runtimeState.serviceTag");
    }

    public static Criterion nullNodeCriterion() {
        return Restrictions.isNull("runtimeState.serviceTag");
    }

    public static Projection instanceIdProjection() {
        return Projections.property("displayName");
    }

    public static Projection instanceUuidProjection() {
        return Projections.property("naturalId");
    }

    /**
     *
     */
    public static RunningInstancesItemType transform(final VmInstance vm) {
        return Transform.INSTANCE.apply(vm);
    }

    private enum StringPropertyFunctions implements Function<VmInstance, String> {
        CLIENT_TOKEN {
            @Override
            public String apply(final VmInstance instance) {
                return instance.getClientToken();
            }
        },
    }

    public enum Filters implements Predicate<VmInstance> {
        BUNDLING {

            @Override
            public boolean apply(final VmInstance arg0) {
                return arg0.getRuntimeState().isBundling();
            }

        }
    }

    public enum VmStateSet implements Predicate<VmInstance> {
        RUN(VmState.PENDING, VmState.RUNNING), CHANGING(VmState.PENDING, VmState.STOPPING, VmState.SHUTTING_DOWN) {

            @Override
            public boolean apply(final VmInstance arg0) {
                return super.apply(arg0) || !arg0.eachVolumeAttachment(new Predicate<VmVolumeAttachment>() {
                    @Override
                    public boolean apply(final VmVolumeAttachment input) {
                        return !input.getAttachmentState().isVolatile();
                    }
                });
            }

        },
        EXPECTING_TEARDOWN(VmState.STOPPING, VmState.SHUTTING_DOWN), TORNDOWN(VmState.STOPPED, VmState.TERMINATED,
                VmState.BURIED), STOP(VmState.STOPPING, VmState.STOPPED), NOT_RUNNING(VmState.STOPPING,
                        VmState.STOPPED, VmState.SHUTTING_DOWN, VmState.TERMINATED,
                        VmState.BURIED), DONE(VmState.TERMINATED, VmState.BURIED);

        private final Set<VmState> states;

        VmStateSet(final VmState... states) {
            this.states = Collections.unmodifiableSet(EnumSet.copyOf(Sets.newHashSet(states)));
        }

        @Override
        public boolean apply(final VmInstance arg0) {
            return this.states.contains(arg0.getState());
        }

        public boolean contains(final VmState state) {
            return this.states.contains(state);
        }

        public Predicate<VmInstance> not() {
            return Predicates.not(this);
        }

        public Set<VmState> set() {
            return states;
        }

        public VmState[] array() {
            return states.toArray(new VmState[states.size()]);
        }
    }

    public enum VmState implements Predicate<VmInstance> {
        PENDING(0), RUNNING(16), SHUTTING_DOWN(32), TERMINATED(48), STOPPING(64), STOPPED(80), BURIED(128,
                TERMINATED);

        private final String name;
        private final int code;
        private final VmState displayState;

        VmState(final int code) {
            this(code, null);
        }

        VmState(final int code, final VmState displayState) {
            this.name = this.name().toLowerCase().replace("_", "-");
            this.code = code;
            this.displayState = Objects.firstNonNull(displayState, this);
        }

        public String getName() {
            return this.name;
        }

        public int getCode() {
            return this.code;
        }

        public VmState getDisplayState() {
            return this.displayState;
        }

        public static class Mapper {
            private static Map<String, VmState> stateMap = getStateMap();

            private static Map<String, VmState> getStateMap() {
                final Map<String, VmState> map = new HashMap<String, VmState>();
                map.put("Extant", VmState.RUNNING);
                map.put("Pending", VmState.PENDING);
                map.put("Teardown", VmState.SHUTTING_DOWN);
                return map;
            }

            public static VmState get(final String stateName) {
                return Mapper.stateMap.get(stateName);
            }
        }

        @Override
        public boolean apply(final VmInstance arg0) {
            return this.equals(arg0.getState());
        }

        public Predicate<VmInstance> not() {
            return Predicates.not(this);
        }
    }

    public enum Lookup implements Function<String, VmInstance> {
        INSTANCE {

            @Nonnull
            @Override
            public VmInstance apply(final String arg0) {
                final EntityTransaction db = Entities.get(VmInstance.class);
                try {
                    final VmInstance vm = Entities.uniqueResult(VmInstance.named(null, arg0));
                    if ((vm == null)) {
                        throw new NoSuchElementException("Failed to lookup vm instance: " + arg0);
                    }
                    db.commit();
                    return vm;
                } catch (final NoSuchElementException ex) {
                    throw ex;
                } catch (final Exception ex) {
                    throw new NoSuchElementException("An error occurred while trying to lookup vm instance " + arg0
                            + ": " + ex.getMessage() + "\n" + Exceptions.causeString(ex));
                } finally {
                    if (db.isActive())
                        db.rollback();
                }
            }
        };

        @Nonnull
        @Override
        public abstract VmInstance apply(final String arg0);
    }

    public VmInstance(final OwnerFullName owner, final VmId vmId, final VmBootRecord bootRecord,
            final VmLaunchRecord launchRecord, final VmPlacement placement,
            final List<NetworkGroup> networkRulesGroups, final Optional<PrivateNetworkIndex> networkIndex,
            final Boolean usePrivateAddressing, final Date expiration) throws ResourceAllocationException {
        super(owner, vmId.getInstanceId());
        this.setState(VmState.PENDING);
        this.vmId = vmId;
        this.expiration = expiration;
        this.bootRecord = bootRecord;
        this.launchRecord = launchRecord;
        this.placement = placement;
        this.privateNetwork = Boolean.FALSE;
        this.usageStats = new VmUsageStats(this);
        this.runtimeState = new VmRuntimeState(this);
        this.transientVolumeState = new VmVolumeState(this);
        this.networkConfig = new VmNetworkConfig(this, usePrivateAddressing);
        final Function<NetworkGroup, NetworkGroup> func = Entities.merge();
        this.networkGroups.addAll(Collections2.transform(networkRulesGroups, func));
        this.networkIndex = networkIndex.isPresent() ? Entities.merge(networkIndex.get().set(this)) : null;
        this.store();
    }

    private VmInstance(final OwnerFullName owner, final VmId vmId) {
        super(owner, null);
        this.vmId = vmId;
        this.expiration = null;
        this.runtimeState = null;
        this.bootRecord = null;
        this.launchRecord = null;
        this.placement = null;
        this.privateNetwork = null;
        this.usageStats = null;
        this.networkConfig = null;
        this.transientVolumeState = null;
    }

    protected VmInstance(final OwnerFullName ownerFullName, final String instanceId2) {
        super(ownerFullName, instanceId2);
        this.expiration = null;
        this.runtimeState = null;
        this.vmId = null;
        this.bootRecord = null;
        this.launchRecord = null;
        this.placement = null;
        this.privateNetwork = null;
        this.usageStats = null;
        this.networkConfig = null;
        this.transientVolumeState = null;
    }

    protected VmInstance() {
        this.expiration = null;
        this.vmId = null;
        this.bootRecord = null;
        this.launchRecord = null;
        this.placement = null;
        this.privateNetwork = null;
        this.networkIndex = null;
        this.usageStats = null;
        this.runtimeState = null;
        this.networkConfig = null;
        this.transientVolumeState = null;
    }

    /**
     * Clear references that are not valid for a terminated instance
     */
    public void clearReferences() {
        if (bootRecord.getArchitecture() == null)
            bootRecord.setArchitecture(bootRecord.getMachine().getArchitecture());
        bootRecord.setMachine();
        bootRecord.setKernel();
        bootRecord.setRamdisk();
        bootRecord.setVpc(null);
        bootRecord.setSubnet(null);
        clearRunReferences();
    }

    /**
     * Clear any references that are not valid for a stopped instance
     */
    public void clearRunReferences() {
        getRuntimeState().setServiceTag(null);
    }

    public void updatePublicAddress(final String publicAddr, final String publicDnsName) {
        this.getNetworkConfig().setPublicAddress(publicAddr);
        this.getNetworkConfig().setPublicDnsName(dnsHostnamesEnabled() ? publicDnsName : "");
    }

    public void updatePrivateAddress(final String privateAddr, final String privateDnsName) {
        this.getNetworkConfig().setPrivateAddress(privateAddr);
        this.getNetworkConfig().setPrivateDnsName(privateDnsName);
    }

    public void updateMacAddress(final String macAddress) {
        if (getMacAddress() == null) {
            getNetworkConfig().setMacAddress(macAddress);
        }
    }

    public VmRuntimeState getRuntimeState() {
        if (this.runtimeState == null) {
            this.runtimeState = new VmRuntimeState(this);
        }
        return this.runtimeState;
    }

    private boolean dnsHostnamesEnabled() {
        final Vpc vpc = getBootRecord().getVpc();
        return vpc == null || Objects.firstNonNull(vpc.getDnsHostnames(), Boolean.FALSE);
    }

    public void store() {
        this.updateTimeStamps();
        this.firePersist();
        this.fireUsageEvent();
    }

    private void firePersist() {
        final EntityTransaction db = Entities.get(VmInstance.class);
        try {
            if (Entities.isPersistent(this))
                try {
                    Entities.merge(this);
                    db.commit();
                } catch (final Exception ex) {
                    LOG.debug(ex);
                }
        } catch (final Exception ex) {
            Logs.extreme().error(ex, ex);
        } finally {
            if (db.isActive())
                db.rollback();
        }
    }

    private void fireUsageEvent() {
        if (VmState.RUNNING.equals(this.getState())) {
            try {
                final OwnerFullName owner = this.getOwner();
                final String userId = owner.getUserId();
                final String userName = owner.getUserName();
                final String accountId = owner.getAccountNumber();

                ListenerRegistry.getInstance()
                        .fireEvent(new InstanceCreationEvent(getInstanceUuid(), getDisplayName(),
                                this.bootRecord.getVmType().getName(), userId, userName, accountId,
                                Accounts.lookupAccountAliasById(accountId), this.placement.getPartitionName()));
            } catch (final Exception ex) {
                LOG.error(ex, ex);
            }
        }
    }

    public synchronized long getSplitTime() {
        final long time = System.currentTimeMillis();
        final long split = time - super.getLastUpdateTimestamp().getTime();
        return split;
    }

    public synchronized long getCreationSplitTime() {
        final long time = System.currentTimeMillis();
        final long split = time - super.getCreationTimestamp().getTime();
        return split;
    }

    public String getImageId() {
        return this.bootRecord.getDisplayMachineImageId();
    }

    @Nullable
    public String getRamdiskId() {
        return this.bootRecord.getDisplayRamdiskImageId();
    }

    @Nullable
    public String getKernelId() {
        return this.bootRecord.getDisplayKernelImageId();
    }

    public boolean hasPublicAddress() {
        return (this.networkConfig != null)
                && !(VmNetworkConfig.DEFAULT_IP.equals(this.getNetworkConfig().getPublicAddress()) || this
                        .getNetworkConfig().getPrivateAddress().equals(this.getNetworkConfig().getPublicAddress()));
    }

    public String getInstanceId() {
        return super.getDisplayName();
    }

    public VmType getVmType() {
        return this.bootRecord.getVmType();
    }

    public boolean isUsePrivateAddressing() {
        // allow for null value
        return Boolean.TRUE.equals(this.getNetworkConfig().getUsePrivateAddressing());
    }

    public String getPrivateAddress() {
        return this.getNetworkConfig().getPrivateAddress();
    }

    public String getPublicAddress() {
        return this.getNetworkConfig().getPublicAddress();
    }

    public String getPrivateDnsName() {
        return this.getNetworkConfig().getPrivateDnsName();
    }

    public String getPublicDnsName() {
        return this.getNetworkConfig().getPublicDnsName();
    }

    public String getMacAddress() {
        return this.getNetworkConfig().getMacAddress();
    }

    public List<NetworkInterface> getNetworkInterfaces() {
        return this.getNetworkConfig().getNetworkInterfaces();
    }

    public String getPasswordData() {
        return this.getRuntimeState().getPasswordData();
    }

    public void updatePasswordData(final String passwordData) {
        this.getRuntimeState().setPasswordData(passwordData);
    }

    /**
     * @return the platform
     */
    public String getPlatform() {
        return this.bootRecord.getPlatform().toString();
    }

    public String getDisplayPlatform() {
        return Platform.windows == this.bootRecord.getPlatform() ? Platform.windows.name() : "";
    }

    @Nullable
    public String getSubnetId() {
        return this.bootRecord.getSubnetId();
    }

    @Nullable
    public String getVpcId() {
        return this.bootRecord.getVpcId();
    }

    @Override
    public String getPartition() {
        return this.placement.getPartitionName();
    }

    public String getInstanceUuid() {
        return this.getNaturalId();
    }

    public static VmInstance named(final String instanceId) {
        return new VmInstance(null, instanceId);
    }

    public static VmInstance named(final OwnerFullName ownerFullName, final String instanceId) {
        return new VmInstance(ownerFullName, instanceId);
    }

    public static VmInstance withToken(final OwnerFullName ownerFullName, final String clientToken) {
        return new VmInstance(ownerFullName, new VmId(null, null, clientToken, null));
    }

    public static VmInstance withUuid(final String uuid) {
        final VmInstance example = new VmInstance();
        example.setNaturalId(uuid);
        return example;
    }

    @Override
    public FullName getFullName() {
        return FullName.create.vendor("euca").region(ComponentIds.lookup(Eucalyptus.class).name())
                .namespace(this.getOwnerAccountNumber()).relativeId("instance", this.getDisplayName());
    }

    public enum Reason implements Predicate<VmInstance> {
        NORMAL(""), EXPIRED("Instance expired after not being reported."), FAILED(
                "The instance failed to start on the NC."), USER_TERMINATED(true, "User terminated."), USER_STOPPED(
                        true, "User stopped."), USER_STARTED(true, "User started."), APPEND("");

        private final boolean user;
        private final String message;

        Reason(final String message) {
            this(false, message);
        }

        Reason(final boolean user, final String message) {
            this.user = user;
            this.message = message;
        }

        public boolean user() {
            return user;
        }

        @Override
        public String toString() {
            return this.message;
        }

        @Override
        public boolean apply(final VmInstance vmInstance) {
            return this.equals(vmInstance.getRuntimeState().reason());
        }
    }

    public PrivateNetworkIndex getNetworkIndex() {
        return this.networkIndex;
    }

    private Boolean getPrivateNetwork() {
        return this.privateNetwork;
    }

    public Collection<VmInstanceTag> getTags() {
        return tags;
    }

    public Set<NetworkGroup> getNetworkGroups() {
        return (Set<NetworkGroup>) (this.networkGroups != null ? this.networkGroups : Sets.newHashSet());
    }

    public Set<NetworkGroupId> getNetworkGroupIds() {
        return this.networkGroupIds;
    }

    static long getSerialversionuid() {
        return serialVersionUID;
    }

    static Logger getLOG() {
        return LOG;
    }

    static String getDEFAULT_IP() {
        return VmNetworkConfig.DEFAULT_IP;
    }

    static String getDEFAULT_TYPE() {
        return DEFAULT_TYPE;
    }

    VmId getVmId() {
        return this.vmId;
    }

    public VmBootRecord getBootRecord() {
        return this.bootRecord;
    }

    VmUsageStats getUsageStats() {
        return this.usageStats;
    }

    public VmLaunchRecord getLaunchRecord() {
        return this.launchRecord;
    }

    VmPlacement getPlacement() {
        return this.placement;
    }

    public Partition lookupPartition() {
        return this.placement.lookupPartition();
    }

    /**
     *
     */
    public VmVolumeAttachment lookupVolumeAttachmentByDevice(final String volumeDevice) {
        final EntityTransaction db = Entities.get(VmInstance.class);
        try {
            final VmInstance entity = Entities.merge(this);
            VmVolumeAttachment ret;
            try {
                ret = entity.getTransientVolumeState().lookupVolumeAttachmentByDevice(volumeDevice);
            } catch (final Exception ex) {
                ret = Iterables.find(entity.getBootRecord().getPersistentVolumes(),
                        VmVolumeAttachment.volumeDeviceFilter(volumeDevice));
            }
            db.commit();
            return ret;
        } catch (final NoSuchElementException ex) {
            Logs.extreme().error(ex, ex);
            throw ex;
        } catch (final Exception ex) {
            Logs.extreme().error(ex, ex);
            throw new NoSuchElementException("Failed to lookup volume with device: " + volumeDevice);
        } finally {
            if (db.isActive())
                db.rollback();
        }
    }

    /**
     *
     */
    public VmVolumeAttachment lookupVolumeAttachment(final String volumeId) {
        final EntityTransaction db = Entities.get(VmInstance.class);
        try {
            final VmInstance entity = Entities.isPersistent(this) ? this : Entities.uniqueResult(this);
            VmVolumeAttachment volumeAttachment;
            try {
                volumeAttachment = entity.getTransientVolumeState().lookupVolumeAttachment(volumeId);
            } catch (final NoSuchElementException ex) {
                volumeAttachment = Iterables.find(entity.getBootRecord().getPersistentVolumes(),
                        VmVolumeAttachment.volumeIdFilter(volumeId));
            }
            db.commit();
            return volumeAttachment;
        } catch (final Exception ex) {
            throw new NoSuchElementException("Failed to lookup volume: " + volumeId);
        } finally {
            if (db.isActive())
                db.rollback();
        }
    }

    /**
     *
     */
    public boolean eachVolumeAttachment(final Predicate<VmVolumeAttachment> predicate) {
        if (VmStateSet.DONE.contains(this.getState())
                && !VmStateSet.EXPECTING_TEARDOWN.contains(this.getLastState())) {
            return false;
        } else {
            final EntityTransaction db = Entities.get(VmInstance.class);
            try {
                final VmInstance entity = Entities.merge(this);
                Set<VmVolumeAttachment> persistentAttachments = Sets
                        .<VmVolumeAttachment>newHashSet(entity.getBootRecord().getPersistentVolumes());
                boolean ret = Iterables.all(persistentAttachments, new Predicate<VmVolumeAttachment>() {

                    @Override
                    public boolean apply(final VmVolumeAttachment arg0) {
                        return predicate.apply(arg0);
                    }
                });

                ret |= entity.getTransientVolumeState().eachVolumeAttachment(new Predicate<VmVolumeAttachment>() {

                    @Override
                    public boolean apply(final VmVolumeAttachment arg0) {
                        return predicate.apply(arg0);
                    }
                });
                db.commit();
                return ret;
            } catch (final Exception ex) {
                Logs.extreme().error(ex, ex);
                db.rollback();
                return false;
            }
        }
    }

    /**
     *
     */
    public String getServiceTag() {
        return this.getRuntimeState().getServiceTag();
    }

    /**
     *
     */
    public String getReservationId() {
        return this.vmId.getReservationId();
    }

    /**
     *
     */
    public byte[] getUserData() {
        return this.bootRecord.getUserData();
    }

    public void setUserDataAsString(final String userData) {
        if (userData == null || userData.length() <= 0) {
            this.bootRecord.setUserData(new byte[0]);
        } else {
            this.bootRecord.setUserData(userData.getBytes());
        }
    }

    public void setNetworkIndex(final PrivateNetworkIndex networkIndex) {
        this.networkIndex = networkIndex;
    }

    /**
     * Get the state suitable for EC2 API.
     *
     * @see #getState
     */
    public VmState getDisplayState() {
        return getState().getDisplayState();
    }

    @Nonnull
    public String getDisplayPublicDnsName() {
        return VmStateSet.TORNDOWN.apply(this) ? "" : dns() ? getPublicDnsName() : getDisplayPublicAddress();
    }

    @Nonnull
    public String getDisplayPublicAddress() {
        return VmStateSet.TORNDOWN.apply(this) ? ""
                : VmNetworkConfig.DEFAULT_IP.equals(
                        Objects.firstNonNull(Strings.emptyToNull(getPublicAddress()), VmNetworkConfig.DEFAULT_IP))
                                ? getVpcId() == null ? getDisplayPrivateAddress() : ""
                                : getPublicAddress();
    }

    @Nonnull
    public String getDisplayPrivateDnsName() {
        return VmStateSet.TORNDOWN.apply(this) ? "" : dns() ? getPrivateDnsName() : getDisplayPrivateAddress();
    }

    @Nonnull
    public String getDisplayPrivateAddress() {
        return VmStateSet.TORNDOWN.apply(this) ? ""
                : VmNetworkConfig.DEFAULT_IP.equals(
                        Objects.firstNonNull(Strings.emptyToNull(getPrivateAddress()), VmNetworkConfig.DEFAULT_IP))
                                ? VmNetworkConfig.DEFAULT_IP
                                : getPrivateAddress();
    }

    private static boolean dns() {
        return StackConfiguration.USE_INSTANCE_DNS && !ComponentIds.lookup(Dns.class).runLimitedServices();
    }

    @TypeMapper
    public enum Transform implements Function<VmInstance, RunningInstancesItemType> {
        INSTANCE;

        /**
         * @see Supplier#get()
         */
        @Override
        public RunningInstancesItemType apply(final VmInstance input) {
            if (!Entities.isPersistent(input)) {
                throw new TransientEntityException(input.toString());
            } else {
                try {
                    final RunningInstancesItemType runningInstance = new RunningInstancesItemType();

                    runningInstance.setAmiLaunchIndex(Integer.toString(input.getLaunchRecord().getLaunchIndex()));
                    final VmState displayState = input.getDisplayState();
                    runningInstance.setStateCode(Integer.toString(displayState.getCode()));
                    runningInstance.setStateName(displayState.getName());
                    runningInstance.setPlatform(input.getPlatform());

                    runningInstance.setInstanceId(input.getVmId().getInstanceId());
                    //ASAP:FIXME:GRZE: restore.
                    runningInstance.setProductCodes(new ArrayList<String>());
                    runningInstance.setImageId(input.getImageId());
                    runningInstance.setKernel(input.getKernelId());
                    runningInstance.setRamdisk(input.getRamdiskId());
                    runningInstance.setPlatform(Strings.emptyToNull(input.getDisplayPlatform()));
                    runningInstance.setDnsName(input.getDisplayPublicDnsName());
                    runningInstance.setIpAddress(Strings.emptyToNull(input.getDisplayPublicAddress()));
                    runningInstance.setPrivateDnsName(input.getDisplayPrivateDnsName());
                    runningInstance.setPrivateIpAddress(Strings.emptyToNull(input.getDisplayPrivateAddress()));
                    if (input.getBootRecord() == null || input.getBootRecord().getArchitecture() == null) {
                        LOG.debug("WARNING: No architecture set for instance " + input.getInstanceId()
                                + ", defaulting to x86_64");
                        runningInstance.setArchitecture("x86_64");
                    } else {
                        runningInstance.setArchitecture(input.getBootRecord().getArchitecture().toString());
                    }
                    runningInstance.setReason(input.runtimeState.getDisplayReason());
                    if (input.getBootRecord().getSshKeyPair() != null) {
                        runningInstance.setKeyName(input.getBootRecord().getSshKeyPair().getName());
                        if ((runningInstance.getKeyName() != null) && (runningInstance.getKeyName().isEmpty()))
                            runningInstance.setKeyName(null);
                    } else
                        runningInstance.setKeyName("");

                    runningInstance.setInstanceType(input.getVmType().getName());
                    runningInstance.setPlacement(input.getPlacement().getPartitionName());

                    runningInstance.setLaunchTime(input.getLaunchRecord().getLaunchTime());
                    runningInstance.setClientToken(input.getClientToken());

                    runningInstance.setVpcId(input.getVpcId());
                    runningInstance.setSubnetId(input.getSubnetId());

                    if (!Strings.isNullOrEmpty(input.getIamInstanceProfileId())) {
                        runningInstance.setIamInstanceProfile(new IamInstanceProfile(
                                input.getIamInstanceProfileArn(), input.getIamInstanceProfileId()));
                    } else if (!Strings.isNullOrEmpty(input.getIamInstanceProfileArn())
                            && input.getIamInstanceProfileArn().startsWith("arn:")) {

                        final String rawName = input.getIamInstanceProfileArn();
                        final int nameIndex = input.getIamInstanceProfileArn().lastIndexOf('/');
                        final String name = input.getIamInstanceProfileArn().substring(nameIndex + 1,
                                rawName.length());

                        try {
                            BaseInstanceProfile instanceProfile = Accounts
                                    .lookupInstanceProfileByName(input.getOwnerAccountNumber(), name);
                            final String profileArn = Accounts.getInstanceProfileArn(instanceProfile);
                            IamInstanceProfile iamInstanceProfile = new IamInstanceProfile();
                            iamInstanceProfile.setArn(profileArn);
                            iamInstanceProfile.setId(instanceProfile.getInstanceProfileId());
                            runningInstance.setIamInstanceProfile(iamInstanceProfile);

                        } catch (NoSuchElementException nsee) {
                            LOG.debug("profile arn : " + name, nsee);
                        }

                    } else if (!Strings.isNullOrEmpty(input.getIamInstanceProfileArn())
                            && !input.getIamInstanceProfileArn().startsWith("arn:")) {

                        try {
                            final BaseInstanceProfile instanceProfile = Accounts.lookupInstanceProfileByName(
                                    input.getOwnerAccountNumber(), input.getIamInstanceProfileArn());
                            final String profileArn = Accounts.getInstanceProfileArn(instanceProfile);
                            IamInstanceProfile iamInstanceProfile = new IamInstanceProfile();
                            iamInstanceProfile.setArn(profileArn);
                            iamInstanceProfile.setId(instanceProfile.getInstanceProfileId());
                            runningInstance.setIamInstanceProfile(iamInstanceProfile);
                        } catch (NoSuchElementException nsee) {
                            LOG.debug("profile name : " + input.getIamInstanceProfileArn(), nsee);
                        }
                    }

                    if (input.getMonitoring()) {
                        runningInstance.setMonitoring("enabled");
                    } else {
                        runningInstance.setMonitoring("disabled");
                    }

                    runningInstance.getGroupSet().addAll(Collections2.transform(input.getNetworkGroupIds(),
                            TypeMappers.lookup(NetworkGroupId.class, GroupItemType.class)));
                    Collections.sort(runningInstance.getGroupSet());

                    runningInstance.setVirtualizationType(input.getVirtualizationType());

                    if (input.isBlockStorage()) {
                        runningInstance.setRootDeviceType(ROOT_DEVICE_TYPE_EBS);
                    }

                    if (input.getBootRecord().hasPersistentVolumes()) {
                        for (final VmVolumeAttachment attachedVol : input.getBootRecord().getPersistentVolumes()) {
                            runningInstance.getBlockDevices()
                                    .add(new InstanceBlockDeviceMapping(attachedVol.getDevice(),
                                            attachedVol.getVolumeId(), attachedVol.getStatus(),
                                            attachedVol.getAttachTime(), attachedVol.getDeleteOnTerminate()));
                            if (attachedVol.getIsRootDevice()) {
                                runningInstance.setRootDeviceName(attachedVol.getDevice());
                            }
                        }
                    }
                    for (final VmVolumeAttachment attachedVol : input.getTransientVolumeState().getAttachments()) {
                        runningInstance.getBlockDevices()
                                .add(new InstanceBlockDeviceMapping(attachedVol.getDevice(),
                                        attachedVol.getVolumeId(), attachedVol.getStatus(),
                                        attachedVol.getAttachTime(), attachedVol.getDeleteOnTerminate()));
                    }

                    for (final NetworkInterface networkInterface : input.getNetworkInterfaces()) {
                        if (runningInstance.getNetworkInterfaceSet() == null) {
                            runningInstance.setNetworkInterfaceSet(new InstanceNetworkInterfaceSetType());
                        }
                        runningInstance.getNetworkInterfaceSet().getItem()
                                .add(new InstanceNetworkInterfaceSetItemType(networkInterface.getDisplayName(),
                                        networkInterface.getSubnet().getDisplayName(),
                                        networkInterface.getVpc().getDisplayName(),
                                        networkInterface.getDescription(), networkInterface.getOwnerAccountNumber(),
                                        String.valueOf(networkInterface.getState()),
                                        networkInterface.getMacAddress(), networkInterface.getPrivateIpAddress(),
                                        networkInterface.getPrivateDnsName(), networkInterface.getSourceDestCheck(),
                                        new GroupSetType(Collections2.transform(networkInterface.getNetworkGroups(),
                                                TypeMappers.lookup(NetworkGroup.class, GroupItemType.class))),
                                        new InstanceNetworkInterfaceAttachmentType(
                                                networkInterface.getAttachment().getAttachmentId(),
                                                networkInterface.getAttachment().getDeviceIndex(),
                                                String.valueOf(networkInterface.getAttachment().getStatus()),
                                                networkInterface.getAttachment().getAttachTime(),
                                                networkInterface.getAttachment().getDeleteOnTerminate()),
                                        networkInterface.isAssociated()
                                                ? new InstanceNetworkInterfaceAssociationType(
                                                        networkInterface.getAssociation().getPublicIp(),
                                                        networkInterface.getAssociation().getPublicDnsName(),
                                                        networkInterface.getAssociation().getDisplayIpOwnerId())
                                                : null,
                                        new InstancePrivateIpAddressesSetType(
                                                Lists.newArrayList(new InstancePrivateIpAddressesSetItemType(
                                                        networkInterface.getPrivateIpAddress(),
                                                        networkInterface.getPrivateDnsName(), true,
                                                        networkInterface.isAssociated()
                                                                ? new InstanceNetworkInterfaceAssociationType(
                                                                        networkInterface.getAssociation()
                                                                                .getPublicIp(),
                                                                        networkInterface.getAssociation()
                                                                                .getPublicDnsName(),
                                                                        networkInterface.getAssociation()
                                                                                .getDisplayIpOwnerId())
                                                                : null)))));
                    }

                    return runningInstance;
                } catch (final NoSuchElementException ex) {
                    throw ex;
                } catch (final Exception ex) {
                    throw new NoSuchElementException("Failed to lookup vm instance: " + input);
                }
            }
        }
    }

    @TypeMapper
    public enum ReservationTransform implements Function<VmInstance, ReservationInfoType> {
        INSTANCE;

        @Override
        public ReservationInfoType apply(final VmInstance instance) {
            return new ReservationInfoType(instance.getReservationId(), instance.getOwner().getAccountNumber(),
                    Collections2.transform(instance.getNetworkGroupIds(),
                            TypeMappers.lookup(NetworkGroupId.class, GroupItemType.class)));
        }
    }

    @TypeMapper
    public enum NetworkGroupIdTransform implements Function<NetworkGroup, NetworkGroupId> {
        INSTANCE;

        @Override
        public NetworkGroupId apply(final NetworkGroup networkGroup) {
            return new NetworkGroupId(networkGroup.getGroupId(), networkGroup.getName());
        }
    }

    @TypeMapper
    public enum NetworkGroupIdToGroupItemTypeTransform implements Function<NetworkGroupId, GroupItemType> {
        INSTANCE;

        @Override
        public GroupItemType apply(final NetworkGroupId networkGroupId) {
            return new GroupItemType(networkGroupId.getGroupId(), networkGroupId.getGroupName());
        }
    }

    @TypeMapper
    public enum NetworkGroupToGroupItemTypeTransform implements Function<NetworkGroup, GroupItemType> {
        INSTANCE;

        @Override
        public GroupItemType apply(final NetworkGroup networkGroupId) {
            return new GroupItemType(networkGroupId.getGroupId(), networkGroupId.getName());
        }
    }

    public boolean isBlockStorage() {
        return this.bootRecord.isBlockStorage();
    }

    public boolean isEbsOptimized() {
        return false;
    }

    public String getInstanceType() {
        return getVmType().getName();
    }

    @Override
    public void setNaturalId(final String naturalId) {
        super.setNaturalId(naturalId);
    }

    public VmVolumeState getTransientVolumeState() {
        if (this.transientVolumeState == null) {
            this.transientVolumeState = new VmVolumeState(this);
        }
        return this.transientVolumeState;
    }

    @Override
    public String toString() {
        final StringBuilder builder2 = new StringBuilder();
        builder2.append("VmInstance:");
        if (this.networkConfig != null)
            builder2.append("networkConfig=").append(this.getNetworkConfig()).append(":");
        if (this.vmId != null)
            builder2.append("vmId=").append(this.vmId).append(":");
        if (this.bootRecord != null)
            builder2.append("bootRecord=").append(this.bootRecord).append(":");
        if (this.usageStats != null)
            builder2.append("usageStats=").append(this.usageStats).append(":");
        if (this.launchRecord != null)
            builder2.append("launchRecord=").append(this.launchRecord).append(":");
        if (this.runtimeState != null)
            builder2.append("runtimeState=").append(this.runtimeState).append(":");
        if (this.transientVolumeState != null)
            builder2.append("transientVolumeState=").append(this.transientVolumeState).append(":");
        if (this.placement != null)
            builder2.append("placement=").append(this.placement).append(":");
        if (this.privateNetwork != null)
            builder2.append("privateNetwork=").append(this.privateNetwork).append(":");
        if (Entities.isReadable(this.networkGroups))
            builder2.append("networkGroups=").append(this.networkGroups).append(":");
        if (Entities.isReadable(this.networkIndex))
            builder2.append("networkIndex=").append(this.networkIndex);
        return builder2.toString();
    }

    private VmNetworkConfig getNetworkConfig() {
        if (this.networkConfig == null) {
            this.networkConfig = new VmNetworkConfig(this);
        }
        return this.networkConfig;
    }

    private void setNetworkGroups(final Set<NetworkGroup> networkGroups) {
        this.networkGroups = networkGroups;
    }

    /**
     *
     */
    void setNetworkConfig(final VmNetworkConfig networkConfig) {
        this.networkConfig = networkConfig;
    }

    public Date getExpiration() {
        return this.expiration;
    }

    public Integer getLaunchIndex() {
        return this.getLaunchRecord().getLaunchIndex();
    }

    @Nullable
    public String getClientToken() {
        return this.getVmId().getClientToken();
    }

    /**
     * For instances created prior to 3.4. this is the ARN or the profile name.
     */
    @Nullable
    public String getIamInstanceProfileArn() {
        return getBootRecord().getIamInstanceProfileArn();
    }

    @Nullable
    public String getIamInstanceProfileId() {
        return getBootRecord().getIamInstanceProfileId();
    }

    @Nullable
    public String getIamRoleArn() {
        return getBootRecord().getIamRoleArn();
    }

    public SshKeyPair getKeyPair() {
        return this.getBootRecord().getSshKeyPair();
    }

    public String getVirtualizationType() {
        return this.getBootRecord().getDisplayVirtualizationType().toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((this.vmId == null) ? 0 : this.vmId.hashCode());
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        final VmInstance other = (VmInstance) obj;
        if (this.vmId == null) {
            if (other.vmId != null) {
                return false;
            }
        } else if (!this.vmId.equals(other.vmId)) {
            return false;
        }
        return true;
    }

    /**
     *
     */
    public static VmInstance create() {
        return new VmInstance();
    }

    public static VmInstance exampleWithPublicIp(String ip) {
        VmInstance vmExample = VmInstance.create();
        vmExample.setNetworkConfig(VmNetworkConfig.exampleWithPublicIp(ip));
        return vmExample;
    }

    public static VmInstance exampleWithPrivateIp(String ip) {
        VmInstance vmExample = VmInstance.create();
        vmExample.setNetworkConfig(VmNetworkConfig.exampleWithPrivateIp(ip));
        return vmExample;
    }

    public static VmInstance exampleResource(final OwnerFullName owner, final String availabilityZone,
            final String instanceProfileArn, final String instanceType, final boolean isBlockStorage) {
        return new VmInstance(owner, "") {
            @Override
            public String getIamInstanceProfileArn() {
                return instanceProfileArn;
            }

            @Override
            public String getInstanceType() {
                return instanceType;
            }

            @Override
            public String getPartition() {
                return availabilityZone;
            }

            @Override
            public boolean isBlockStorage() {
                return isBlockStorage;
            }
        };
    }

    public static Function<VmInstance, String> clientToken() {
        return StringPropertyFunctions.CLIENT_TOKEN;
    }

    private void setBootRecord(VmBootRecord bootRecord) {
        this.bootRecord = bootRecord;
    }

    @TypeMapper
    public enum StatusTransform implements Function<VmInstance, InstanceStatusItemType> {
        INSTANCE;

        @Override
        public InstanceStatusItemType apply(final VmInstance instance) {
            final InstanceStatusItemType instanceStatusItemType = new InstanceStatusItemType();
            final VmState displayState = instance.getDisplayState();

            instanceStatusItemType.setInstanceId(instance.getInstanceId());
            instanceStatusItemType.setAvailabilityZone(instance.getPlacement().getPartitionName());
            instanceStatusItemType.setEventsSet(buildEventSet(instance));

            final InstanceStateType state = new InstanceStateType();
            state.setCode(displayState.getCode());
            state.setName(displayState.getName());
            instanceStatusItemType.setInstanceState(state);
            instanceStatusItemType
                    .setInstanceStatus(buildStatus(Optional.of(Pair.pair(VmRuntimeState.InstanceStatus.Ok,
                            Pair.pair(VmRuntimeState.ReachabilityStatus.Passed, Optional.<Date>absent())))));
            instanceStatusItemType.setSystemStatus(buildStatus(instance));

            return instanceStatusItemType;
        }

        public static Optional<InstanceStatusEventType> getEventInfo(final VmInstance instance) {
            Optional<InstanceStatusEventType> eventInfo = Optional.absent();
            if (VmState.RUNNING.apply(instance)) {
                final VmRuntimeState vmRuntimeState = instance.getRuntimeState();
                final Date unreachableTimestamp = vmRuntimeState.getUnreachableTimestamp();
                if (Boolean.TRUE.equals(vmRuntimeState.getZombie()) && unreachableTimestamp != null) {
                    final InstanceStatusEventType event = new InstanceStatusEventType();
                    event.setCode("instance-retirement");
                    event.setDescription("Instance recovered with degraded functionality");
                    event.setNotAfter(new Date(unreachableTimestamp.getTime() + TimeUnit.DAYS.toMillis(365)));
                    event.setNotBefore(unreachableTimestamp);
                    eventInfo = Optional.of(event);
                }
            }
            return eventInfo;
        }

        private InstanceStatusEventsSetType buildEventSet(final VmInstance instance) {
            InstanceStatusEventsSetType eventSet = null;
            final Optional<InstanceStatusEventType> eventInfo = getEventInfo(instance);
            if (eventInfo.isPresent()) {
                eventSet = new InstanceStatusEventsSetType();
                eventSet.getItem().add(eventInfo.get());
            }
            return eventSet;
        }

        private InstanceStatusType buildStatus(final VmInstance instance) {
            if (VmState.RUNNING.apply(instance)) {
                final VmRuntimeState vmRuntimeState = instance.getRuntimeState();
                final VmRuntimeState.InstanceStatus instanceStatus = Objects
                        .firstNonNull(vmRuntimeState.getInstanceStatus(), VmRuntimeState.InstanceStatus.Ok);
                final VmRuntimeState.ReachabilityStatus reachabilityStatus = Objects.firstNonNull(
                        vmRuntimeState.getReachabilityStatus(), VmRuntimeState.ReachabilityStatus.Passed);
                final Date unreachabilityTimestamp = reachabilityStatus != VmRuntimeState.ReachabilityStatus.Passed
                        ? vmRuntimeState.getUnreachableTimestamp()
                        : null;
                return buildStatus(Optional
                        .of(Pair.pair(instanceStatus, Pair.ropair(reachabilityStatus, unreachabilityTimestamp))));
            } else {
                return buildStatus(
                        Optional.<Pair<VmRuntimeState.InstanceStatus, Pair<VmRuntimeState.ReachabilityStatus, Optional<Date>>>>absent());
            }
        }

        private InstanceStatusType buildStatus(
                final Optional<Pair<VmRuntimeState.InstanceStatus, Pair<VmRuntimeState.ReachabilityStatus, Optional<Date>>>> status) {
            final InstanceStatusType instanceStatusType = new InstanceStatusType();
            if (status.isPresent()) {
                final VmRuntimeState.InstanceStatus instanceStatus = status.get().getLeft();
                final VmRuntimeState.ReachabilityStatus reachabilityStatus = status.get().getRight().getLeft();
                final Optional<Date> unreachableTimestamp = status.get().getRight().getRight();
                final InstanceStatusDetailsSetItemType statusDetailsItem = new InstanceStatusDetailsSetItemType();
                statusDetailsItem.setName("reachability");
                statusDetailsItem.setStatus(reachabilityStatus.toString());
                statusDetailsItem.setImpairedSince(unreachableTimestamp.orNull());

                final InstanceStatusDetailsSetType statusDetails = new InstanceStatusDetailsSetType();
                statusDetails.getItem().add(statusDetailsItem);

                instanceStatusType.setStatus(instanceStatus.toString());
                instanceStatusType.setDetails(statusDetails);
            } else {
                instanceStatusType.setStatus("not-applicable");
            }
            return instanceStatusType;
        }
    }

    public Boolean getMonitoring() {
        return this.getBootRecord().isMonitoring();
    }

    public VmMigrationTask getMigrationTask() {
        return this.runtimeState.getMigrationTask();
    }

    @PrePersist
    @PreUpdate
    private void updateGroups() {
        networkGroupIds.clear();
        CollectionUtils.fluent(networkGroups)
                .transform(TypeMappers.lookup(NetworkGroup.class, NetworkGroupId.class)).copyInto(networkGroupIds);
    }

    @EntityUpgrade(entities = { VmInstance.class }, since = Version.v3_3_0, value = Eucalyptus.class)
    public enum VmInstanceUpgrade_3_3_0 implements Predicate<Class> {
        INSTANCE;
        private static Logger LOG = Logger.getLogger(VmInstance.VmInstanceUpgrade_3_3_0.class);

        @Override
        public boolean apply(Class arg0) {
            EntityTransaction db = Entities.get(VmInstance.class);
            try {
                List<VmInstance> instances = Entities.query(new VmInstance());
                for (VmInstance vm : instances) {
                    if (vm.getBootRecord().getMachine() instanceof BlockStorageImageInfo) {
                        LOG.info("Upgrading bfebs VmInstance: " + vm.toString());
                        if (vm.getBootRecord().getEphemeralStorage().isEmpty()) {
                            LOG.info("Adding ephemeral disk at " + Images.DEFAULT_EPHEMERAL_DEVICE);
                            vm.getBootRecord().getEphemeralStorage().add(
                                    new VmEphemeralAttachment(vm, "ephemeral0", Images.DEFAULT_EPHEMERAL_DEVICE));
                        }

                        // Pre 3.3 code allowed only one persistent volume i.e. the root volume. Check before upgrading
                        if (vm.getBootRecord().getPersistentVolumes().size() == 1) {
                            VmVolumeAttachment attachment = vm.getBootRecord().getPersistentVolumes().iterator()
                                    .next();
                            LOG.info("Found the only VmVolumeAttachment: " + attachment.toString());
                            LOG.info("Setting root device flag to true");
                            attachment.setIsRootDevice(Boolean.TRUE);
                            LOG.info("Changing the device name to " + Images.DEFAULT_ROOT_DEVICE);
                            attachment.setDevice(Images.DEFAULT_ROOT_DEVICE);
                        } else { // This should not be the case updating to 3.3
                            // If the instance has more or less than one persistent volume, iterate through them and update the one with device "/dev/sda1"
                            for (VmVolumeAttachment attachment : vm.getBootRecord().getPersistentVolumes()) {
                                LOG.info("Found VmVolumeAttachment: " + attachment.toString());
                                if (attachment.getDevice()
                                        .equalsIgnoreCase(Images.DEFAULT_PARTITIONED_ROOT_DEVICE)) {
                                    LOG.info("Setting root device flag to true");
                                    attachment.setIsRootDevice(Boolean.TRUE);
                                    LOG.info("Changing the device name from "
                                            + Images.DEFAULT_PARTITIONED_ROOT_DEVICE + " to "
                                            + Images.DEFAULT_ROOT_DEVICE);
                                    attachment.setDevice(Images.DEFAULT_ROOT_DEVICE);
                                }
                            }
                        }
                    }
                    Entities.persist(vm);
                }
                db.commit();
                return true;
            } catch (Exception ex) {
                LOG.error("Error upgrading VmInstance: ", ex);
                db.rollback();
                throw Exceptions.toUndeclared(ex);
            }
        }
    }
}