com.cloud.migration.Db20to21MigrationUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.cloud.migration.Db20to21MigrationUtil.java

Source

/**
 *  Copyright (C) 2010 Cloud.com, Inc.  All rights reserved.
 * 
 * This software is licensed under the GNU General Public License v3 or later.
 * 
 * It 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, either version 3 of the License, or any later version.
 * 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/>.
 * 
 */

package com.cloud.migration;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.NoSuchAlgorithmException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Random;
import java.util.Scanner;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationVO;
import com.cloud.configuration.dao.ConfigurationDao;
import com.cloud.consoleproxy.ConsoleProxyManager;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.migration.DiskOffering21VO.Type;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StoragePoolVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.StoragePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.secondary.SecondaryStorageVmManager;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.PropertiesUtil;
import com.cloud.utils.component.ComponentLocator;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Func;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.Script;
import com.cloud.vm.ConsoleProxyVO;
import com.cloud.vm.DomainRouterVO;
import com.cloud.vm.SecondaryStorageVmVO;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.ConsoleProxyDao;
import com.cloud.vm.dao.DomainRouterDao;
import com.cloud.vm.dao.SecondaryStorageVmDao;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;

public class Db20to21MigrationUtil {
    private static final Logger s_logger = Logger.getLogger(Db20to21MigrationUtil.class);

    protected DataCenterDao _dcDao;
    protected HostPodDao _podDao;
    protected ConfigurationDao _configDao;
    protected ClusterDao _clusterDao;
    protected HostDao _hostDao;
    protected StoragePoolDao _spDao;
    protected DomainDao _domainDao;
    protected ServiceOffering20Dao _serviceOffering20Dao;
    protected DiskOffering20Dao _diskOffering20Dao;
    protected ServiceOffering21Dao _serviceOffering21Dao;
    protected DiskOffering21Dao _diskOffering21Dao;
    protected ConsoleProxyDao _consoleProxyDao;
    protected SecondaryStorageVmDao _secStorageVmDao;
    protected VMInstanceDao _vmInstanceDao;
    protected VolumeDao _volumeDao;
    protected UserVmDao _userVmDao;
    protected DomainRouterDao _routerDao;
    protected StoragePoolDao _poolDao;

    protected long _consoleProxyServiceOfferingId;
    protected long _secStorageServiceOfferingId;
    protected long _domRServiceOfferingId;
    protected boolean _isPremium = false;

    protected static class DcPod {
        long id;
        String name;
        long count;

        public DcPod() {
        }
    }

    private void migrateZones() {
        boolean createCluster = false;
        String value = _configDao.getValue("xen.create.pools.in.pod");
        if (value == null || !value.equalsIgnoreCase("true")) {
            s_logger.info(
                    "System is not configured to use Xen server pool, we will skip creating cluster for pods");
        } else {
            createCluster = true;
        }

        // Displaying summarize data center/pod configuration from old DB before we continue
        GenericSearchBuilder<DataCenterVO, DcPod> sb = _dcDao.createSearchBuilder(DcPod.class);
        sb.selectField(sb.entity().getId());
        sb.selectField(sb.entity().getName());
        sb.select("count", Func.COUNT, null);
        sb.groupBy(sb.entity().getId(), sb.entity().getName());
        sb.done();

        SearchCriteria<DcPod> sc = sb.create();
        List<DcPod> results = _dcDao.customSearchIncludingRemoved(sc, (Filter) null);
        if (results.size() > 0) {
            System.out.println("We've found following zones are deployed in your database");
            for (DcPod cols : results) {
                System.out.println("\tid: " + cols.id + ",\tname: " + cols.name + ",\tpods in zone: " + cols.count);
            }
            System.out.println("From 2.0 to 2.1, pod is required to have gateway configuration");

            for (DcPod cols : results) {
                migrateZonePods(cols.id, cols.name, createCluster);

                s_logger.info("Set system VM guest MAC in zone" + cols.name);
                migrateSystemVmGuestMacAndState(cols.id);
            }
        } else {
            System.out.println("We couldn't find any zone being deployed. Skip Zone/Pod migration");
        }
    }

