// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.cloudstack.network.contrail.management;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import javax.inject.Inject;

import net.juniper.contrail.api.ApiConnector;
import net.juniper.contrail.api.ApiObjectBase;
import net.juniper.contrail.api.ApiPropertyBase;
import net.juniper.contrail.api.ObjectReference;
import net.juniper.contrail.api.types.FloatingIp;
import net.juniper.contrail.api.types.FloatingIpPool;
import net.juniper.contrail.api.types.NetworkPolicy;
import net.juniper.contrail.api.types.InstanceIp;
import net.juniper.contrail.api.types.ServiceInstance;
import net.juniper.contrail.api.types.VirtualMachine;
import net.juniper.contrail.api.types.VirtualMachineInterface;
import net.juniper.contrail.api.types.VirtualNetwork;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import org.apache.cloudstack.network.contrail.model.FloatingIpModel;
import org.apache.cloudstack.network.contrail.model.FloatingIpPoolModel;
import org.apache.cloudstack.network.contrail.model.NetworkPolicyModel;
import org.apache.cloudstack.network.contrail.model.ServiceInstanceModel;
import org.apache.cloudstack.network.contrail.model.VMInterfaceModel;
import org.apache.cloudstack.network.contrail.model.VirtualMachineModel;
import org.apache.cloudstack.network.contrail.model.VirtualNetworkModel;

import com.cloud.dc.dao.VlanDao;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InternalErrorException;
import com.cloud.network.IpAddress;
import com.cloud.network.Networks.TrafficType;
import com.cloud.network.addr.PublicIp;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
import com.cloud.network.vpc.NetworkACLItemDao;
import com.cloud.network.vpc.NetworkACLItemVO;
import com.cloud.network.vpc.NetworkACLVO;
import com.cloud.network.vpc.dao.NetworkACLDao;
import com.cloud.network.vpc.VpcVO;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.projects.ProjectVO;
import com.cloud.projects.dao.ProjectDao;
import com.cloud.vm.NicVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.VMInstanceDao;

@Component
public class ServerDBSyncImpl implements ServerDBSync {

    @Inject
    DomainDao _domainDao;
    @Inject
    ProjectDao _projectDao;
    @Inject
    NetworkDao _networksDao;
    @Inject
    VMInstanceDao _vmInstanceDao;
    @Inject
    NicDao _nicDao;
    @Inject
    VlanDao _vlanDao;
    @Inject
    PhysicalNetworkDao _physicalNetworkDao;
    @Inject
    PhysicalNetworkServiceProviderDao _physProviderDao;
    @Inject
    ContrailManager _manager;
    @Inject
    VpcDao _vpcDao;
    @Inject
    NetworkACLItemDao _networkACLItemDao;
    @Inject
    NetworkACLDao _networkACLDao;

    DBSyncGeneric _dbSync;
    Class<?>[] _vncClasses;
    // Read-Write (true) or Read-Only mode.
    boolean _rwMode;
    private final ReentrantLock _lockSyncMode = new ReentrantLock();

    ServerDBSyncImpl() {
        _vncClasses =
            new Class[] {net.juniper.contrail.api.types.Domain.class, net.juniper.contrail.api.types.Project.class, NetworkPolicy.class, VirtualNetwork.class,
                VirtualMachine.class, ServiceInstance.class, FloatingIp.class};
        _dbSync = new DBSyncGeneric(this);
    }

    private static final Logger s_logger = Logger.getLogger(ServerDBSync.class);

    /*
     * API for syncing all classes of vnc objects with cloudstack
     *
     * Sync cloudstack and vnc objects.
     * Order has to be maintained
     */
    @Override
    public short syncAll(short syncMode) {
        short syncState = SYNC_STATE_IN_SYNC;

        /* vnc classes need to be synchronized with cloudstack */
        s_logger.debug("syncing cloudstack db with vnc");
        try {
            for (Class<?> cls : _vncClasses) {

                /* lock the sync mode*/
                _lockSyncMode.lock();
                _rwMode = syncMode == DBSyncGeneric.SYNC_MODE_UPDATE;
                _dbSync.setSyncMode(syncMode);

                if (_dbSync.getSyncMode() == DBSyncGeneric.SYNC_MODE_CHECK) {
                    s_logger.debug("sync check start: " + DBSyncGeneric.getClassName(cls));
                } else {
                    s_logger.debug("sync start: " + DBSyncGeneric.getClassName(cls));
                }

                if (_dbSync.sync(cls) == false) {
                    if (_dbSync.getSyncMode() == DBSyncGeneric.SYNC_MODE_CHECK) {
                        s_logger.info("out of sync detected: " + DBSyncGeneric.getClassName(cls));
                    } else {
                        s_logger.info("out of sync detected and re-synced: " + DBSyncGeneric.getClassName(cls));
                    }
                    syncState = SYNC_STATE_OUT_OF_SYNC;
                }
                if (_dbSync.getSyncMode() == DBSyncGeneric.SYNC_MODE_CHECK) {
                    s_logger.debug("sync check finish: " + DBSyncGeneric.getClassName(cls));
                } else {
                    s_logger.debug("sync finish: " + DBSyncGeneric.getClassName(cls));
                }
                /* unlock the sync mode */
                _lockSyncMode.unlock();
            }
        } catch (Exception ex) {
            s_logger.warn("DB Synchronization", ex);
            syncState = SYNC_STATE_UNKNOWN;
            if (_lockSyncMode.isLocked()) {
                _lockSyncMode.unlock();
            }
        }

        return syncState;
    }

