org.ndsc.mimicIO.db.DBManager.java Source code

Java tutorial

Introduction

Here is the source code for org.ndsc.mimicIO.db.DBManager.java

Source

/*
 * Copyright 2016-present NDSC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/liceCopyrightnses/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.ndsc.mimicIO.db;

import com.mongodb.*;
import net.onrc.openvirtex.api.service.handlers.TenantHandler;
import net.onrc.openvirtex.elements.Persistable;
import net.onrc.openvirtex.elements.datapath.DPIDandPort;
import net.onrc.openvirtex.elements.datapath.DPIDandPortPair;
import net.onrc.openvirtex.elements.datapath.Switch;
import net.onrc.openvirtex.elements.link.Link;
import net.onrc.openvirtex.elements.link.OVXLink;
import net.onrc.openvirtex.elements.network.OVXNetwork;
import net.onrc.openvirtex.elements.port.Port;
import net.onrc.openvirtex.exceptions.DuplicateIndexException;
import net.onrc.openvirtex.exceptions.IndexOutOfBoundException;
import net.onrc.openvirtex.routing.SwitchRoute;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The singleton class database manager is responsible
 * for all read/write operations to persistent storage.
 * Upon start, it will read all persisted virtual networks,
 * and spawn virtual network managers for each that will
 * wait until all their elements are available and then boot
 * the virtual network.
 */
public final class DBManager {
    /**
     * Database collection name for OVX configuration.
     */
    public static final String DB_CONFIG = "CONFIG";
    /**
     * Database collection name for OVX users.
     */
    public static final String DB_USER = "USER";
    /**
     * Database collection name for virtual networks.
     */
    public static final String DB_VNET = "VNET";

    private static DBManager instance;
    private DBConnection dbConnection;
    private Map<String, DBCollection> collections;
    private boolean clear;
    // Mapping between physical dpids and a list of vnet managers
    private Map<Long, List<OVXNetworkManager>> dpidToMngr;
    // Mapping between physical links and a list of vnet managers
    private Map<DPIDandPortPair, List<OVXNetworkManager>> linkToMngr;
    // Mapping between physical ports and a list of vnet managers
    private Map<DPIDandPort, List<OVXNetworkManager>> portToMngr;

    private static Logger log = LogManager.getLogger(DBManager.class.getName());

    /** Creates the database manager instance. Connects
     * to the database backend, and creates mappings between
     * network elements and virtual network managers.
     */
    private DBManager() {
        this.dbConnection = new MongoConnection();
        this.collections = new HashMap<String, DBCollection>();
        this.dpidToMngr = new HashMap<Long, List<OVXNetworkManager>>();
        this.linkToMngr = new HashMap<DPIDandPortPair, List<OVXNetworkManager>>();
        this.portToMngr = new HashMap<DPIDandPort, List<OVXNetworkManager>>();
    }

    /**
     * Gets the database manager instance, and creates one
     * if it doesn't exist.
     *
     * @return the database manager instance
     */
    public static DBManager getInstance() {
        if (DBManager.instance == null) {
            DBManager.instance = new DBManager();
        }
        return DBManager.instance;
    }