    private void migrateZonePods(long zoneId, String zoneName, boolean createCluster) {
        SearchBuilder<HostPodVO> sb = _podDao.createSearchBuilder();
        sb.and("zoneId", sb.entity().getDataCenterId(), Op.EQ);
        sb.done();

        SearchCriteria<HostPodVO> sc = sb.create();
        sc.setParameters("zoneId", zoneId);

        List<HostPodVO> pods = _podDao.searchIncludingRemoved(sc, null, false, false);
        if (pods.size() > 0) {
            for (HostPodVO pod : pods) {
                System.out.println("Migrating pod " + pod.getName() + " in zone " + zoneName + "...");
                System.out.println("Current pod " + pod.getName() + " configuration as");
                System.out.println("\tCIDR: " + pod.getCidrAddress() + "/" + pod.getCidrSize());
                System.out.println("\tGateway: " + pod.getGateway());
                System.out.print("Please type your gateway address for the pod: ");

                String gateway = readInput();
                pod.setGateway(gateway);
                _podDao.update(pod.getId(), pod);
                if (createCluster) {
                    migrateHostsInPod(zoneId, pod.getId(), pod.getName());
                }

                System.out.println("Set last_host_id for VMs in pod " + pod.getName());
                migrateVmInstanceLastHostId(zoneId, pod.getId());

                System.out.println("Setup link local addresses, it will take a while, please wait...");
                String ipNums = _configDao.getValue("linkLocalIp.nums");
                int nums = Integer.parseInt(ipNums);
                if (nums > 16 || nums <= 0) {
                    nums = 10;
                }

                /*local link ip address starts from 169.254.0.2 - 169.254.(nums)*/
                String[] ipRanges = NetUtils.getLinkLocalIPRange(nums);
                _dcDao.addLinkLocalIpAddress(zoneId, pod.getId(), ipRanges[0], ipRanges[1]);
            }
        }
    }

    private void migrateHostsInPod(long zoneId, long podId, String podName) {
        System.out.println("Creating cluster for pod " + podName);

        ClusterVO cluster = null;

        SearchBuilder<HostVO> sb = _hostDao.createSearchBuilder();
        sb.and("dc", sb.entity().getDataCenterId(), Op.EQ);
        sb.and("pod", sb.entity().getPodId(), Op.EQ);
        sb.and("type", sb.entity().getType(), Op.EQ);
        sb.done();

        SearchCriteria<HostVO> sc = sb.create();
        sc.setParameters("dc", zoneId);
        sc.setParameters("pod", podId);
        sc.setParameters("type", Host.Type.Routing);

        // join cluster for hosts in pod
        List<HostVO> hostsInPod = _hostDao.searchIncludingRemoved(sc, null, false, false);
        if (hostsInPod.size() > 0) {
            if (cluster == null) {
                cluster = new ClusterVO(zoneId, podId, String.valueOf(podId));
                cluster = _clusterDao.persist(cluster);
            }

            for (HostVO host : hostsInPod) {
                host.setClusterId(cluster.getId());
                _hostDao.update(host.getId(), host);

                System.out.println("Join host " + host.getName() + " to auto-formed cluster");
            }
        }

        SearchBuilder<StoragePoolVO> sbPool = _spDao.createSearchBuilder();
        sbPool.and("dc", sbPool.entity().getDataCenterId(), Op.EQ);
        sbPool.and("pod", sbPool.entity().getPodId(), Op.EQ);
        sbPool.and("poolType", sbPool.entity().getPoolType(), Op.IN);
        sbPool.done();

        SearchCriteria<StoragePoolVO> scPool = sbPool.create();
        scPool.setParameters("dc", zoneId);
        scPool.setParameters("pod", podId);
        scPool.setParameters("poolType", StoragePoolType.NetworkFilesystem.toString(),
                StoragePoolType.IscsiLUN.toString());

        List<StoragePoolVO> sPoolsInPod = _spDao.searchIncludingRemoved(scPool, null, false, false);
        if (sPoolsInPod.size() > 0) {
            if (cluster == null) {
                cluster = new ClusterVO(zoneId, podId, String.valueOf(podId));
                cluster = _clusterDao.persist(cluster);
            }

            for (StoragePoolVO spool : sPoolsInPod) {
                spool.setClusterId(cluster.getId());
                _spDao.update(spool.getId(), spool);

                System.out.println("Join host " + spool.getName() + " to auto-formed cluster");
            }
        }
    }

    private void composeDomainPath(DomainVO domain, StringBuilder sb) {
        if (domain.getParent() == null) {
            sb.append("/");
        } else {
            DomainVO parent = _domainDao.findById(domain.getParent());
            composeDomainPath(parent, sb);

            if (domain.getName().contains("/")) {
                System.out.println(
                        "Domain " + domain.getName() + " contains invalid domain character, replace it with -");
                sb.append(domain.getName().replace('/', '-'));
            } else {
                sb.append(domain.getName());
            }
            sb.append("/");
        }
    }

    private void migrateDomains() {
        System.out.println("Migrating domains...");

        // we shouldn't have too many domains in the system, use a very dumb way to setup domain path
        List<DomainVO> domains = _domainDao.listAllIncludingRemoved();
        for (DomainVO domain : domains) {
            StringBuilder path = new StringBuilder();
            composeDomainPath(domain, path);

            System.out.println("Convert domain path, domin: " + domain.getId() + ", path:" + path.toString());

            domain.setPath(path.toString());
            _domainDao.update(domain.getId(), domain);
        }

        System.out.println("All domains have been migrated to 2.1 format");
    }