    @Override
    public void syncClass(Class<?> cls) {

        s_logger.debug("syncClass: " + cls.getName());
        try {
            s_logger.debug("sync start: " + DBSyncGeneric.getClassName(cls));
            _lockSyncMode.lock();
            _dbSync.setSyncMode(DBSyncGeneric.SYNC_MODE_UPDATE);
            _dbSync.sync(cls);
            _lockSyncMode.unlock();
            s_logger.debug("sync finish: " + DBSyncGeneric.getClassName(cls));
        } catch (Exception ex) {
            s_logger.warn("Sync error: " + cls.getName(), ex);
            if (_lockSyncMode.isLocked()) {
                _lockSyncMode.unlock();
            }
        }
    }

    public <T extends ApiPropertyBase> void deleteChildren(List<ObjectReference<T>> childs, Class<?> childCls, StringBuffer syncLogMesg) throws Exception {
        final ApiConnector api = _manager.getApiConnector();
        if (childs == null) {
            syncLogMesg.append("no children of type: " + childCls.getName() + "\n");
            return;
        }

        syncLogMesg.append("delete children of type : " + DBSyncGeneric.getClassName(childCls) + "\n");
        String deleteChildMethod = "delete" + DBSyncGeneric.getClassName(childCls);
        Method method = null;
        Method methods[] = this.getClass().getMethods();
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName().equalsIgnoreCase(deleteChildMethod)) {
                method = methods[i];
                break;
            }
        }
        int count = 0;
        for (ObjectReference<T> childRef : childs) {
            @SuppressWarnings("unchecked")
            ApiObjectBase child = api.findById((Class<? extends ApiObjectBase>)childCls, childRef.getUuid());
            if (method != null) {
                method.invoke(this, child, syncLogMesg);
            } else {
                deleteDefault(child, childCls, syncLogMesg);
            }
            count++;
        }
        syncLogMesg.append("deleted children count : " + count + "\n");
    }

    public void deleteDefault(ApiObjectBase vnc, Class<?> cls, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        api.delete(vnc);
        syncLogMesg.append(cls.getCanonicalName() + "# VNC: " + vnc.getName() + " deleted\n");
    }

    /*
     *  Domain Synchronization methods
     */
    public boolean syncDomain() throws Exception {
        final ApiConnector api = _manager.getApiConnector();
        try {
            List<?> dbList = _domainDao.listAll();
            @SuppressWarnings("unchecked")
            List<?> vncList = api.list(net.juniper.contrail.api.types.Domain.class, null);
            return _dbSync.syncGeneric(net.juniper.contrail.api.types.Domain.class, dbList, vncList);
        } catch (Exception ex) {
            s_logger.warn("syncDomain", ex);
            throw ex;
        }
    }

    @Override
    public void createDomain(DomainVO db, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        net.juniper.contrail.api.types.Domain vnc = new net.juniper.contrail.api.types.Domain();
        vnc.setName(db.getName());
        vnc.setUuid(db.getUuid());
        if (!api.create(vnc)) {
            s_logger.error("Unable to create domain " + vnc.getName());
            syncLogMesg.append("Error: Virtual domain# VNC : Unable to create domain: " + vnc.getName() + "\n");
            return;
        }
        syncLogMesg.append("Domain# VNC: " + vnc.getName() + " created \n");
    }

    public void deleteDomain(net.juniper.contrail.api.types.Domain vnc, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        api.read(vnc);
        syncLogMesg.append("Domain# DB: none; VNC: " + vnc.getName() + "(" + vnc.getUuid() + "); action: delete\n");

        /* delete all projects under this domain */
        try {
            deleteChildren(vnc.getProjects(), net.juniper.contrail.api.types.Project.class, syncLogMesg);
        } catch (Exception ex) {
            s_logger.warn("deleteDomain", ex);
        }

        api.delete(vnc);
        syncLogMesg.append("Domain# VNC: " + vnc.getName() + " deleted\n");
    }

    public Integer compareDomain(DomainVO db, net.juniper.contrail.api.types.Domain vnc, StringBuffer syncLogMesg) {
        if (_manager.isSystemRootDomain(db) && _manager.isSystemRootDomain(vnc)) {
            return _manager.getDomainCanonicalName(db).compareTo(vnc.getName());
        } else if (_manager.isSystemRootDomain(db)) {
            return -1;
        } else if (_manager.isSystemRootDomain(vnc)) {
            return 1;
        }
        return db.getUuid().compareTo(vnc.getUuid());
    }

    public Boolean filterDomain(net.juniper.contrail.api.types.Domain vnc, StringBuffer syncLogMesg) {
        if (_manager.isSystemRootDomain(vnc)) {
            return true;
        }
        return false;
    }

    public Boolean equalDomain(DomainVO db, net.juniper.contrail.api.types.Domain vnc, StringBuffer syncLogMesg) {
        syncLogMesg.append("Domain# DB: " + db.getName() + "; VNC: " + vnc.getName() + "; action: equal, no action\n");
        return true;
    }

    public Comparator<?> dbComparatorDomain() {
        Comparator<?> comparator = new Comparator<DomainVO>() {
            @Override
            public int compare(DomainVO u1, DomainVO u2) {
                if (_manager.isSystemRootDomain(u1)) {
                    return -1;
                }
                if (_manager.isSystemRootDomain(u2)) {
                    return 1;
                }
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Comparator<?> vncComparatorDomain() {
        Comparator<?> comparator = new Comparator<net.juniper.contrail.api.types.Domain>() {
            @Override
            public int compare(net.juniper.contrail.api.types.Domain u1, net.juniper.contrail.api.types.Domain u2) {
                if (_manager.isSystemRootDomain(u1)) {
                    return -1;
                }
                if (_manager.isSystemRootDomain(u2)) {
                    return 1;
                }
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    /*
     *  Project Synchronization methods
     */
    @SuppressWarnings("unchecked")
    public boolean syncProject() throws Exception {
        final ApiConnector api = _manager.getApiConnector();
        try {
            List<?> dbList = _projectDao.listAll();
            List<?> vncList = api.list(net.juniper.contrail.api.types.Project.class, null);
            return _dbSync.syncGeneric(net.juniper.contrail.api.types.Project.class, dbList, vncList);
        } catch (Exception ex) {
            s_logger.warn("syncProject", ex);
            throw ex;
        }
    }

    @Override
    public void createProject(ProjectVO db, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        net.juniper.contrail.api.types.Project vnc = new net.juniper.contrail.api.types.Project();
        vnc.setName(db.getName());
        vnc.setUuid(db.getUuid());
        if (!api.create(vnc)) {
            s_logger.error("Unable to create project: " + vnc.getName());
            syncLogMesg.append("Error: Virtual project# VNC : Unable to create project: " + vnc.getName() + "\n");
            return;
        }
        syncLogMesg.append("Project# VNC: " + vnc.getName() + " created \n");
    }

    public void deleteProject(net.juniper.contrail.api.types.Project vnc, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        api.read(vnc);
        syncLogMesg.append("Project# DB: none; VNC: " + vnc.getName() + "(" + vnc.getUuid() + "); action: delete\n");

        try {
            deleteChildren(vnc.getVirtualNetworks(), VirtualNetwork.class, syncLogMesg);
            deleteChildren(vnc.getSecurityGroups(), net.juniper.contrail.api.types.SecurityGroup.class, syncLogMesg);
            deleteChildren(vnc.getNetworkIpams(), net.juniper.contrail.api.types.NetworkIpam.class, syncLogMesg);
            deleteChildren(vnc.getNetworkPolicys(), net.juniper.contrail.api.types.NetworkPolicy.class, syncLogMesg);
        } catch (Exception ex) {
            s_logger.warn("deleteProject", ex);
        }

        api.delete(vnc);
        syncLogMesg.append("Project# VNC: " + vnc.getName() + " deleted\n");
    }

    public Integer compareProject(ProjectVO db, net.juniper.contrail.api.types.Project vnc, StringBuffer syncLogMesg) {
        if (_manager.isSystemDefaultProject(db) && _manager.isSystemDefaultProject(vnc)) {
            return _manager.getProjectCanonicalName(db).compareTo(vnc.getName());
        } else if (_manager.isSystemDefaultProject(db)) {
            return -1;
        } else if (_manager.isSystemDefaultProject(vnc)) {
            return 1;
        }
        return db.getUuid().compareTo(vnc.getUuid());
    }

    public Boolean filterProject(net.juniper.contrail.api.types.Project vnc, StringBuffer syncLogMesg) {
        if (_manager.isSystemDefaultProject(vnc)) {
            syncLogMesg.append("VNC: " + vnc.getName() + " filtered; action: don't delete\n");
            return true;
        }
        return false;
    }

    public Boolean equalProject(ProjectVO db, net.juniper.contrail.api.types.Project vnc, StringBuffer syncLogMesg) {
        syncLogMesg.append("Project# DB: " + db.getName() + "; VNC: " + vnc.getName() + "; action: equal, no action\n");
        return true;
    }

    public Comparator<?> dbComparatorProject() {
        Comparator<?> comparator = new Comparator<ProjectVO>() {
            @Override
            public int compare(ProjectVO u1, ProjectVO u2) {
                if (_manager.isSystemDefaultProject(u1)) {
                    return -1;
                }
                if (_manager.isSystemDefaultProject(u2)) {
                    return 1;
                }
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Comparator<?> vncComparatorProject() {
        Comparator<?> comparator = new Comparator<net.juniper.contrail.api.types.Project>() {
            @Override
            public int compare(net.juniper.contrail.api.types.Project u1, net.juniper.contrail.api.types.Project u2) {
                if (_manager.isSystemDefaultProject(u1)) {
                    return -1;
                }
                if (_manager.isSystemDefaultProject(u2)) {
                    return 1;
                }
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    /*
     * Security Groups
     */

    public void deleteSecurityGroup(net.juniper.contrail.api.types.SecurityGroup vnc, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        api.delete(vnc);
        syncLogMesg.append("SecurityGroup# VNC: " + vnc.getName() + " deleted\n");
    }

    /*
     *  Virtual Network Synchronization methods
     */
    @SuppressWarnings({"unchecked"})
    public boolean syncVirtualNetwork() throws Exception {
        final ApiConnector api = _manager.getApiConnector();
        try {

            List<TrafficType> types = new ArrayList<TrafficType>();
            types.add(TrafficType.Public);
            types.add(TrafficType.Guest);
            List<NetworkVO> dbNets = _manager.findManagedNetworks(types);

            List<VirtualNetwork> vList = (List<VirtualNetwork>)api.list(VirtualNetwork.class, null);
            List<VirtualNetwork> vncList = new ArrayList<VirtualNetwork>();
            for (VirtualNetwork vn : vList) {
                if (!_manager.isSystemDefaultNetwork(vn)) {
                    vncList.add(vn);
                }
            }
            s_logger.debug("sync VN - DB size: " + dbNets.size() + " VNC Size: " + vncList.size());
            return _dbSync.syncGeneric(VirtualNetwork.class, dbNets, vncList);
        } catch (Exception ex) {
            s_logger.warn("sync virtual-networks", ex);
            throw ex;
        }
    }

    public Comparator<NetworkVO> dbComparatorVirtualNetwork() {
        Comparator<NetworkVO> comparator = new Comparator<NetworkVO>() {
            @Override
            public int compare(NetworkVO u1, NetworkVO u2) {
                if (_manager.isSystemDefaultNetwork(u1) && _manager.isSystemDefaultNetwork(u2)) {
                    return _manager.getCanonicalName(u1).compareTo(_manager.getCanonicalName(u2));
                } else if (_manager.isSystemDefaultNetwork(u1)) {
                    return -1;
                } else if (_manager.isSystemDefaultNetwork(u2)) {
                    return 1;
                }
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Comparator<?> vncComparatorVirtualNetwork() {
        Comparator<?> comparator = new Comparator<VirtualNetwork>() {
            @Override
            public int compare(VirtualNetwork u1, VirtualNetwork u2) {
                if (_manager.isSystemDefaultNetwork(u1) && _manager.isSystemDefaultNetwork(u2)) {
                    return u1.getName().compareTo(u2.getName());
                } else if (_manager.isSystemDefaultNetwork(u1)) {
                    return -1;
                } else if (_manager.isSystemDefaultNetwork(u2)) {
                    return 1;
                }
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public void createVirtualNetwork(NetworkVO dbNet, StringBuffer syncLogMesg) throws IOException {
        syncLogMesg.append("VN# DB: " + _manager.getCanonicalName(dbNet) + "(" + dbNet.getUuid() + "); VNC: none;  action: create\n");

        if (_manager.getDatabase().lookupVirtualNetwork(dbNet.getUuid(), _manager.getCanonicalName(dbNet), dbNet.getTrafficType()) != null) {
            s_logger.warn("VN model object is already present in DB: " + dbNet.getUuid() + ", name: " + dbNet.getName());
        }

        VirtualNetworkModel vnModel = new VirtualNetworkModel(dbNet, dbNet.getUuid(), _manager.getCanonicalName(dbNet), dbNet.getTrafficType());
        if (dbNet.getTrafficType() == TrafficType.Guest && dbNet.getNetworkACLId() != null) {
            NetworkACLVO acl = _networkACLDao.findById(dbNet.getNetworkACLId());
            NetworkPolicyModel policyModel = _manager.getDatabase().lookupNetworkPolicy(acl.getUuid());
            if (policyModel == null) {
                s_logger.error("Network(" + dbNet.getName() + ") has ACL but policy model not created: " +
                                       acl.getUuid() + ", name: " + acl.getName());
            } else {
                vnModel.addToNetworkPolicy(policyModel);
            }
        }
        vnModel.build(_manager.getModelController(), dbNet);

        if (_rwMode) {
            try {
                if (!vnModel.verify(_manager.getModelController())) {
                    vnModel.update(_manager.getModelController());
                }
            } catch (InternalErrorException ex) {
                s_logger.warn("create virtual-network", ex);
                syncLogMesg.append("Error: VN# VNC : Unable to create network " + dbNet.getName() + "\n");
                return;
            }
            s_logger.debug("add model " + vnModel.getName());
            _manager.getDatabase().getVirtualNetworks().add(vnModel);
            syncLogMesg.append("VN# VNC: " + dbNet.getUuid() + ", " + vnModel.getName() + " created\n");
        } else {
            syncLogMesg.append("VN# VNC: " + vnModel.getName() + " created \n");
        }
    }

    public void deleteVirtualNetwork(VirtualNetwork vnet, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        if (_manager.isSystemDefaultNetwork(vnet)) {
            syncLogMesg.append("VN# System default virtual Network# VNC: " + vnet.getName() + " can not be deleted\n");
            return;
        }
        api.read(vnet);

        deleteInstanceIps(vnet.getInstanceIpBackRefs(), syncLogMesg);

        List<ObjectReference<ApiPropertyBase>> fipPools = vnet.getFloatingIpPools();
        if (fipPools != null && !fipPools.isEmpty()) {
            FloatingIpPool floatingIpPool = (FloatingIpPool)api.findById(FloatingIpPool.class, fipPools.get(0).getUuid());
            if (floatingIpPool != null) {
                deleteFloatingIps(floatingIpPool.getFloatingIps(), syncLogMesg);
            }
        }

        deleteVirtualMachineInterfaces(vnet.getVirtualMachineInterfaceBackRefs(), syncLogMesg);

        syncLogMesg.append("VN# DB: none; VNC: " + vnet.getName() + "(" + vnet.getUuid() + "); action: delete\n");
        api.delete(vnet);
        syncLogMesg.append("VN# VNC: " + vnet.getName() + " deleted\n");
    }

    public Integer compareVirtualNetwork(NetworkVO dbn, VirtualNetwork vnet, StringBuffer syncLogMesg) {
        if (_manager.isSystemDefaultNetwork(dbn) && _manager.isSystemDefaultNetwork(vnet)) {
            return _manager.getCanonicalName(dbn).compareTo(vnet.getName());
        } else if (_manager.isSystemDefaultNetwork(dbn)) {
            return -1;
        } else if (_manager.isSystemDefaultNetwork(vnet)) {
            return 1;
        }
        return dbn.getUuid().compareTo(vnet.getUuid());
    }

    public Boolean filterVirtualNetwork(VirtualNetwork vnet, StringBuffer syncLogMesg) {
        if (_manager.isSystemDefaultNetwork(vnet)) {
            syncLogMesg.append("VN# VNC: " + vnet.getName() + " filtered; action: don't delete\n");
            return true;
        }
        return false;
    }

    public Boolean equalVirtualNetwork(NetworkVO dbn, VirtualNetwork vnet, StringBuffer syncLogMesg) {
        syncLogMesg.append("VN# DB: " + _manager.getCanonicalName(dbn) + "; VNC: " + vnet.getName() + "; action: equal\n");

        VirtualNetworkModel current = _manager.getDatabase().lookupVirtualNetwork(vnet.getUuid(), _manager.getCanonicalName(dbn), dbn.getTrafficType());

        VirtualNetworkModel vnModel = new VirtualNetworkModel(dbn, vnet.getUuid(), _manager.getCanonicalName(dbn), dbn.getTrafficType());
        if (dbn.getTrafficType() == TrafficType.Guest && dbn.getNetworkACLId() != null) {
            NetworkACLVO acl = _networkACLDao.findById(dbn.getNetworkACLId());
            NetworkPolicyModel policyModel = _manager.getDatabase().lookupNetworkPolicy(acl.getUuid());
            if (policyModel == null) {
                s_logger.error("Network(" + dbn.getName() + ") has ACL but policy model not created: " +
                                       acl.getUuid() + ", name: " + acl.getName());
            } else {
                vnModel.addToNetworkPolicy(policyModel);
            }
        }
        vnModel.build(_manager.getModelController(), dbn);

        if (_rwMode) {
            if (current != null) {
                FloatingIpPoolModel fipPoolModel = current.getFipPoolModel();
                if (fipPoolModel != null) {
                    vnModel.setFipPoolModel(fipPoolModel);
                    fipPoolModel.addToVirtualNetwork(vnModel);
                }
                _manager.getDatabase().getVirtualNetworks().remove(current);
            }
            s_logger.debug("add model " + vnModel.getName());
            _manager.getDatabase().getVirtualNetworks().add(vnModel);
            try {
                if (!vnModel.verify(_manager.getModelController())) {
                    vnModel.update(_manager.getModelController());
                }
            } catch (Exception ex) {
                s_logger.warn("update virtual-network", ex);
            }
            if (current != null) {
                NetworkPolicyModel oldPolicyModel = current.getNetworkPolicyModel();
                if (oldPolicyModel != vnModel.getNetworkPolicyModel()) {
                    /*
                     * if no other VNs are associated with the old policy,
                     * we could delete it from the Contrail VNC
                     */
                    if (oldPolicyModel != null && !oldPolicyModel.hasDescendents()) {
                        try {
                            oldPolicyModel.delete(_manager.getModelController());
                            _manager.getDatabase().getNetworkPolicys().remove(oldPolicyModel);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } else {
            //compare
            if (current != null && current.compare(_manager.getModelController(), vnModel) == false) {
                syncLogMesg.append("VN# DB: " + _manager.getCanonicalName(dbn) + "; VNC: " + vnet.getName() + "; attributes differ\n");
                return false;
            }
        }
        return true;
    }

    /*
     *  Virtual Machine Synchronization methods
     */

    public boolean syncVirtualMachine() {
        final ApiConnector api = _manager.getApiConnector();
        try {
            List<VMInstanceVO> vmDbList = _vmInstanceDao.listAll();
            @SuppressWarnings("unchecked")
            List<VirtualMachine> vncVmList = (List<VirtualMachine>)api.list(VirtualMachine.class, null);
            s_logger.debug("sync VM:  CS size: " + vmDbList.size() + " VNC size: " + vncVmList.size());
            return _dbSync.syncGeneric(VirtualMachine.class, vmDbList, vncVmList);
        } catch (Exception ex) {
            s_logger.warn("sync virtual-machines", ex);
        }
        return false;
    }

    public Comparator<?> dbComparatorVirtualMachine() {
        Comparator<?> comparator = new Comparator<VMInstanceVO>() {
            @Override
            public int compare(VMInstanceVO u1, VMInstanceVO u2) {
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Comparator<?> vncComparatorVirtualMachine() {
        Comparator<?> comparator = new Comparator<VirtualMachine>() {
            @Override
            public int compare(VirtualMachine u1, VirtualMachine u2) {
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public void createVirtualMachine(VMInstanceVO dbVm, StringBuffer syncLogMesg) throws IOException {
        syncLogMesg.append("VM# DB: " + dbVm.getInstanceName() + "/" + dbVm.getUuid() + "; VNC: none; action: create\n");
        VirtualMachineModel vmModel = new VirtualMachineModel(dbVm, dbVm.getUuid());
        vmModel.build(_manager.getModelController(), dbVm);
        buildNicResources(vmModel, dbVm, syncLogMesg);

        if (_rwMode) {
            try {
                vmModel.update(_manager.getModelController());
            } catch (InternalErrorException ex) {
                s_logger.warn("create virtual-machine", ex);
                return;
            }
            _manager.getDatabase().getVirtualMachines().add(vmModel);
            syncLogMesg.append("VM# VNC: " + dbVm.getUuid() + " created\n");
        }
    }

    private void deleteVirtualMachineInterfaces(List<ObjectReference<ApiPropertyBase>> list, StringBuffer syncLogMesg) throws IOException {
        if (list == null) {
            return;
        }
        final ApiConnector api = _manager.getApiConnector();
        for (ObjectReference<ApiPropertyBase> vmiRef : list) {
            VirtualMachineInterface vmi = (VirtualMachineInterface)api.findById(VirtualMachineInterface.class, vmiRef.getUuid());
            deleteInstanceIps(vmi.getInstanceIpBackRefs(), syncLogMesg);
            deleteFloatingIps(vmi.getFloatingIpBackRefs(), syncLogMesg);
            api.delete(VirtualMachineInterface.class, vmiRef.getUuid());
            syncLogMesg.append("VNC vmi: " + vmi.getUuid() + " deleted\n");
        }
    }

    private void deleteInstanceIps(List<ObjectReference<ApiPropertyBase>> list, StringBuffer syncLogMesg) throws IOException {
        if (list == null) {
            return;
        }
        final ApiConnector api = _manager.getApiConnector();
        for (ObjectReference<ApiPropertyBase> instIp : list) {
            api.delete(InstanceIp.class, instIp.getUuid());
            syncLogMesg.append("VNC instance ip: " + instIp.getUuid() + " deleted\n");
        }

    }

    private void deleteFloatingIps(List<ObjectReference<ApiPropertyBase>> list, StringBuffer syncLogMesg) throws IOException {
        if (list == null) {
            return;
        }
        final ApiConnector api = _manager.getApiConnector();
        for (ObjectReference<?> floatingIp : list) {
            api.delete(FloatingIp.class, floatingIp.getUuid());
            syncLogMesg.append("VNC instance ip: " + floatingIp.getUuid() + " deleted\n");
        }
    }

    public void deleteVirtualMachine(VirtualMachine vncVm, StringBuffer syncLogMesg) {
        final ApiConnector api = _manager.getApiConnector();
        syncLogMesg.append("VM# DB:none; VNC: " + vncVm.getName() + "/" + vncVm.getUuid() + "; action: delete\n");
        if (!_rwMode) {
            return;
        }
        try {
            if (!api.read(vncVm)) {
                return;
            }
            deleteVirtualMachineInterfaces(vncVm.getVirtualMachineInterfaces(), syncLogMesg);
            api.delete(VirtualMachine.class, vncVm.getUuid());
        } catch (IOException ex) {
            s_logger.warn("delete virtual-machine", ex);
            return;
        }
        syncLogMesg.append("VM# VNC: " + vncVm.getName() + " deleted\n");
    }

    public Integer compareVirtualMachine(VMInstanceVO dbVm, VirtualMachine vncVm, StringBuffer syncLogMesg) {
        String dbVmId = dbVm.getUuid();
        String vncVmId = vncVm.getUuid();
        return dbVmId.compareTo(vncVmId);
    }

    public boolean filterVirtualMachine(VirtualMachine vncVm, StringBuffer syncLogMesg) {
        return false;
    }

    private void buildNicResources(VirtualMachineModel vmModel, VMInstanceVO dbVm, StringBuffer syncLogMsg) throws IOException {
        List<NicVO> nics = _nicDao.listByVmId(dbVm.getId());
        for (NicVO nic : nics) {
            VMInterfaceModel vmiModel = vmModel.getVMInterface(nic.getUuid());
            if (vmiModel == null) {
                vmiModel = new VMInterfaceModel(nic.getUuid());
                NetworkVO network = _networksDao.findById(nic.getNetworkId());
                VirtualNetworkModel vnModel =
                    _manager.getDatabase().lookupVirtualNetwork(network.getUuid(), _manager.getCanonicalName(network), network.getTrafficType());
                if (vnModel == null) {
                    s_logger.warn("Unable to locate virtual-network for network id " + network.getId());
                    continue;
                }
                vmiModel.addToVirtualMachine(vmModel);
                vmiModel.addToVirtualNetwork(vnModel);
            }
            vmiModel.build(_manager.getModelController(), dbVm, nic);
        }
    }

    public Boolean equalVirtualMachine(VMInstanceVO dbVm, VirtualMachine vncVm, StringBuffer syncLogMsg) {

        syncLogMsg.append("VM# DB: " + dbVm.getInstanceName() + "/" + dbVm.getUuid() + "; VNC: " + vncVm.getUuid() + "; action: equal; DB VM State: " + dbVm.getState() +
            "\n");

        VirtualMachineModel vmModel = new VirtualMachineModel(dbVm, dbVm.getUuid());
        vmModel.build(_manager.getModelController(), dbVm);

        if (vmModel.isActive()) {
            try {
                buildNicResources(vmModel, dbVm, syncLogMsg);
            } catch (IOException ex) {
                s_logger.warn("build nic information for " + dbVm.getInstanceName(), ex);
            }
        }

        VirtualMachineModel current = _manager.getDatabase().lookupVirtualMachine(vncVm.getUuid());
        if (_rwMode) {
            if (current != null) {
                _manager.getDatabase().getVirtualMachines().remove(current);
            }
            _manager.getDatabase().getVirtualMachines().add(vmModel);
            try {
                vmModel.update(_manager.getModelController());
            } catch (Exception ex) {
                s_logger.warn("update virtual-machine", ex);
            }
        } else {
            //compare
            if (current != null && current.compare(_manager.getModelController(), vmModel) == false) {
                syncLogMsg.append("VM # DB: " + dbVm.getInstanceName() + "; VNC: " + vncVm.getName() + "; attributes differ\n");
                return false;
            }
        }
        return true;
    }

    public boolean syncFloatingIp() throws Exception {

        List<IPAddressVO> ipList = _manager.findManagedPublicIps();
        List<FloatingIp> vncList = _manager.getFloatingIps();
        if (ipList == null) {
            ipList = new ArrayList<IPAddressVO>();
        }
        if (vncList == null) {
            vncList = new ArrayList<FloatingIp>();
        }

        boolean status = false;
        try {
            status = _dbSync.syncGeneric(FloatingIp.class, ipList, vncList);
        } catch (Exception ex) {
            s_logger.warn("sync floating-ips", ex);
            throw ex;
        }
        return status;
    }

    public Comparator<?> dbComparatorFloatingIp() {
        Comparator<?> comparator = new Comparator<IpAddress>() {
            @Override
            public int compare(IpAddress u1, IpAddress u2) {
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Comparator<?> vncComparatorFloatingIp() {
        Comparator<?> comparator = new Comparator<FloatingIp>() {
            @Override
            public int compare(FloatingIp u1, FloatingIp u2) {
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Integer compareFloatingIp(IpAddress db, FloatingIp vnc, StringBuffer syncLogMesg) {
        String dbId = db.getUuid();
        String vncId = vnc.getUuid();
        return dbId.compareTo(vncId);
    }

    public void createFloatingIp(IPAddressVO dbIp, StringBuffer syncLogMesg) throws Exception {

        if (dbIp.getState() == IpAddress.State.Releasing) {
            /* Don't need to push releasing ip */
            syncLogMesg.append("fip# DB: " + dbIp.getUuid() + ", state releasing, don't create in vnc\n");
            return;
        }
        syncLogMesg.append("fip# DB: " + dbIp.getAddress().addr() + "; VNC: none; action: create\n");
        if (!_manager.createFloatingIp(PublicIp.createFromAddrAndVlan(dbIp, _vlanDao.findById(dbIp.getVlanId())))) {
            syncLogMesg.append("fip# VNC: " + dbIp.getAddress().addr() + " unable to create\n");
            return;
        }
        syncLogMesg.append("fip# VNC: " + dbIp.getUuid() + " created\n");
    }

    public void deleteFloatingIp(FloatingIp vnc, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        syncLogMesg.append("fip# DB: none; VNC: " + vnc.getAddress() + "(" + vnc.getUuid() + "); action: delete\n");
        api.delete(vnc);
        syncLogMesg.append("fip# VNC: " + vnc.getUuid() + " deleted\n");
    }

    public Boolean equalFloatingIp(IPAddressVO db, FloatingIp vnc, StringBuffer syncLogMsg) throws IOException {

        syncLogMsg.append("fip# DB: " + db.getAddress().addr() + "; VNC: " + vnc.getAddress() + "; action: equal" + "\n");

        VirtualNetworkModel vnModel = _manager.lookupPublicNetworkModel();
        assert vnModel != null : "public network vn model is null";

        FloatingIpPoolModel fipPoolModel = vnModel.getFipPoolModel();
        if (fipPoolModel == null) {
            fipPoolModel = new FloatingIpPoolModel();
            fipPoolModel.addToVirtualNetwork(vnModel);
            fipPoolModel.build(_manager.getModelController());
            try {
                fipPoolModel.update(_manager.getModelController());
                vnModel.setFipPoolModel(fipPoolModel);
            } catch (Exception ex) {
                s_logger.warn("floating-ip-pool create: ", ex);
                return false;
            }
        }

        FloatingIpModel current = fipPoolModel.getFloatingIpModel(db.getUuid());
        if (current == null) {
            s_logger.debug("add model " + db.getAddress().addr());
            FloatingIpModel fipModel = new FloatingIpModel(db.getUuid());
            fipModel.addToFloatingIpPool(fipPoolModel);
            fipModel.build(_manager.getModelController(), PublicIp.createFromAddrAndVlan(db, _vlanDao.findById(db.getVlanId())));
            try {
                fipModel.update(_manager.getModelController());
            } catch (Exception ex) {
                s_logger.warn("floating-ip create: ", ex);
                return false;
            }
        }
        return true;
    }

    /*
     *  Network Policy Synchronization methods
     */
    @SuppressWarnings({ "unchecked" })
    public boolean syncNetworkPolicy() throws Exception {
        final ApiConnector api = _manager.getApiConnector();
        try {

            List<NetworkACLVO> dbAcls = _manager.findManagedACLs();
            if (dbAcls == null) {
                dbAcls = new ArrayList<NetworkACLVO>();
            }

            List<NetworkPolicy> pList = (List<NetworkPolicy>) api.list(NetworkPolicy.class, null);
            List<NetworkPolicy> vncList = new ArrayList<NetworkPolicy>();

            for (NetworkPolicy policy:pList) {
                if (!_manager.isSystemDefaultNetworkPolicy(policy)) {
                    vncList.add(policy);
                }
            }
            s_logger.debug("sync Network Policy - DB size: " + dbAcls.size() + " VNC Size: " + vncList.size());
            return _dbSync.syncGeneric(NetworkPolicy.class, dbAcls, vncList);
        } catch (Exception ex) {
            s_logger.warn("sync network-policys", ex);
            throw ex;
        }
    }

    public Comparator<NetworkACLVO> dbComparatorNetworkPolicy() {
        Comparator<NetworkACLVO> comparator = new Comparator<NetworkACLVO>() {
            public int compare(NetworkACLVO u1, NetworkACLVO u2) {
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public Comparator<?> vncComparatorNetworkPolicy() {
        Comparator<?> comparator = new Comparator<NetworkPolicy>() {
            public int compare(NetworkPolicy u1, NetworkPolicy u2) {
                return u1.getUuid().compareTo(u2.getUuid());
            }
        };
        return comparator;
    }

    public void createNetworkPolicy(NetworkACLVO db, StringBuffer syncLogMesg) throws IOException {
        syncLogMesg.append("Policy# DB: " + db.getName() +
                "(" + db.getUuid() + "); VNC: none;  action: create\n");

        if (_manager.getDatabase().lookupNetworkPolicy(db.getUuid()) != null) {
             s_logger.warn("Policy model object is already present in DB: " +
                                   db.getUuid() + ", name: " + db.getName());
        }
        NetworkPolicyModel policyModel = new NetworkPolicyModel(db.getUuid(), db.getName());
        net.juniper.contrail.api.types.Project project = null;
        try {
            VpcVO vpc = _vpcDao.findById(db.getVpcId());
            if (vpc != null) {
                project = _manager.getVncProject(vpc.getDomainId(), vpc.getAccountId());
            } else {
                project = _manager.getDefaultVncProject();
            }
        } catch (IOException ex) {
            s_logger.warn("read project", ex);
            throw ex;
        }
        policyModel.setProject(project);
        List<NetworkACLItemVO> rules = _networkACLItemDao.listByACL(db.getId());
        try {
            policyModel.build(_manager.getModelController(), rules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (_rwMode) {
            try {
                if (!policyModel.verify(_manager.getModelController())) {
                    policyModel.update(_manager.getModelController());
                }
            } catch (Exception ex) {
                s_logger.warn("create network-policy", ex);
                syncLogMesg.append("Error: Policy# VNC : Unable to create network policy " +
                    db.getName() + "\n");
                return;
            }
            s_logger.debug("add model " + policyModel.getName());
            _manager.getDatabase().getNetworkPolicys().add(policyModel);
            syncLogMesg.append("Policy# VNC: " + db.getUuid() + ", " + policyModel.getName() + " created\n");
        } else {
            syncLogMesg.append("Policy# VNC: " + policyModel.getName() + " created \n");
        }
    }

    public void deleteNetworkPolicy(NetworkPolicy policy, StringBuffer syncLogMesg) throws IOException {
        final ApiConnector api = _manager.getApiConnector();
        if (_manager.isSystemDefaultNetworkPolicy(policy)) {
            syncLogMesg.append("Policy# System default Network Policy# VNC: " + policy.getName() + " can not be deleted\n");
            return;
        }
        syncLogMesg.append("Policy# DB: none; VNC: " + policy.getName() + "(" + policy.getUuid() + "); action: delete\n");
        api.delete(policy);
        syncLogMesg.append("Policy# VNC: " + policy.getName() + " deleted\n");
    }

    public Integer compareNetworkPolicy(NetworkACLVO dbn, NetworkPolicy policy, StringBuffer syncLogMesg) {
        if (_manager.isSystemDefaultNetworkPolicy(policy)) {
            return 1;
        }
        return dbn.getUuid().compareTo(policy.getUuid());
    }

    public Boolean filterNetworkPolicy(NetworkPolicy policy, StringBuffer syncLogMesg)  {
        if (_manager.isSystemDefaultNetworkPolicy(policy)) {
            syncLogMesg.append("Policy# VNC: " + policy.getName() + " filtered; action: don't delete\n");
            return true;
        }
        return false;
    }

    public Boolean equalNetworkPolicy(NetworkACLVO db, NetworkPolicy policy, StringBuffer syncLogMesg) {
        syncLogMesg.append("Policy# DB: " + db.getName() +
                "; VNC: " + policy.getName() + "; action: equal\n");
        NetworkPolicyModel current = _manager.getDatabase().lookupNetworkPolicy(policy.getUuid());
        NetworkPolicyModel policyModel = new NetworkPolicyModel(db.getUuid(), db.getName());
        net.juniper.contrail.api.types.Project project = null;
        try {
            VpcVO vpc = _vpcDao.findById(db.getVpcId());
            if (vpc != null) {
                project = _manager.getVncProject(vpc.getDomainId(), vpc.getAccountId());
            } else {
                project = _manager.getDefaultVncProject();
            }
        } catch (IOException ex) {
            s_logger.warn("read project", ex);
        }
        policyModel.setProject(project);
        List<NetworkACLItemVO> rules = _networkACLItemDao.listByACL(db.getId());
        try {
            policyModel.build(_manager.getModelController(), rules);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (_rwMode) {
            if (current != null) {
                _manager.getDatabase().getNetworkPolicys().remove(current);
            }
            s_logger.debug("add policy model " + policyModel.getName());
            _manager.getDatabase().getNetworkPolicys().add(policyModel);
            try {
                if (!policyModel.verify(_manager.getModelController())) {
                    policyModel.update(_manager.getModelController());
                }
            } catch (Exception ex) {
                s_logger.warn("update network-policy", ex);
            }
        } else {
            //compare
            if (current != null && current.compare(_manager.getModelController(), policyModel) == false) {
                syncLogMesg.append("Policy# DB: " + db.getName() +
                        "; VNC: " + policy.getName() + "; attributes differ\n");
                return false;
            }
        }
        return true;
    }

    public Integer compareServiceInstance(ServiceInstanceModel siModel, ServiceInstance siObj, StringBuffer logMsg) {
        String fqn = StringUtils.join(siObj.getQualifiedName(), ':');
        return siModel.getQualifiedName().compareTo(fqn);
    }

    /**
     * createServiceInstance
     *
     * This method should never be invoked since the model objects have been installed already when sync is called.
     * @param siModel
     * @param logMsg
     */
    public void createServiceInstance(ServiceInstanceModel siModel, StringBuffer logMsg) {
        assert false;
    }

    public void deleteServiceInstance(ServiceInstance siObj, StringBuffer logMsg) {
        final ApiConnector api = _manager.getApiConnector();
        s_logger.debug("delete " + siObj.getQualifiedName());
        if (!_rwMode) {
            return;
        }
        try {
            api.delete(siObj);
        } catch (IOException ex) {
            s_logger.warn("service-instance delete", ex);
        }
    }

    /**
     * equalServiceInstance
     *
     * @param siModel
     * @param siObj
     * @param logMsg
     */
    public void equalServiceInstance(ServiceInstanceModel siModel, ServiceInstance siObj, StringBuffer logMsg) {
        s_logger.debug("equal " + siModel.getQualifiedName());
    }

    static class ServiceInstanceComparator implements Comparator<ServiceInstance>, Serializable {
        @Override
        public int compare(ServiceInstance obj1, ServiceInstance obj2) {
            String name1 = StringUtils.join(obj1.getQualifiedName(), ':');
            String name2 = StringUtils.join(obj2.getQualifiedName(), ':');
            return name1.compareTo(name2);
        }

    }

    /**
     * The service-instance model list is build as a result of synchronizing virtual-machines.
     * @return
     */
    public boolean syncServiceInstance() {
        final ApiConnector api = _manager.getApiConnector();
        boolean inSync;
        try {
            @SuppressWarnings("unchecked")
            List<ServiceInstance> siList = (List<ServiceInstance>)api.list(ServiceInstance.class, null);
            java.util.Collections.sort(siList, new ServiceInstanceComparator());
            DBSyncGeneric.SyncStats stats = new DBSyncGeneric.SyncStats();
            _dbSync.syncCollections(ServiceInstance.class, _manager.getDatabase().getServiceInstances(), siList, _rwMode, stats);
            inSync = stats.create == 0 && stats.delete == 0;
        } catch (Exception ex) {
            s_logger.warn("synchronize service-instances", ex);
            return false;
        }
        return inSync;
    }
}