    /**
     * Initializes database backend by creating
     * config, user and vnet collections.
     *
     * @param host the database host
     * @param port the database port
     * @param clear flag to clear the database
     */
    public void init(String host, Integer port, boolean clear) {
        this.dbConnection.connect(host, port);
        // Suppress error stream when MongoDB raises java.net.ConnectException
        // in another component (and cannot be caught)
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            // Retrieve (create if non-existing) collections from db
            // and store their handlers
            DB db = ((MongoConnection) this.dbConnection).getDB();

            DBCollection cfg = db.getCollection(DBManager.DB_CONFIG);
            this.collections.put(DBManager.DB_CONFIG, cfg);
            DBCollection user = db.getCollection(DBManager.DB_USER);
            this.collections.put(DBManager.DB_USER, user);
            DBCollection vnet = db.getCollection(DBManager.DB_VNET);
            this.collections.put(DBManager.DB_VNET, vnet);

            this.setIndex(DBManager.DB_VNET);

            this.clear = clear;
            if (this.clear) {
                this.clear(DBManager.DB_VNET);
            } else {
                this.readOVXNetworks();
            }

        } catch (Exception e) {
            log.error("Failed to initialize database: {}", e.getMessage());
        } finally {
            // Restore error stream
            System.setErr(ps);
        }
    }

    /**
     * Sets the index of the given collection.
     *
     * @param coll the collection
     */
    private void setIndex(String coll) {
        // Suppress error stream when MongoDB raises java.net.ConnectException
        // in another component (and cannot be caught)
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            BasicDBObject options = new BasicDBObject("unique", true);
            BasicDBObject index = new BasicDBObject(TenantHandler.TENANT, 1);
            this.collections.get(coll).ensureIndex(index, options);
        } catch (Exception e) {
            log.error("Failed to set database index: {}", e.getMessage());
        } finally {
            // Restore error stream
            System.setErr(ps);
        }
    }

    /**
     * Clears the given collection in the database.
     *
     * @param coll the collection
     */
    private void clear(String coll) {
        // Suppress error stream when MongoDB raises java.net.ConnectException
        // in another component (and cannot be caught)
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            this.collections.get(coll).drop();
            this.setIndex(DBManager.DB_VNET);
        } catch (Exception e) {
            log.error("Failed to clear database: {}", e.getMessage());
        } finally {
            // Restore error stream
            System.setErr(ps);
        }
    }

    /**
     * Closes connection to database backend.
     */
    public void close() {
        // Suppress error stream when MongoDB raises java.net.ConnectException
        // in another component (and cannot be caught)
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            this.dbConnection.disconnect();
        } catch (Exception e) {
            log.error("Failed to close database connection: {}", e.getMessage());
        } finally {
            // Restore error stream
            System.setErr(ps);
        }
    }

    /**
     * Creates document in db from persistable object obj.
     *
     * @param obj the object to create
     */
    public void createDoc(Persistable obj) {
        // Suppress error stream when MongoDB raises java.net.ConnectException
        // in another component (and cannot be caught)
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            DBCollection collection = this.collections.get(obj.getDBName());
            collection.insert(new BasicDBObject(obj.getDBObject()));
        } catch (Exception e) {
            // Do not log when duplicate key
            // Virtual network was already stored and we're trying to create it
            // again on startup
            if (e instanceof MongoException.DuplicateKey) {
                log.warn("Skipped saving of virtual network with duplicate tenant id");
            } else {
                log.error("Failed to insert document into database: {}", e.getMessage());
            }
        } finally {
            // Restore error stream
            System.setErr(ps);
        }
    }

    /**
     * Removes document from db.
     *
     * @param obj the object to remove
     */
    public void removeDoc(Persistable obj) {
        // Suppress error stream when MongoDB raises java.net.ConnectException
        // in another component (and cannot be caught)
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            DBCollection collection = this.collections.get(obj.getDBName());
            collection.remove(new BasicDBObject(obj.getDBObject()));
        } catch (Exception e) {
            log.error("Failed to remove document from database: {}", e.getMessage());
        } finally {
            // Restore error stream
            System.setErr(ps);
        }
    }

    /**
     * Saves persistable object obj.
     *
     * @param obj the object to persist
     */
    public void save(Persistable obj) {
        BasicDBObject query = new BasicDBObject();
        query.putAll(obj.getDBIndex());
        BasicDBObject update = new BasicDBObject("$addToSet", new BasicDBObject(obj.getDBKey(), obj.getDBObject()));
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            DBCollection collection = this.collections.get(obj.getDBName());
            collection.update(query, update, true, false);
        } catch (Exception e) {
            log.error("Failed to update database: {}", e.getMessage());
        } finally {
            System.setErr(ps);
        }
    }

    /**
     * Removes persistable object obj.
     *
     * @param obj the object to remove
     */
    public void remove(Persistable obj) {
        BasicDBObject query = new BasicDBObject();
        query.putAll(obj.getDBIndex());
        BasicDBObject update = new BasicDBObject("$pull", new BasicDBObject(obj.getDBKey(), obj.getDBObject()));
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            DBCollection collection = this.collections.get(obj.getDBName());
            collection.update(query, update);
        } catch (Exception e) {
            log.error("Failed to remove from db: {}", e.getMessage());
        } finally {
            System.setErr(ps);
        }
    }

    /**
     * Removes all routes of switch for given tenant.
     *
     * @param tenantId the tenant ID
     * @param switchId the dpid
     */
    public void removeSwitchPath(int tenantId, long switchId) {
        BasicDBObject query = new BasicDBObject();
        query.put(TenantHandler.TENANT, tenantId);
        BasicDBObject pull = new BasicDBObject("$pull",
                new BasicDBObject(SwitchRoute.DB_KEY, new BasicDBObject(TenantHandler.DPID, switchId)));
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            DBCollection collection = this.collections.get(DB_VNET);
            collection.update(query, pull);
        } catch (Exception e) {
            log.error("Failed to remove from db: {}", e.getMessage());
        } finally {
            System.setErr(ps);
        }
    }

    /**
     * Removes stored path of vlink for specified tenant.
     *
     * @param tenantId the tenant ID
     * @param linkId the link ID
     */
    public void removeLinkPath(int tenantId, int linkId) {
        BasicDBObject query = new BasicDBObject();
        query.put(TenantHandler.TENANT, tenantId);
        BasicDBObject pull = new BasicDBObject("$pull",
                new BasicDBObject(OVXLink.DB_KEY, new BasicDBObject(TenantHandler.LINK, linkId)));
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            DBCollection collection = this.collections.get(DB_VNET);
            collection.update(query, pull);
        } catch (Exception e) {
            log.error("Failed to remove from db: {}", e.getMessage());
        } finally {
            System.setErr(ps);
        }
    }

    /**
     * Reads all virtual networks from database and spawn an OVXNetworkManager
     * for each.
     */
    @SuppressWarnings("unchecked")
    private void readOVXNetworks() {
        PrintStream ps = System.err;
        System.setErr(null);
        try {
            // Get a cursor over all virtual networks
            DBCollection coll = this.collections.get(DBManager.DB_VNET);
            DBCursor cursor = coll.find();
            log.info("Loading {} virtual networks from database", cursor.size());
            while (cursor.hasNext()) {
                OVXNetworkManager mngr = null;
                Map<String, Object> vnet = cursor.next().toMap();
                try {
                    // Create vnet manager for each virtual network
                    mngr = new OVXNetworkManager(vnet);
                    OVXNetwork.reserveTenantId(mngr.getTenantId());
                    // Accessing DB_KEY field through a class derived from the
                    // abstract OVXSwitch
                    List<Map<String, Object>> switches = (List<Map<String, Object>>) vnet.get(Switch.DB_KEY);
                    List<Map<String, Object>> links = (List<Map<String, Object>>) vnet.get(Link.DB_KEY);
                    List<Map<String, Object>> ports = (List<Map<String, Object>>) vnet.get(Port.DB_KEY);
                    List<Map<String, Object>> routes = (List<Map<String, Object>>) vnet.get(SwitchRoute.DB_KEY);
                    this.readOVXSwitches(switches, mngr);
                    this.readOVXLinks(links, mngr);
                    this.readOVXPorts(ports, mngr);
                    this.readOVXRoutes(routes, mngr);
                    DBManager.log.info("Virtual network {} waiting for {} switches, {} links and {} ports",
                            mngr.getTenantId(), mngr.getSwitchCount(), mngr.getLinkCount(), mngr.getPortCount());
                } catch (IndexOutOfBoundException | DuplicateIndexException e) {
                    DBManager.log.error("Failed to load virtual network {}: {}", mngr.getTenantId(),
                            e.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("Failed to load virtual networks from db: {}", e.getMessage());
        } finally {
            System.setErr(ps);
        }
    }

    /**
     * Reads OVX switches from a list of maps in db format and registers them in
     * their manager.
     *
     * @param switches the list of switches
     * @param mngr the virtual network manager
     */
    @SuppressWarnings("unchecked")
    private void readOVXSwitches(List<Map<String, Object>> switches, OVXNetworkManager mngr) {
        if (switches == null) {
            return;
        }
        // Read explicit switch mappings (virtual to physical)
        for (Map<String, Object> sw : switches) {
            List<Long> physwitches = (List<Long>) sw.get(TenantHandler.DPIDS);
            for (Long physwitch : physwitches) {
                mngr.registerSwitch(physwitch);
                List<OVXNetworkManager> mngrs = this.dpidToMngr.get(physwitch);
                if (mngrs == null) {
                    this.dpidToMngr.put(physwitch, new ArrayList<OVXNetworkManager>());
                }
                this.dpidToMngr.get(physwitch).add(mngr);
            }
        }
    }

    /**
     * Reads OVX links from a list of maps in db format and registers them in
     * their manager. Also reads switches that form a virtual link and registers
     * them.
     *
     * @param links the list of links
     * @param mngr the virtual network manager
     */
    @SuppressWarnings("unchecked")
    private void readOVXLinks(List<Map<String, Object>> links, OVXNetworkManager mngr) {
        if (links == null) {
            return;
        }
        // Register links in the appropriate manager
        for (Map<String, Object> link : links) {
            List<Map<String, Object>> path = (List<Map<String, Object>>) link.get(TenantHandler.PATH);
            for (Map<String, Object> hop : path) {
                // Fetch link
                Long srcDpid = (Long) hop.get(TenantHandler.SRC_DPID);
                Short srcPort = ((Integer) hop.get(TenantHandler.SRC_PORT)).shortValue();
                Long dstDpid = (Long) hop.get(TenantHandler.DST_DPID);
                Short dstPort = ((Integer) hop.get(TenantHandler.DST_PORT)).shortValue();
                DPIDandPortPair dpp = new DPIDandPortPair(new DPIDandPort(srcDpid, srcPort),
                        new DPIDandPort(dstDpid, dstPort));
                // Register link in current manager
                mngr.registerLink(dpp);
                // Update list of managers that wait for this link
                List<OVXNetworkManager> mngrs = this.linkToMngr.get(dpp);
                if (mngrs == null) {
                    this.linkToMngr.put(dpp, new ArrayList<OVXNetworkManager>());
                }
                this.linkToMngr.get(dpp).add(mngr);

                // Register src/dst switches of this link
                mngr.registerSwitch(srcDpid);
                mngr.registerSwitch(dstDpid);
                // Update list of managers that wait for these switches
                mngrs = this.dpidToMngr.get(srcDpid);
                if (mngrs == null) {
                    this.dpidToMngr.put(srcDpid, new ArrayList<OVXNetworkManager>());
                }
                this.dpidToMngr.get(srcDpid).add(mngr);
                mngrs = this.dpidToMngr.get(dstDpid);
                if (mngrs == null) {
                    this.dpidToMngr.put(dstDpid, new ArrayList<OVXNetworkManager>());
                }
                this.dpidToMngr.get(dstDpid).add(mngr);
            }
        }
    }

    /**
     * Reads OVX links from a list of maps in db format and registers them in
     * their manager. Also reads switches that form a virtual link and register
     * them.
     *
     * @param ports the list of ports
     * @param mngr the virtual network manager
     */
    private void readOVXPorts(List<Map<String, Object>> ports, OVXNetworkManager mngr) {
        if (ports == null) {
            return;
        }
        for (Map<String, Object> port : ports) {
            // Read dpid and port number
            Long dpid = (Long) port.get(TenantHandler.DPID);
            Short portNumber = ((Integer) port.get(TenantHandler.PORT)).shortValue();
            DPIDandPort p = new DPIDandPort(dpid, portNumber);
            // Register port in current manager
            mngr.registerPort(p);
            // Update list of managers that wait for this port
            List<OVXNetworkManager> mngrs = this.portToMngr.get(p);
            if (mngrs == null) {
                this.portToMngr.put(p, new ArrayList<OVXNetworkManager>());
            }
            this.portToMngr.get(p).add(mngr);
        }
    }

    /**
     * Reads OVX routes from a list of maps in db format and registers the
     * switches and links in their manager.
     *
     * @param routes the list of routes
     * @param mngr the virtual network manager
     */
    @SuppressWarnings({ "unchecked" })
    private void readOVXRoutes(List<Map<String, Object>> routes, OVXNetworkManager mngr) {
        if (routes == null) {
            return;
        }
        for (Map<String, Object> route : routes) {
            List<Map<String, Object>> path = (List<Map<String, Object>>) route.get(TenantHandler.PATH);
            for (Map<String, Object> hop : path) {
                Long srcDpid = (Long) hop.get(TenantHandler.SRC_DPID);
                Short srcPort = ((Integer) hop.get(TenantHandler.SRC_PORT)).shortValue();
                Long dstDpid = (Long) hop.get(TenantHandler.DST_DPID);
                Short dstPort = ((Integer) hop.get(TenantHandler.DST_PORT)).shortValue();
                DPIDandPortPair dpp = new DPIDandPortPair(new DPIDandPort(srcDpid, srcPort),
                        new DPIDandPort(dstDpid, dstPort));
                // Register links in the appropriate manager
                mngr.registerLink(dpp);
                List<OVXNetworkManager> mngrs = this.linkToMngr.get(dpp);
                if (mngrs == null) {
                    this.linkToMngr.put(dpp, new ArrayList<OVXNetworkManager>());
                }
                this.linkToMngr.get(dpp).add(mngr);

                // Register switches
                mngr.registerSwitch(srcDpid);
                mngr.registerSwitch(dstDpid);
                mngrs = this.dpidToMngr.get(srcDpid);
                if (mngrs == null) {
                    this.dpidToMngr.put(srcDpid, new ArrayList<OVXNetworkManager>());
                }
                this.dpidToMngr.get(srcDpid).add(mngr);
                mngrs = this.dpidToMngr.get(dstDpid);
                if (mngrs == null) {
                    this.dpidToMngr.put(dstDpid, new ArrayList<OVXNetworkManager>());
                }
                this.dpidToMngr.get(dstDpid).add(mngr);
            }
        }
    }

    /**
     * Adds physical switch to the OVXNetworkManagers that are waiting for this
     * switch. Removes OVXNetworkManagers that were booted after adding this
     * switch. This method is called by the PhysicalSwitch.boot() method.
     *
     * @param dpid the swith dpid
     */
    public void addSwitch(final Long dpid) {
        // Disregard physical switch creation if OVX was started with --dbClear
        if (!this.clear) {
            List<OVXNetworkManager> completedMngrs = new ArrayList<OVXNetworkManager>();
            synchronized (this.dpidToMngr) {
                // Lookup virtual networks that use this physical switch
                List<OVXNetworkManager> mngrs = this.dpidToMngr.get(dpid);
                if (mngrs != null) {
                    for (OVXNetworkManager mngr : mngrs) {
                        mngr.setSwitch(dpid);
                        if (mngr.getStatus()) {
                            completedMngrs.add(mngr);
                        }
                    }
                }
            }
            this.removeOVXNetworkManagers(completedMngrs);
        }
    }

    /**
     * Deletes physical switch from the OVXNetworkManagers that are waiting for
     * this switch. This method is called by PhysicalNetwork when switch has
     * disconnected.
     *
     * @param dpid the switch dpid
     */
    public void delSwitch(final Long dpid) {
        // Disregard physical switch deletion if OVX was started with --dbClear
        if (!this.clear) {
            synchronized (this.dpidToMngr) {
                // Lookup virtual networks that use this physical switch
                List<OVXNetworkManager> mngrs = this.dpidToMngr.get(dpid);
                if (mngrs != null) {
                    for (OVXNetworkManager mngr : mngrs) {
                        mngr.unsetSwitch(dpid);
                    }
                }
            }
        }
    }

    /**
     * Adds physical link to the OVXNetworkManagers that are waiting for this
     * link. Removes OVXNetworkManagers that were booted after adding this link.
     *
     * @param dpp physical link given as a dpid and port pair
     */
    public void addLink(final DPIDandPortPair dpp) {
        // Disregard physical link creation if OVX was started with --dbClear
        if (!this.clear) {
            List<OVXNetworkManager> completedMngrs = new ArrayList<OVXNetworkManager>();
            synchronized (this.linkToMngr) {
                // Lookup virtual networks that use this physical link
                List<OVXNetworkManager> mngrs = this.linkToMngr.get(dpp);
                if (mngrs != null) {
                    for (OVXNetworkManager mngr : mngrs) {
                        mngr.setLink(dpp);
                        if (mngr.getStatus()) {
                            completedMngrs.add(mngr);
                        }
                    }
                }
            }
            this.removeOVXNetworkManagers(completedMngrs);
        }
    }

    /**
     * Deletes physical link from the OVXNetworkManagers that are waiting for
     * this switch.
     *
     * @param dpp the physical link given as a dpid and port pair
     */
    public void delLink(final DPIDandPortPair dpp) {
        // Disregard physical link deletion if OVX was started with --dbClear
        if (!this.clear) {
            synchronized (this.linkToMngr) {
                // Lookup virtual networks that use this physical link
                List<OVXNetworkManager> mngrs = this.linkToMngr.get(dpp);
                if (mngrs != null) {
                    for (OVXNetworkManager mngr : mngrs) {
                        mngr.unsetLink(dpp);
                    }
                }
            }
        }
    }

    /**
     * Adds physical port to the OVXNetworkManagers that are waiting for this
     * port. Remove OVXNetworkManagers that were booted after adding this port.
     *
     * @param port the port given as a dpid and port pair
     */
    public void addPort(final DPIDandPort port) {
        // Disregard physical port creation if OVX was started with --dbClear
        if (!this.clear) {
            List<OVXNetworkManager> completedMngrs = new ArrayList<OVXNetworkManager>();
            synchronized (this.portToMngr) {
                // Lookup virtual networks that use this physical port
                List<OVXNetworkManager> mngrs = this.portToMngr.get(port);
                if (mngrs != null) {
                    for (OVXNetworkManager mngr : mngrs) {
                        mngr.setPort(port);
                        if (mngr.getStatus()) {
                            completedMngrs.add(mngr);
                        }
                    }
                }
            }
            this.removeOVXNetworkManagers(completedMngrs);
        }
    }

    /**
     * Deletes physical port from the OVXNetworkManagers that are waiting for
     * this switch.
     *
     * @param port the port given as a dpid and port pair
     */
    public void delPort(final DPIDandPort port) {
        // Disregard physical link deletion if OVX was started with --dbClear
        if (!this.clear) {
            synchronized (this.dpidToMngr) {
                // Lookup virtual networks that use this physical link
                List<OVXNetworkManager> mngrs = this.portToMngr.get(port);
                if (mngrs != null) {
                    for (OVXNetworkManager mngr : mngrs) {
                        mngr.unsetPort(port);
                    }
                }
            }
        }
    }

    /**
     * Removes network managers that were waiting for switches, links or ports.
     *
     * @param mngrs list of virtual network managers
     */
    private void removeOVXNetworkManagers(List<OVXNetworkManager> mngrs) {
        for (OVXNetworkManager mngr : mngrs) {
            synchronized (this.dpidToMngr) {
                for (Long dpid : this.dpidToMngr.keySet()) {
                    this.dpidToMngr.get(dpid).remove(mngr);
                }
            }
            synchronized (this.linkToMngr) {
                for (DPIDandPortPair dpp : this.linkToMngr.keySet()) {
                    this.linkToMngr.get(dpp).remove(mngr);
                }
            }
            synchronized (this.portToMngr) {
                for (DPIDandPort dp : this.portToMngr.keySet()) {
                    this.portToMngr.get(dp).remove(mngr);
                }
            }
        }
    }
}