    private void migrateServiceOfferings() {
        System.out.println("Migrating service offering...");

        long seq = getServiceOfferingStartSequence();

        List<ServiceOffering20VO> oldServiceOfferings = _serviceOffering20Dao.listAllIncludingRemoved();
        for (ServiceOffering20VO so20 : oldServiceOfferings) {
            ServiceOffering21VO so21 = new ServiceOffering21VO(so20.getName(), so20.getCpu(), so20.getRamSize(),
                    so20.getSpeed(), so20.getRateMbps(), so20.getMulticastRateMbps(), so20.getOfferHA(),
                    so20.getDisplayText(), so20.getUseLocalStorage(), false, null);
            so21.setId(seq++);
            so21.setDiskSize(0);
            so21 = _serviceOffering21Dao.persist(so21);

            if (so20.getId().longValue() != so21.getId()) {
                // Update all foreign reference from old value to new value, need to be careful with foreign key constraints
                updateServiceOfferingReferences(so20.getId().longValue(), so21.getId());
            }
        }

        boolean useLocalStorage = Boolean.parseBoolean(_configDao.getValue(Config.SystemVMUseLocalStorage.key()));

        // create service offering for system VMs and update references
        int proxyRamSize = NumbersUtil.parseInt(_configDao.getValue(Config.ConsoleProxyRamSize.key()),
                ConsoleProxyManager.DEFAULT_PROXY_VM_RAMSIZE);
        ServiceOffering21VO soConsoleProxy = new ServiceOffering21VO("Fake Offering For DomP", 1, proxyRamSize, 0,
                0, 0, false, null, useLocalStorage, true, null);
        soConsoleProxy.setId(seq++);
        soConsoleProxy.setUniqueName("Cloud.com-ConsoleProxy");
        soConsoleProxy = _serviceOffering21Dao.persist(soConsoleProxy);
        _consoleProxyServiceOfferingId = soConsoleProxy.getId();

        int secStorageVmRamSize = NumbersUtil.parseInt(_configDao.getValue(Config.SecStorageVmRamSize.key()),
                SecondaryStorageVmManager.DEFAULT_SS_VM_RAMSIZE);
        ServiceOffering21VO soSecondaryVm = new ServiceOffering21VO("Fake Offering For Secondary Storage VM", 1,
                secStorageVmRamSize, 0, 0, 0, false, null, useLocalStorage, true, null);
        soSecondaryVm.setId(seq++);
        soSecondaryVm.setUniqueName("Cloud.com-SecondaryStorage");
        soSecondaryVm = _serviceOffering21Dao.persist(soSecondaryVm);
        _secStorageServiceOfferingId = soSecondaryVm.getId();

        int routerRamSize = NumbersUtil.parseInt(_configDao.getValue("router.ram.size"), 128);
        ServiceOffering21VO soDomainRouter = new ServiceOffering21VO("Fake Offering For DomR", 1, routerRamSize, 0,
                0, 0, false, null, useLocalStorage, true, null);
        soDomainRouter.setId(seq++);
        soDomainRouter.setUniqueName("Cloud.Com-SoftwareRouter");
        soDomainRouter = _serviceOffering21Dao.persist(soDomainRouter);
        _domRServiceOfferingId = soDomainRouter.getId();

        System.out.println("Service offering has been migrated to 2.1 format");
    }

    private long getServiceOfferingStartSequence() {
        Transaction txn = Transaction.open(Transaction.CLOUD_DB);
        long seq = 0;
        try {
            PreparedStatement pstmt = null;
            pstmt = txn.prepareAutoCloseStatement("SELECT max(id) FROM service_offering");
            ResultSet rs = pstmt.executeQuery();
            rs.next();
            seq = rs.getLong(1);
            pstmt.close();

            pstmt = txn.prepareAutoCloseStatement("SELECT max(id) FROM disk_offering");
            rs = pstmt.executeQuery();
            rs.next();
            seq += rs.getLong(1);
            pstmt.close();

            seq += 100; // add a gap
            return seq;
        } catch (SQLException e) {
            s_logger.error("Unhandled exception: ", e);
        } finally {
            txn.close();
        }

        return 10000;
    }

    private void updateConsoleProxyServiceOfferingReferences(long serviceOfferingId) {
        Transaction txn = Transaction.open(Transaction.CLOUD_DB);
        try {
            PreparedStatement pstmt = null;
            pstmt = txn.prepareAutoCloseStatement(
                    "UPDATE volumes SET disk_offering_id=? WHERE instance_id IN (SELECT id FROM vm_instance WHERE type='ConsoleProxy')");
            pstmt.setLong(1, serviceOfferingId);

            int rows = pstmt.executeUpdate();
            s_logger.info("Update volumes for console proxy service offering change, affected rows: " + rows);
        } catch (SQLException e) {
            s_logger.error("Unhandled exception: ", e);
        } finally {
            txn.close();
        }
    }

    private void updateSecondaryStorageServiceOfferingReferences(long serviceOfferingId) {
        Transaction txn = Transaction.open(Transaction.CLOUD_DB);
        try {
            PreparedStatement pstmt = null;
            pstmt = txn.prepareAutoCloseStatement(
                    "UPDATE volumes SET disk_offering_id=? WHERE instance_id IN (SELECT id FROM vm_instance WHERE type='SecondaryStorageVm')");
            pstmt.setLong(1, serviceOfferingId);

            int rows = pstmt.executeUpdate();
            s_logger.info("Update volumes for secondary storage service offering change, affected rows: " + rows);
        } catch (SQLException e) {
            s_logger.error("Unhandled exception: ", e);
        } finally {
            txn.close();
        }
    }

    private void updateDomainRouterServiceOfferingReferences(long serviceOfferingId) {
        Transaction txn = Transaction.open(Transaction.CLOUD_DB);
        try {
            PreparedStatement pstmt = null;
            pstmt = txn.prepareAutoCloseStatement(
                    "UPDATE volumes SET disk_offering_id=? WHERE instance_id IN (SELECT id FROM vm_instance WHERE type='DomainRouter')");
            pstmt.setLong(1, serviceOfferingId);

            int rows = pstmt.executeUpdate();
            s_logger.info("Update volumes for secondary storage service offering change, affected rows: " + rows);
        } catch (SQLException e) {
            s_logger.error("Unhandled exception: ", e);
        } finally {
            txn.close();
        }
    }

    private void updateServiceOfferingReferences(long oldServiceOfferingId, long newServiceOfferingId) {
        Transaction txn = Transaction.open(Transaction.CLOUD_DB);
        try {
            PreparedStatement pstmt = null;
            pstmt = txn.prepareAutoCloseStatement(
                    "UPDATE user_vm SET service_offering_id=? WHERE service_offering_id=?");

            pstmt.setLong(1, newServiceOfferingId);
            pstmt.setLong(2, oldServiceOfferingId);

            int rows = pstmt.executeUpdate();
            s_logger.info("Update user_vm for service offering change (" + oldServiceOfferingId + "->"
                    + newServiceOfferingId + "), affected rows: " + rows);

        } catch (SQLException e) {
            s_logger.error("Unhandled exception: ", e);
        } finally {
            txn.close();
        }
    }

    private void migrateDiskOfferings() {
        System.out.println("Migrating disk offering...");

        List<DiskOffering20VO> oldDiskOfferings = _diskOffering20Dao.listAllIncludingRemoved();
        long maxDiskOfferingId = _domRServiceOfferingId;
        maxDiskOfferingId += 100;

        for (DiskOffering20VO do20 : oldDiskOfferings) {
            DiskOffering21VO do21 = new DiskOffering21VO(do20.getDomainId(), do20.getName(), do20.getDisplayText(),
                    do20.getDiskSize(), do20.getMirrored(), null);
            do21.setType(Type.Disk);
            do21.setId(maxDiskOfferingId++);

            do21 = _diskOffering21Dao.persist(do21);
            if (do20.getId().longValue() != do21.getId()) {
                updateDiskOfferingReferences(do20.getId().longValue(), do21.getId());
            }
        }

        FixupNullDiskOfferingInVolumes();

        System.out.println("Disk offering has been migrated to 2.1 format");
    }

    private void FixupNullDiskOfferingInVolumes() {
        System.out.println("Fixup NULL disk_offering_id references in volumes table ...");

        SearchCriteria<DiskOffering21VO> scDiskOffering = _diskOffering21Dao.createSearchCriteria();
        List<DiskOffering21VO> offeringList = _diskOffering21Dao.searchIncludingRemoved(scDiskOffering,
                new Filter(DiskOffering21VO.class, "diskSize", true, null, null), false, false);

        for (DiskOffering21VO offering : offeringList) {
            s_logger.info(
                    "Disk offering name: " + offering.getName() + ", disk size: " + offering.getDiskSizeInBytes());
        }

        SearchBuilder<VolumeVO> sb = _volumeDao.createSearchBuilder();
        sb.and("diskOfferingId", sb.entity().getDiskOfferingId(), Op.NULL);
        sb.done();

        SearchCriteria<VolumeVO> sc = sb.create();
        List<VolumeVO> volumes = _volumeDao.searchIncludingRemoved(sc, null, false, false);

        if (volumes.size() > 0) {
            for (VolumeVO vol : volumes) {
                if (vol.getInstanceId() != null) {
                    VMInstanceVO vmInstance = _vmInstanceDao.findById(vol.getInstanceId());

                    if (vmInstance.getType() == VirtualMachine.Type.User) {
                        // if the volume is for user VM, we can retrieve the information from service_offering_id
                        UserVmVO userVm = _userVmDao.findById(vol.getInstanceId());
                        if (userVm != null) {
                            // following operation requires that all service offerings should have been fixed up already
                            vol.setDiskOfferingId(userVm.getServiceOfferingId());
                        } else {
                            System.out.println("Data integrity could not be fixed up for volume: " + vol.getId()
                                    + " because its owner user vm no longer exists");
                        }
                    } else if (vmInstance.getType() == VirtualMachine.Type.ConsoleProxy) {
                        vol.setDiskOfferingId(this._consoleProxyServiceOfferingId);
                    } else if (vmInstance.getType() == VirtualMachine.Type.SecondaryStorageVm) {
                        vol.setDiskOfferingId(this._secStorageServiceOfferingId);
                    } else if (vmInstance.getType() == VirtualMachine.Type.DomainRouter) {
                        vol.setDiskOfferingId(this._domRServiceOfferingId);
                    }
                } else {
                    System.out.println("volume: " + vol.getId()
                            + " is standalone, fix disck_offering_id based on volume size");

                    // try to guess based on volume size and fill it in
                    boolean found = false;
                    for (DiskOffering21VO do21 : offeringList) {
                        if (do21.getType() == Type.Disk && vol.getSize() > do21.getDiskSizeInBytes()) {
                            found = true;
                            System.out.println(
                                    "volume: " + vol.getId() + " disck_offering_id is fixed to " + do21.getId());
                            vol.setDiskOfferingId(do21.getId());
                            break;
                        }
                    }

                    if (!found) {
                        System.out.println("volume: " + vol.getId() + " disck_offering_id is fixed to "
                                + offeringList.get(offeringList.size() - 1).getId());
                        vol.setDiskOfferingId(offeringList.get(offeringList.size() - 1).getId());
                    }
                }

                _volumeDao.update(vol.getId(), vol);
            }
        }

        System.out.println("Disk offering fixup is done");
    }

    private void updateDiskOfferingReferences(long oldDiskOfferingId, long newDiskOfferingId) {
        Transaction txn = Transaction.open(Transaction.CLOUD_DB);
        try {
            PreparedStatement pstmt = null;
            pstmt = txn.prepareAutoCloseStatement("UPDATE vm_disk SET disk_offering_id=? WHERE disk_offering_id=?");

            pstmt.setLong(1, newDiskOfferingId);
            pstmt.setLong(2, oldDiskOfferingId);

            int rows = pstmt.executeUpdate();
            pstmt.close();

            s_logger.info("Update vm_disk for disk offering change (" + oldDiskOfferingId + "->" + newDiskOfferingId
                    + "), affected rows: " + rows);

            pstmt = txn.prepareAutoCloseStatement("UPDATE volumes SET disk_offering_id=? WHERE disk_offering_id=?");
            pstmt.setLong(1, newDiskOfferingId);
            pstmt.setLong(2, oldDiskOfferingId);
            rows = pstmt.executeUpdate();
            pstmt.close();
            s_logger.info("Update volumes for disk offering change (" + oldDiskOfferingId + "->" + newDiskOfferingId
                    + "), affected rows: " + rows);
        } catch (SQLException e) {
            s_logger.error("Unhandled exception: ", e);
        } finally {
            txn.close();
        }
    }

    private void migrateSystemVmGuestMacAndState(long zoneId) {
        // for console proxy VMs
        SearchBuilder<ConsoleProxyVO> sb = _consoleProxyDao.createSearchBuilder();
        sb.and("zoneId", sb.entity().getDataCenterIdToDeployIn(), Op.EQ);
        sb.done();

        SearchCriteria<ConsoleProxyVO> sc = sb.create();
        sc.setParameters("zoneId", zoneId);

        List<ConsoleProxyVO> proxies = _consoleProxyDao.searchIncludingRemoved(sc, null, false, false);
        for (ConsoleProxyVO proxy : proxies) {
            String[] macAddresses = _dcDao.getNextAvailableMacAddressPair(zoneId, (1L << 31));
            String guestMacAddress = macAddresses[0];

            if (proxy.getState() == State.Running || proxy.getState() == State.Starting) {
                System.out.println("System VM " + proxy.getHostName()
                        + " is in active state, mark it to Stopping state for migration");
                proxy.setState(State.Stopping);
            }

            String guestIpAddress = _dcDao.allocateLinkLocalIpAddress(proxy.getDataCenterIdToDeployIn(),
                    proxy.getPodIdToDeployIn(), proxy.getId(), null);

            System.out.println("Assign link loal address to proxy " + proxy.getHostName() + ", link local address: "
                    + guestIpAddress);
            _consoleProxyDao.update(proxy.getId(), proxy);
        }

        // for secondary storage VMs
        SearchBuilder<SecondaryStorageVmVO> sb2 = _secStorageVmDao.createSearchBuilder();
        sb2.and("zoneId", sb2.entity().getDataCenterIdToDeployIn(), Op.EQ);
        sb2.done();

        SearchCriteria<SecondaryStorageVmVO> sc2 = sb2.create();
        sc2.setParameters("zoneId", zoneId);

        List<SecondaryStorageVmVO> secStorageVms = _secStorageVmDao.searchIncludingRemoved(sc2, null, false, false);
        for (SecondaryStorageVmVO secStorageVm : secStorageVms) {
            String[] macAddresses = _dcDao.getNextAvailableMacAddressPair(zoneId, (1L << 31));
            String guestMacAddress = macAddresses[0];

            if (secStorageVm.getState() == State.Running || secStorageVm.getState() == State.Starting) {
                System.out.println("System VM " + secStorageVm.getHostName()
                        + " is in active state, mark it to Stopping state for migration");
                secStorageVm.setState(State.Stopping);
            }

            String guestIpAddress = _dcDao.allocateLinkLocalIpAddress(secStorageVm.getDataCenterIdToDeployIn(),
                    secStorageVm.getPodIdToDeployIn(), secStorageVm.getId(), null);

            System.out.println("Assign link loal address to secondary storage VM " + secStorageVm.getHostName()
                    + ", link local address: " + guestIpAddress);
            _secStorageVmDao.update(secStorageVm.getId(), secStorageVm);
        }

        // for Domain Router VMs
        // Although we can list those we are interested, but just too lazy, list all of them and check their states. 
        SearchBuilder<DomainRouterVO> sb3 = _routerDao.createSearchBuilder();
        sb3.and("zoneId", sb3.entity().getDataCenterIdToDeployIn(), Op.EQ);
        sb3.done();

        SearchCriteria<DomainRouterVO> sc3 = sb3.create();
        sc3.setParameters("zoneId", zoneId);
        List<DomainRouterVO> domRs = _routerDao.searchIncludingRemoved(sc3, null, false, false);
        for (DomainRouterVO router : domRs) {
            if (router.getState() == State.Running || router.getState() == State.Starting) {
                router.setState(State.Stopping);

                System.out.println("System VM " + router.getHostName()
                        + " is in active state, mark it to Stopping state for migration");
                _routerDao.update(router.getId(), router);
            }
        }
    }

    private void migrateVmInstanceLastHostId(long zoneId, long podId) {
        SearchBuilder<VMInstanceVO> sb = _vmInstanceDao.createSearchBuilder();
        sb.and("zoneId", sb.entity().getDataCenterIdToDeployIn(), Op.EQ);
        sb.and("podId", sb.entity().getPodIdToDeployIn(), Op.EQ);
        sb.done();

        Random rand = new Random();
        SearchCriteria<VMInstanceVO> sc = sb.create();
        sc.setParameters("zoneId", zoneId);
        sc.setParameters("podId", podId);
        List<VMInstanceVO> vmInstances = _vmInstanceDao.searchIncludingRemoved(sc, null, false, false);
        List<HostVO> podHosts = getHostsInPod(zoneId, podId);
        for (VMInstanceVO vm : vmInstances) {
            if (vm.getHostId() != null) {
                vm.setLastHostId(vm.getHostId());
            } else {
                if (podHosts.size() > 0) {
                    int next = rand.nextInt(podHosts.size());
                    vm.setLastHostId(podHosts.get(next).getId());
                }
            }
            _vmInstanceDao.update(vm.getId(), vm);
        }
    }

    private List<HostVO> getHostsInPod(long zoneId, long podId) {
        SearchBuilder<HostVO> sb = _hostDao.createSearchBuilder();
        sb.and("zoneId", sb.entity().getDataCenterId(), Op.EQ);
        sb.and("podId", sb.entity().getPodId(), Op.EQ);
        sb.and("type", sb.entity().getType(), Op.EQ);
        sb.done();

        SearchCriteria<HostVO> sc = sb.create();
        sc.setParameters("zoneId", zoneId);
        sc.setParameters("podId", podId);
        sc.setParameters("type", Host.Type.Routing.toString());

        return _hostDao.searchIncludingRemoved(sc, null, false, false);
    }

    private void migrateVolumDeviceIds() {
        System.out.println("Migrating device_id for volumes, this may take a while, please wait...");
        SearchCriteria<VMInstanceVO> sc = _vmInstanceDao.createSearchCriteria();
        List<VMInstanceVO> vmInstances = _vmInstanceDao.searchIncludingRemoved(sc, null, false, false);

        long deviceId = 1;
        for (VMInstanceVO vm : vmInstances) {
            SearchBuilder<VolumeVO> sb = _volumeDao.createSearchBuilder();
            sb.and("instanceId", sb.entity().getInstanceId(), Op.EQ);
            sb.done();

            SearchCriteria<VolumeVO> sc2 = sb.create();
            sc2.setParameters("instanceId", vm.getId());

            List<VolumeVO> volumes = _volumeDao.searchIncludingRemoved(sc2, null, false, false);
            deviceId = 1; // reset for each VM iteration
            for (VolumeVO vol : volumes) {
                if (vol.getVolumeType() == Volume.Type.ROOT) {
                    System.out.println("Setting root volume device id to zero, vol: " + vol.getName()
                            + ", instance: " + vm.getHostName());

                    vol.setDeviceId(0L);
                } else if (vol.getVolumeType() == Volume.Type.DATADISK) {
                    System.out.println("Setting data volume device id, vol: " + vol.getName() + ", instance: "
                            + vm.getHostName() + ", device id: " + deviceId);

                    vol.setDeviceId(deviceId);

                    // don't use device ID 3
                    if (++deviceId == 3) {
                        deviceId++;
                    }
                } else {
                    System.out.println("Unsupported volume type found for volume: " + vol.getName());
                }

                _volumeDao.update(vol.getId(), vol);
            }
        }

        System.out.println("Migrating device_id for volumes done");
    }

    private void migrateVolumePoolType() {
        System.out.println("Migrating pool type for volumes...");

        SearchCriteria<VolumeVO> sc = _volumeDao.createSearchCriteria();
        List<VolumeVO> volumes = _volumeDao.searchIncludingRemoved(sc, null, false, false);
        for (VolumeVO vol : volumes) {
            if (vol.getPoolId() != null) {
                StoragePoolVO pool = _poolDao.findById(vol.getPoolId());
                if (pool != null) {
                    vol.setPoolType(pool.getPoolType());

                    _volumeDao.update(vol.getId(), vol);
                } else {
                    System.out.println("Unable to determine pool type for volume: " + vol.getName());
                }
            }
        }

        System.out.println("Migrating pool type for volumes done");
    }

    private void migrateConfiguration() {
        System.out.println("Migrating 2.1 configuration variables...");

        System.out.print("Are you migrating from 2.0 Premium Edition? (yes/no): ");
        String answer = readInput();
        if (answer != null && answer.equalsIgnoreCase("yes")) {
            _isPremium = true;
        }

        // Save default Configuration Table values
        List<String> categories = Config.getCategories();
        for (String category : categories) {
            // If this is not a premium environment, don't insert premium configuration values
            if (!_isPremium && category.equals("Premium")) {
                continue;
            }

            List<Config> configs = Config.getConfigs(category);
            for (Config c : configs) {
                String name = c.key();

                // If the value is already in the table, don't reinsert it
                if (_configDao.getValue(name) != null) {
                    continue;
                }

                String instance = "DEFAULT";
                String component = c.getComponent();
                String value = c.getDefaultValue();
                String description = c.getDescription();
                ConfigurationVO configVO = new ConfigurationVO(category, instance, component, name, value,
                        description);
                _configDao.persist(configVO);
            }

            // If this is a premium environment, set the network type to be "vlan"
            if (_isPremium) {
                _configDao.update("network.type", "vlan");
                _configDao.update("hypervisor.type", "xenserver");
                _configDao.update("secondary.storage.vm", "true");
                _configDao.update("secstorage.encrypt.copy", "true");
                _configDao.update("secstorage.secure.copy.cert", "realhostip");
            }

            boolean externalIpAlloator = Boolean
                    .parseBoolean(_configDao.getValue("direct.attach.network.externalIpAllocator.enabled"));
            String hyperVisor = _configDao.getValue("hypervisor.type");
            if (hyperVisor.equalsIgnoreCase("KVM") && !externalIpAlloator) {
                /*For KVM, it's enabled by default*/
                _configDao.update("direct.attach.network.externalIpAllocator.enabled", "true");
            }

            // Save the mount parent to the configuration table
            String mountParent = getMountParent();
            if (mountParent != null) {
                _configDao.update("mount.parent", mountParent);
            }

            if (_configDao.getValue("host") == null) {
                String hostIpAdr = getHost();
                if (hostIpAdr != null) {
                    _configDao.update("host", hostIpAdr);
                }
            }

            // generate a single sign-on key
            updateSSOKey();
        }

        System.out.println("Migrating 2.1 configuration done");
    }

    private String getEthDevice() {
        String defaultRoute = Script.runSimpleBashScript("/sbin/route | grep default");

        if (defaultRoute == null) {
            return null;
        }

        String[] defaultRouteList = defaultRoute.split("\\s+");

        if (defaultRouteList.length != 8) {
            return null;
        }

        return defaultRouteList[7];
    }

    protected String getHost() {
        NetworkInterface nic = null;
        String pubNic = getEthDevice();

        if (pubNic == null) {
            return null;
        }

        try {
            nic = NetworkInterface.getByName(pubNic);
        } catch (final SocketException e) {
            return null;
        }

        String[] info = NetUtils.getNetworkParams(nic);
        return info[0];
    }

    private String getMountParent() {
        return getEnvironmentProperty("mount.parent");
    }

    private String getEnvironmentProperty(String name) {
        try {
            final File propsFile = PropertiesUtil.findConfigFile("environment.properties");

            if (propsFile == null) {
                return null;
            } else {
                final FileInputStream finputstream = new FileInputStream(propsFile);
                final Properties props = new Properties();
                props.load(finputstream);
                finputstream.close();
                return props.getProperty("mount.parent");
            }
        } catch (IOException e) {
            return null;
        }
    }

    private void updateSSOKey() {
        try {
            String encodedKey = null;

            // Algorithm for SSO Keys is SHA1, should this be configuable?
            KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
            SecretKey key = generator.generateKey();
            encodedKey = Base64.encodeBase64URLSafeString(key.getEncoded());

            _configDao.update("security.singlesignon.key", encodedKey);
        } catch (NoSuchAlgorithmException ex) {
            s_logger.error("error generating sso key", ex);
        }
    }

    private void doMigration() {
        setupComponents();

        migrateZones();
        migrateDomains();
        migrateServiceOfferings();
        migrateDiskOfferings();
        migrateVolumDeviceIds();
        migrateVolumePoolType();
        migrateConfiguration();

        // update disk_offering_id for system VMs. As of the id-space collision for servercie_offering_ids
        // before and after migration, this should be done in the last step
        updateConsoleProxyServiceOfferingReferences(_consoleProxyServiceOfferingId);
        updateSecondaryStorageServiceOfferingReferences(_secStorageServiceOfferingId);
        updateDomainRouterServiceOfferingReferences(_domRServiceOfferingId);

        System.out.println("Migration done");
    }

    private String readInput() {
        try {
            Scanner in = new Scanner(System.in);
            String input = in.nextLine();
            return input;
        } catch (NoSuchElementException e) {
            return "";
        }
    }

    private void setupComponents() {
        ComponentLocator.getLocator("migration", "migration-components.xml", "log4j-cloud.xml");
        ComponentLocator locator = ComponentLocator.getCurrentLocator();

        _configDao = locator.getDao(ConfigurationDao.class);
        _podDao = locator.getDao(HostPodDao.class);
        _dcDao = locator.getDao(DataCenterDao.class);
        _clusterDao = locator.getDao(ClusterDao.class);
        _hostDao = locator.getDao(HostDao.class);
        _spDao = locator.getDao(StoragePoolDao.class);
        _domainDao = locator.getDao(DomainDao.class);
        _serviceOffering20Dao = locator.getDao(ServiceOffering20Dao.class);
        _diskOffering20Dao = locator.getDao(DiskOffering20Dao.class);
        _serviceOffering21Dao = locator.getDao(ServiceOffering21Dao.class);
        _diskOffering21Dao = locator.getDao(DiskOffering21Dao.class);
        _consoleProxyDao = locator.getDao(ConsoleProxyDao.class);
        _secStorageVmDao = locator.getDao(SecondaryStorageVmDao.class);
        _vmInstanceDao = locator.getDao(VMInstanceDao.class);
        _volumeDao = locator.getDao(VolumeDao.class);
        _userVmDao = locator.getDao(UserVmDao.class);
        _routerDao = locator.getDao(DomainRouterDao.class);
        _poolDao = locator.getDao(StoragePoolDao.class);
    }

    public static void main(String[] args) {
        File file = PropertiesUtil.findConfigFile("log4j-cloud.xml");

        if (file != null) {
            System.out.println("Log4j configuration from : " + file.getAbsolutePath());
            DOMConfigurator.configureAndWatch(file.getAbsolutePath(), 10000);
        } else {
            System.out.println("Configure log4j with default properties");
        }

        new Db20to21MigrationUtil().doMigration();
        System.exit(0);
    }
}