Esempio n. 1
0
    def __init__(self, conf={}, daoClass=Dao):
        if any(conf) == False:
            self._openclosProperty = OpenClosProperty(appName=moduleName)
            self._conf = self._openclosProperty.getProperties()

            global webServerRoot
            webServerRoot = self._conf['outputDir']
        else:
            self._conf = conf

        self.__daoClass = daoClass
        self.__dao = daoClass.getInstance()
        self.openclosDbSessionPlugin = OpenclosDbSessionPlugin(daoClass)

        if 'httpServer' in self._conf and 'ipAddr' in self._conf[
                'httpServer'] and self._conf['httpServer'][
                    'ipAddr'] is not None:
            self.host = self._conf['httpServer']['ipAddr']
        else:
            self.host = 'localhost'

        if 'httpServer' in self._conf and 'port' in self._conf['httpServer']:
            self.port = self._conf['httpServer']['port']
        else:
            self.port = 8080
        self.baseUrl = 'http://%s:%d' % (self.host, self.port)

        self.report = ResourceAllocationReport(self._conf, daoClass)
        # Create a single instance of l2Report as it holds thread-pool
        # for device connection. Don't create l2Report multiple times
        self.l2Report = L2Report(self._conf, daoClass)
        # Create a single instance of l3Report as it holds thread-pool
        # for device connection. Don't create l3Report multiple times
        self.l3Report = L3Report(self._conf, daoClass)
        self.deviceSku = DeviceSku()
Esempio n. 2
0
    def __init__(self, conf = {}, daoClass = Dao):
        if any(conf) == False:
            self._openclosProperty = OpenClosProperty(appName = moduleName)
            self._conf = self._openclosProperty.getProperties()

            global webServerRoot
            webServerRoot = self._conf['outputDir']
        else:
            self._conf = conf
        
        self.__daoClass = daoClass
        self.__dao = daoClass.getInstance()
        self.openclosDbSessionPlugin = OpenclosDbSessionPlugin(daoClass)
        
        if 'httpServer' in self._conf and 'ipAddr' in self._conf['httpServer'] and self._conf['httpServer']['ipAddr'] is not None:
            self.host = self._conf['httpServer']['ipAddr']
        else:
            self.host = 'localhost'

        if 'httpServer' in self._conf and 'port' in self._conf['httpServer']:
            self.port = self._conf['httpServer']['port']
        else:
            self.port = 8080
        self.baseUrl = 'http://%s:%d' % (self.host, self.port)

        self.report = ResourceAllocationReport(self._conf, daoClass)
        # Create a single instance of l2Report as it holds thread-pool
        # for device connection. Don't create l2Report multiple times 
        self.l2Report = L2Report(self._conf, daoClass)
        # Create a single instance of l3Report as it holds thread-pool
        # for device connection. Don't create l3Report multiple times 
        self.l3Report = L3Report(self._conf, daoClass)
        self.deviceSku = DeviceSku()
Esempio n. 3
0
    def __init__(self, conf={}, daoClass=Dao):
        if any(conf) == False:
            self._conf = OpenClosProperty(appName=moduleName).getProperties()
        else:
            self._conf = conf

        self._dao = daoClass.getInstance()
Esempio n. 4
0
    def __init__(self, deviceId, conf={}, daoClass=Dao):
        if any(conf) == False:
            self._conf = OpenClosProperty(appName=moduleName).getProperties()
        else:
            self._conf = conf

        self.daoClass = daoClass
        self.pod = None
        self.deviceId = deviceId
        self.deviceConnectionHandle = None
        self.deviceSku = DeviceSku()
Esempio n. 5
0
    def __init__(self, conf = {}, daoClass = Dao):
        if any(conf) == False:
            self._conf = OpenClosProperty(appName = moduleName).getProperties()
        else:
            self._conf = conf

        self._dao = daoClass.getInstance()

        self._templateEnv = Environment(loader=PackageLoader(junosTemplatePackage, junosTemplateLocation))
        self._templateEnv.keep_trailing_newline = True
        self.isZtpStaged = util.isZtpStaged(self._conf)
        self.deviceSku = DeviceSku()
Esempio n. 6
0
    def __init__(self, conf={}, templateEnv=None, daoClass=Dao):
        if any(conf) == False:
            self.__conf = OpenClosProperty(appName=moduleName).getProperties()
        else:
            self.__conf = conf

        self._dao = daoClass.getInstance()

        if templateEnv is None:
            self.templateEnv = Environment(
                loader=PackageLoader('jnpr.openclos', ztpTemplateLocation))
            self.templateEnv.lstrip_blocks = True
            self.templateEnv.trim_blocks = True
Esempio n. 7
0
    def __init__(self, conf={}):
        if conf is None or any(conf) == False:
            self.__conf = OpenClosProperty(appName=moduleName).getProperties()
        else:
            self.__conf = conf

        # default value
        self.target = DEFAULT_HOST
        self.port = DEFAULT_PORT

        # validate required parameter
        if 'snmpTrap' in self.__conf and 'openclos_trap_group' in self.__conf[
                'snmpTrap'] and 'target' in self.__conf['snmpTrap'][
                    'openclos_trap_group']:
            self.target = self.__conf['snmpTrap']['openclos_trap_group'][
                'target']
        else:
            logger.info(
                "snmpTrap:openclos_trap_group:target is missing from configuration. using %s"
                % (self.target))

        if 'snmpTrap' in self.__conf and 'openclos_trap_group' in self.__conf[
                'snmpTrap'] and 'port' in self.__conf['snmpTrap'][
                    'openclos_trap_group']:
            self.port = int(
                self.__conf['snmpTrap']['openclos_trap_group']['port'])
        else:
            logger.info(
                "snmpTrap:openclos_trap_group:port is missing from configuration. using %d"
                % (self.port))

        if 'snmpTrap' in self.__conf and 'threadCount' in self.__conf[
                'snmpTrap']:
            self.executor = concurrent.futures.ThreadPoolExecutor(
                max_workers=self.__conf['snmpTrap']['threadCount'])
        else:
            self.executor = concurrent.futures.ThreadPoolExecutor(
                max_workers=DEFAULT_MAX_THREADS)

        # event to stop from sleep
        self.stopEvent = Event()

        self.twoStageConfigurationCallback = util.getTwoStageConfigurationCallback(
            self.__conf)
Esempio n. 8
0
class RestServer():
    def __init__(self, conf = {}, daoClass = Dao):
        if any(conf) == False:
            self._openclosProperty = OpenClosProperty(appName = moduleName)
            self._conf = self._openclosProperty.getProperties()

            global webServerRoot
            webServerRoot = self._conf['outputDir']
        else:
            self._conf = conf
        
        self.__daoClass = daoClass
        self.__dao = daoClass.getInstance()
        self.openclosDbSessionPlugin = OpenclosDbSessionPlugin(daoClass)
        
        if 'httpServer' in self._conf and 'ipAddr' in self._conf['httpServer'] and self._conf['httpServer']['ipAddr'] is not None:
            self.host = self._conf['httpServer']['ipAddr']
        else:
            self.host = 'localhost'

        if 'httpServer' in self._conf and 'port' in self._conf['httpServer']:
            self.port = self._conf['httpServer']['port']
        else:
            self.port = 8080
        self.baseUrl = 'http://%s:%d' % (self.host, self.port)

        self.report = ResourceAllocationReport(self._conf, daoClass)
        # Create a single instance of l2Report as it holds thread-pool
        # for device connection. Don't create l2Report multiple times 
        self.l2Report = L2Report(self._conf, daoClass)
        # Create a single instance of l3Report as it holds thread-pool
        # for device connection. Don't create l3Report multiple times 
        self.l3Report = L3Report(self._conf, daoClass)
        self.deviceSku = DeviceSku()
        
    def initRest(self):
        self.addRoutes(self.baseUrl)
        self.app = bottle.app()
        self.app.install(loggingPlugin)
        self.app.install(self.openclosDbSessionPlugin)
        logger.info('RestServer initRest() done')

    def _reset(self):
        """
        Resets the state of the rest server and application
        Used for Test only
        """
        self.app.uninstall(loggingPlugin)
        self.app.uninstall(OpenclosDbSessionPlugin)


    def start(self):
        logger.info('REST server starting at %s:%d' % (self.host, self.port))
        debugRest = False
        if logger.isEnabledFor(logging.DEBUG):
            debugRest = True

        if self._openclosProperty.isSqliteUsed():
            bottle.run(self.app, host=self.host, port=self.port, debug=debugRest)
        else:
            bottle.run(self.app, host=self.host, port=self.port, debug=debugRest, server='paste')


    @staticmethod
    @error(400)
    def error400(error):
        bottle.response.headers['Content-Type'] = 'application/json'
        if error.exception is not None:
            return json.dumps({'errorCode': error.exception.code , 'errorMessage' : error.exception.message})
        else:
            return json.dumps({'errorCode': 0, 'errorMessage' : 'A generic error occurred'})
        
    @staticmethod
    @error(404)
    def error404(error):
        bottle.response.headers['Content-Type'] = 'application/json'
        if error.exception is not None:
            return json.dumps({'errorCode': error.exception.code , 'errorMessage' : error.exception.message})
        else:
            return json.dumps({'errorCode': 0, 'errorMessage' : 'A generic error occurred'})
        
    def addRoutes(self, baseUrl):
        self.indexLinks = []

        # GET APIs
        bottle.route('/', 'GET', self.getIndex)
        bottle.route('/openclos', 'GET', self.getIndex)
        bottle.route('/openclos/conf', 'GET', self.getOpenClosConfigParams)
        bottle.route('/openclos/pods', 'GET', self.getPods)
        bottle.route('/openclos/images/<junosImageName>', 'GET', self.getJunosImage)
        bottle.route('/openclos/pods/<podId>', 'GET', self.getPod)
        bottle.route('/openclos/pods/<podId>/cabling-plan', 'GET', self.getCablingPlan)
        bottle.route('/openclos/pods/<podId>/ztp-configuration','GET', self.getZtpConfig)
        bottle.route('/openclos/pods/<podId>/device-configuration', 'GET', self.getDeviceConfigsInZip)
        bottle.route('/openclos/pods/<podId>/leaf-generic-configurations/<deviceModel>', 'GET', self.getLeafGenericConfiguration)
        bottle.route('/openclos/pods/<podId>/l2-report', 'GET', self.getL2Report)
        bottle.route('/openclos/pods/<podId>/l3-report', 'GET', self.getL3Report)
        bottle.route('/openclos/pods/<podId>/devices', 'GET', self.getDevices)
        bottle.route('/openclos/pods/<podId>/devices/<deviceId>', 'GET', self.getDevice)
        bottle.route('/openclos/pods/<podId>/devices/<deviceId>/config', 'GET', self.getDeviceConfig)

        # POST/PUT APIs
        bottle.route('/openclos/pods', 'POST', self.createPod)
        bottle.route('/openclos/pods/<podId>/cabling-plan', 'PUT', self.createCablingPlan)
        bottle.route('/openclos/pods/<podId>/device-configuration', 'PUT', self.createDeviceConfiguration)
        bottle.route('/openclos/pods/<podId>/ztp-configuration', 'PUT', self.createZtpConfiguration)
        bottle.route('/openclos/pods/<podId>', 'PUT', self.reconfigPod)
        bottle.route('/openclos/conf/', 'PUT', self.setOpenClosConfigParams)

        # DELETE APIs
        bottle.route('/openclos/pods/<podId>', 'DELETE', self.deletePod)

        self.createLinkForConfigs()

    def createLinkForConfigs(self):
        # index page should show all top level URLs
        # users whould be able to drill down through navigation
        self.indexLinks.append(ResourceLink(self.baseUrl, '/openclos/pods'))
        self.indexLinks.append(ResourceLink(self.baseUrl, '/openclos/conf'))
    
    def getIndex(self, dbSession=None):
        if 'openclos' not in bottle.request.url:
            bottle.redirect(str(bottle.request.url).translate(None, ',') + 'openclos')

        jsonLinks = []
        for link in self.indexLinks:
            jsonLinks.append({'link': link.toDict()})

        jsonBody = \
            {'href': str(bottle.request.url).translate(None, ','),
             'links': jsonLinks
             }

        return jsonBody
    
    def getPods(self, dbSession):
        
        url = str(bottle.request.url).translate(None, ',')
        podsData = {}
        listOfIpFbarics = []
        pods = self.report.getPods(dbSession)
        logger.debug("count of pods: %d", len(pods))
        if not pods :   
            logger.debug("There are no pods in the system ")
        
        for i in range(len(pods)):
            pod = {}
            pod['uri'] = url +'/'+ pods[i]['id']
            pod['id'] = pods[i]['id']
            pod['name'] = pods[i]['name']
            pod['spineDeviceType'] = pods[i]['spineDeviceType']
            pod['spineCount'] = pods[i]['spineCount']
            pod['leafSettings'] = pods[i]['leafSettings']
            pod['leafCount'] = pods[i]['leafCount']
            pod['devicePassword'] = pods[i]['devicePassword']
            listOfIpFbarics.append(pod)
        podsData['pod'] =  listOfIpFbarics
        podsData['total'] = len(listOfIpFbarics)
        podsData['uri'] = url 
        return {'pods' : podsData}
    
    def getPodFieldListToCopy(self):
        return ['id', 'name', 'description', 'spineAS', 'spineDeviceType', 'spineCount', 'leafAS', 'leafCount', 
                'leafUplinkcountMustBeUp', 'loopbackPrefix', 'vlanPrefix', 'interConnectPrefix', 'managementPrefix', 
                'outOfBandAddressList', 'outOfBandGateway', 'topologyType', 'spineJunosImage', 'hostOrVmCountPerLeaf']
    
    def getPod(self, dbSession, podId, requestUrl = None):
        if requestUrl is None:
            requestUrl = str(bottle.request.url).translate(None, ',')
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            outputDict = {} 
            devices = pod.devices
            for field in self.getPodFieldListToCopy():
                outputDict[field] = pod.__dict__.get(field)
            
            '''
            outputDict['id'] = pod.id
            outputDict['name'] = pod.name
            outputDict['description'] = pod.description 
            outputDict['spineAS'] = pod.spineAS
            outputDict['spineDeviceType'] = pod.spineDeviceType
            outputDict['spineCount'] = pod.spineCount
            outputDict['leafAS'] = pod.leafAS
            outputDict['leafCount'] = pod.leafCount
            outputDict['loopbackPrefix'] = pod.loopbackPrefix 
            outputDict['vlanPrefix'] = pod.vlanPrefix
            outputDict['interConnectPrefix'] = pod.interConnectPrefix 
            outputDict['managementPrefix'] = pod.managementPrefix
            outputDict['outOfBandAddressList'] = pod.outOfBandAddressList
            outputDict['outOfBandGateway'] = pod.outOfBandGateway 
            outputDict['topologyType'] = pod.topologyType
            outputDict['spineJunosImage'] = pod.spineJunosImage
            outputDict['hostOrVmCountPerLeaf'] = pod.hostOrVmCountPerLeaf
            '''
            outputDict['leafSettings'] = []
            for leafSetting in pod.leafSettings:
                outputDict['leafSettings'].append({'deviceType': leafSetting.deviceFamily, 'junosImage': leafSetting.junosImage})

            outputDict['devicePassword'] = pod.getCleartextPassword()
            outputDict['uri'] = requestUrl
            outputDict['devices'] = {'uri': requestUrl + '/devices', 'total':len(devices)}
            outputDict['cablingPlan'] = {'uri': requestUrl + '/cabling-plan'}
            outputDict['deviceConfiguration'] = {'uri': requestUrl + '/device-configuration'}
            outputDict['ztpConfiguration'] = {'uri': requestUrl + '/ztp-configuration'}
            outputDict['l2Report'] = {'uri': requestUrl + '/l2-report'}
            outputDict['l3Report'] = {'uri': requestUrl + '/l3-report'}
            
            logger.debug('getPod: %s' % (podId))
     
            return {'pod': outputDict}

        else:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))
    
    def getCablingPlan(self, dbSession, podId):
        
        header =  bottle.request.get_header('Accept')
        logger.debug('Accept header before processing: %s' % (header))
        # hack to remove comma character, must be a bug on Bottle
        header = header.translate(None, ',')
        logger.debug('Accept header after processing: %s' % (header))

        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            logger.debug('Pod name: %s' % (pod.name))
            
            if header == 'application/json':
                cablingPlan = pod.cablingPlan
                if cablingPlan is not None and cablingPlan.json is not None:
                    logger.debug('CablingPlan found in DB')
                    return cablingPlan.json
                else:
                    raise bottle.HTTPError(404, exception = CablingPlanNotFound(pod.id))
                    
            else:
                podFolder = pod.id + '-' + pod.name
                fileName = os.path.join(podFolder, 'cablingPlan.dot')
                logger.debug('webServerRoot: %s, fileName: %s, exists: %s' % (webServerRoot, fileName, os.path.exists(os.path.join(webServerRoot, fileName))))
                logger.debug('Cabling file name: %s' % (fileName))                
                cablingPlan = bottle.static_file(fileName, root=webServerRoot)

                if isinstance(cablingPlan, bottle.HTTPError):
                    raise bottle.HTTPError(404, exception = CablingPlanNotFound(podFolder))
                return cablingPlan
        
        else:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))

    def getLeafGenericConfiguration(self, dbSession, podId, deviceModel):
        pod = self.report.getPod(dbSession, podId)
        if pod is None:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))
        
        logger.debug('Pod name: %s, id: %s' % (pod.name, podId))
        
        leafSetting = self.__dao.getLeafSetting(dbSession, podId, deviceModel)
        if leafSetting is None or leafSetting.config is None:
            raise bottle.HTTPError(404, exception = DeviceConfigurationNotFound("Pod exists but no leaf generic config found, probably configuration \
                was not created. deviceModel: %s, pod name: '%s', id: '%s'" % (deviceModel, pod.name, podId)))
        
        bottle.response.headers['Content-Type'] = 'application/json'
        return leafSetting.config

    def getDeviceConfigsInZip(self, dbSession, podId):
        pod = self.report.getPod(dbSession, podId)
        if pod is None:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))
        
        logger.debug('Pod name: %s' % (pod.name))

        zippedConfigFiles = self.createZipArchive(pod)
        if zippedConfigFiles is not None:
            bottle.response.headers['Content-Type'] = 'application/zip'
            return zippedConfigFiles
        else:
            raise bottle.HTTPError(404, exception = DeviceConfigurationNotFound("Pod exists but no configs for devices.'%s " % (pod.name)))

    def createZipArchive(self, pod):

        buff = StringIO.StringIO()
        zipArchive = zipfile.ZipFile(buff, mode='w')
        for device in pod.devices:
            fileName = device.id + '__' + device.name + '.conf'
            if device.config is not None:
                zipArchive.writestr(fileName, device.config.config)
                
        if pod.leafSettings is not None:
            for leafSetting in pod.leafSettings:
                if leafSetting.config is not None:
                    zipArchive.writestr(leafSetting.deviceFamily + '.conf', leafSetting.config)
        
        zipArchive.close()
        logger.debug('zip file content:\n' + str(zipArchive.namelist()))
        return buff.getvalue()

    def copyAdditionalDeviceFields(self, dict, device):
        '''
        Hook to enhance Device object
        '''
    def getDevices(self, dbSession, podId):
        
        devices = {}
        listOfDevices = []
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            for device in pod.devices:
                outputDict = {}
                outputDict['id'] = device.id
                outputDict['name'] = device.name
                outputDict['role'] = device.role
                outputDict['family'] = device.family
                outputDict['macAddress'] = device.macAddress
                outputDict['managementIp'] = device.managementIp
                outputDict['serialNumber'] = device.serialNumber
                outputDict['deployStatus'] = device.deployStatus
                outputDict['configStatus'] = device.configStatus
                outputDict['l2Status'] = device.l2Status
                outputDict['l3Status'] = device.l3Status
                outputDict['uri'] = str(bottle.request.url).translate(None, ',') + '/' +device.id
                self.copyAdditionalDeviceFields(outputDict, device)

                listOfDevices.append(outputDict)
            devices['device'] = listOfDevices
            devices['uri'] = str(bottle.request.url).translate(None, ',')
            devices['total'] = len(pod.devices)
            return {'devices' : devices}
        else:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))
        
    def getDevice(self, dbSession, podId, deviceId):
        
        device = self.isDeviceExists(dbSession, podId, deviceId)
        #podUri is constructed from url
        url = str(bottle.request.url).translate(None, ',')
        uri = url.split("/")
        uri.pop()
        uri.pop()
        ipFbaricUri = "/".join(uri)
               
        if device is not None:
            outputDict = {}
            outputDict['id'] = device.id
            outputDict['name'] = device.name
            outputDict['role'] = device.role
            outputDict['family'] = device.family
            outputDict['username'] = device.username
            outputDict['password'] = device.getCleartextPassword()
            outputDict['macAddress'] = device.macAddress
            outputDict['managementIp'] = device.managementIp
            outputDict['asn'] = device.asn
            outputDict['configStatus'] = device.configStatus
            outputDict['configStatusReason'] = device.configStatusReason
            outputDict['l2Status'] = device.l2Status
            outputDict['l2StatusReason'] = device.l2StatusReason
            outputDict['l3Status'] = device.l3Status
            outputDict['l3StatusReason'] = device.l3StatusReason
            outputDict['serialNumber'] = device.serialNumber
            outputDict['deployStatus'] = device.deployStatus
            outputDict['uri'] = str(bottle.request.url).translate(None, ',')
            outputDict['pod'] = {'uri': ipFbaricUri }
            outputDict['config'] = {'uri': str(bottle.request.url).translate(None, ',') + '/config' }
            self.copyAdditionalDeviceFields(outputDict, device)
            
            return {'device': outputDict}
        else:
            raise bottle.HTTPError(404, exception = DeviceNotFound("No device found with podId: '%s', deviceId: '%s'" % (podId, deviceId)))
        
         
    def getDeviceConfig(self, dbSession, podId, deviceId):
        
        device = self.isDeviceExists(dbSession, podId, deviceId)
        if device is None:
            raise bottle.HTTPError(404, exception = DeviceNotFound("No device found with podId: '%s', deviceId: '%s'" % (podId, deviceId)))

        config = device.config
        if config is None:
            raise bottle.HTTPError(404, exception = DeviceConfigurationNotFound("Device exists but no config found, probably fabric script is not ran. podId: '%s', deviceId: '%s'" % (podId, deviceId)))
        
        bottle.response.headers['Content-Type'] = 'application/json'
        return config.config

    
    def getZtpConfig(self, dbSession, podId):
        
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            logger.debug('pod name: %s' % (pod.name))
            
            podFolder = pod.id + '-' + pod.name
            fileName = os.path.join(podFolder, "dhcpd.conf")
            logger.debug('webServerRoot: %s, fileName: %s, exists: %s' % (webServerRoot, fileName, os.path.exists(os.path.join(webServerRoot, fileName))))         
            ztpConf = bottle.static_file(fileName, root=webServerRoot)
            if isinstance(ztpConf, bottle.HTTPError):
                raise bottle.HTTPError(404, exception = DeviceConfigurationNotFound("Pod exists but no ztp Config found. Pod name: '%s " % (pod.name)))
            return ztpConf
        else:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))
    

    def isDeviceExists(self, dbSession, podId, deviceId):
        try:
            device = dbSession.query(Device).join(Pod).filter(Device.id == deviceId).filter(Pod.id == podId).one()
            return device
        except (exc.NoResultFound):
            raise bottle.HTTPError(404, exception = DeviceNotFound("No device found with podId: '%s', deviceId: '%s'" % (podId, deviceId)))

    def getJunosImage(self, dbSession, junosImageName):
           
        fileName = os.path.join(junosImageRoot, junosImageName)
        logger.debug('junosImageRoot: %s, image: %s, exists: %s' % (junosImageRoot, junosImageName, os.path.exists(fileName)))

        config = bottle.static_file(junosImageName, root=junosImageRoot)
        if isinstance(config, bottle.HTTPError):
            raise bottle.HTTPError(404, exception = ImageNotFound("Junos image file not found. name: '%s'" % (junosImageName)))
        return config
    
    def getOpenClosConfigParams(self, dbSession):
        supportedDevices = []
        
        for deviceFamily, value in self.deviceSku.skuDetail.iteritems():
            for role, ports in value.iteritems():
                uplinks = ports.get('uplinkPorts')
                downlinks = ports.get('downlinkPorts')
                deviceDetail = {'family': deviceFamily, 'role': role, 'uplinkPorts': uplinks, 'downlinkPorts': downlinks}                
                supportedDevices.append(deviceDetail)
            
        confValues = {}
        confValues.update({'dbUrl': self._conf['dbUrl']})
        confValues.update({'supportedDevices' :  supportedDevices})
        confValues.update({'dotColors': self._conf['DOT']['colors'] })
        confValues.update({'httpServer' : self._conf['httpServer']})
        confValues.update({'snmpTrap' : self._conf['snmpTrap']})

        return {'OpenClosConf' : confValues }
                    
    def createPod(self, dbSession):  
        if bottle.request.json is None:
            raise bottle.HTTPError(400, exception = InvalidRequest("No json in request object"))
        else:
            pod = bottle.request.json.get('pod')
            if pod is None:
                raise bottle.HTTPError(400, exception = InvalidRequest("POST body cannot be empty"))

        l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
        podDevices = self.getDevDictFromDict(pod)
        pod = self.getPodFromDict(pod)
        podName = pod.pop('name')
        try:
            createdPod =  l3ClosMediation.createPod(podName, pod, podDevices)
            url = str(bottle.request.url).translate(None, ',') + '/' + createdPod.id
            pod = self.getPod(dbSession, createdPod.id, url)
        except Exception as e:
            logger.debug('StackTrace: %s' % (traceback.format_exc()))
            raise bottle.HTTPError(400, exception = e)
        bottle.response.set_header('Location', url)
        bottle.response.status = 201

        return pod
        
    def createCablingPlan(self, dbSession, podId):
        try:
            l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
            if l3ClosMediation.createCablingPlan(podId) is True:
                return bottle.HTTPResponse(status=200)
        except PodNotFound as e:
            raise bottle.HTTPError(404, exception = e)
        except Exception as e:
            raise bottle.HTTPError(500, exception = e)

    def createDeviceConfiguration(self, dbSession, podId):
        try:
            l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
            if l3ClosMediation.createDeviceConfig(podId) is True:
                return bottle.HTTPResponse(status=200)
        except PodNotFound as e:
            raise bottle.HTTPError(404, exception = e)
        except Exception as e:
            raise bottle.HTTPError(500, exception = e)
            
    def createZtpConfiguration(self, dbSession, podId):
        try:
            ZtpServer().createPodSpecificDhcpConfFile(dbSession, podId)
        except PodNotFound as e:
            raise bottle.HTTPError(404, exception = e)
        except Exception as e:
            raise bottle.HTTPError(500, exception = e)

    def reconfigPod(self, dbSession, podId):
        if bottle.request.json is None:
            raise bottle.HTTPError(400, exception = InvalidRequest("No json in request object"))
        else:
            inPod = bottle.request.json.get('pod')
            if inPod is None:
                raise bottle.HTTPError(400, exception = InvalidRequest("POST body cannot be empty"))

        l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
        pod = self.getPodFromDict(inPod)
        #pod['id'] = podId
        #pod['uri'] = str(bottle.request.url).translate(None, ',')
        podDevices = self.getDevDictFromDict(inPod)
        # Pass the pod and podDevices dictionaries to config/update API, then return
        try:
            updatedPod = l3ClosMediation.updatePod(podId, pod, podDevices)
            url = str(bottle.request.url).translate(None, ',') + '/' + updatedPod.id
            return self.getPod(dbSession, podId, url)
        except Exception as e:
            raise bottle.HTTPError(400, exception = e)
    
    def setOpenClosConfigParams(self):
        return bottle.HTTPResponse(status=200)
    
    def deletePod(self, dbSession, podId):
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            self.__dao.deleteObject(dbSession, pod)
            util.deleteOutFolder(self._conf, pod)
            logger.debug("Pod with id: %s deleted" % (podId))
        else:
            raise bottle.HTTPError(404, exception = PodNotFound(podId))
        return bottle.HTTPResponse(status=204)

    def getPodFromDict(self, podDict):
        pod = {}
        '''
        # Need to revisit later on to make thing works as below.
        podDict.pop('devices')
        pod = Pod(**inPod)
        '''
        if podDict is None:
            raise bottle.HTTPError(400, exception = InvalidRequest("Invalid value in request body."))
        
        for field in self.getPodFieldListToCopy():
            pod[field] = podDict.get(field)
        
        '''
        pod['name'] = podDict.get('name')
        pod['description'] = podDict.get('description')
        pod['spineAS'] = podDict.get('spineAS')
        pod['spineDeviceType'] = podDict.get('spineDeviceType')
        pod['spineCount'] = podDict.get('spineCount')
        pod['leafAS'] = podDict.get('leafAS')
        pod['leafCount'] = podDict.get('leafCount')
        pod['leafUplinkcountMustBeUp'] = podDict.get('leafUplinkcountMustBeUp')
        pod['loopbackPrefix'] = podDict.get('loopbackPrefix')
        pod['vlanPrefix'] = podDict.get('vlanPrefix')
        pod['interConnectPrefix'] = podDict.get('interConnectPrefix')
        pod['managementPrefix'] = podDict.get('managementPrefix')
        pod['outOfBandAddressList'] = podDict.get('outOfBandAddressList')
        pod['outOfBandGateway'] = podDict.get('outOfBandGateway')
        pod['topologyType'] = podDict.get('topologyType')
        pod['topologyType'] = podDict.get('topologyType')
        pod['spineJunosImage'] = podDict.get('spineJunosImage')
        pod['hostOrVmCountPerLeaf'] = podDict.get('hostOrVmCountPerLeaf')
        '''

        pod['leafSettings'] = podDict.get('leafSettings')
        pod['devicePassword'] = podDict.get('devicePassword')

        return pod


    def getDevDictFromDict(self, podDict):
        if podDict is not None:
            devices = podDict.get('devices')
        else:
            raise bottle.HTTPError(400, exception = InvalidRequest("Invalid value in request body."))

        podDevices = {}
        spines = []
        leaves = []
        for device in devices:
            temp = {}
            temp['name'] = device.get('name')
            temp['macAddress'] = device.get('macAddress')
            temp['role'] = device.get('role')
            temp['username'] = device.get('username')
            temp['password'] = device.get('password')
            temp['family'] = device.get('family')
            temp['serialNumber'] = device.get('serialNumber')
            temp['deployStatus'] = device.get('deployStatus')
            if temp['role'] == 'spine':
                spines.append(temp)
            elif temp['role'] == 'leaf':
                leaves.append(temp)
            else:
                raise bottle.HTTPError(400, exception = InvalidRequest("Unexpected role value in device inventory list"))
            podDevices['spines'] = spines
            podDevices['leafs'] = leaves

        return podDevices

    def getL2Report(self, dbSession, podId):
        try:
            cached = bottle.request.query.get('cached', '1')
            if cached == '1':
                cachedData = True
            else:
                cachedData = False
            bottle.response.headers['Content-Type'] = 'application/json'
            return self.l2Report.generateReport(podId, cachedData)

        except Exception as e:
            raise bottle.HTTPError(404, exception = PodNotFound(podId, e))
    
    def getL3Report(self, dbSession, podId):
        try:
            cached = bottle.request.query.get('cached', '1')
            if cached == '1':
                cachedData = True
            else:
                cachedData = False
            bottle.response.headers['Content-Type'] = 'application/json'
            return self.l3Report.generateReport(podId, cachedData)

        except Exception as e:
            raise bottle.HTTPError(404, exception = PodNotFound(podId, e))
Esempio n. 9
0
 def _getDbUrl(self):
     from propLoader import OpenClosProperty
     return OpenClosProperty().getDbUrl()
Esempio n. 10
0
class L3ClosMediation():
    def __init__(self, conf = {}, daoClass = Dao):
        if any(conf) == False:
            self._conf = OpenClosProperty(appName = moduleName).getProperties()
        else:
            self._conf = conf

        self._dao = daoClass.getInstance()

        self._templateEnv = Environment(loader=PackageLoader(junosTemplatePackage, junosTemplateLocation))
        self._templateEnv.keep_trailing_newline = True
        self.isZtpStaged = util.isZtpStaged(self._conf)
        self.deviceSku = DeviceSku()

    def loadClosDefinition(self, closDefination = os.path.join(propertyFileLocation, 'closTemplate.yaml')):
        '''
        Loads clos definition from yaml file and creates pod object
        '''
        try:
            stream = open(closDefination, 'r')
            yamlStream = yaml.load(stream)
            
            return yamlStream['pods']
        except (OSError, IOError) as e:
            print "File error:", e
        except (yaml.scanner.ScannerError) as e:
            print "YAML error:", e
            stream.close()
        finally:
            pass
       
    def createPod(self, podName, podDict, inventoryDict = None):
        '''
        Create a new Pod
        :returns str: Pod identifier

        '''
        pod = Pod(podName, podDict)
        pod.validate()
        # inventory can come from either podDict or inventoryDict
        inventoryData = self._resolveInventory(podDict, inventoryDict)
        # check inventory contains correct number of devices
        self._validatePod(pod, podDict, inventoryData)

        with self._dao.getReadWriteSession() as session:
            self._dao.createObjects(session, [pod])
            # shortcut for updating in createPod
            # this use case is typical in script invocation but not in ND REST invocation
            self._updatePodData(session, pod, podDict, inventoryData)
            logger.info("Pod[id='%s', name='%s']: created" % (pod.id, pod.name)) 
            podId = pod.id
        
        #Hack sqlalchemy: access object is REQUIRED after commit as session has expire_on_commit=True.
        with self._dao.getReadSession() as session:
            pod = self._dao.getObjectById(session, Pod, podId)
        return pod
    
    def _createSpineAndIfds(self, session, pod, spines):
        devices = []
        interfaces = []
        for spine in spines:
            username = spine.get('username')    #default is 'root' set on DB
            password = spine.get('password')
            macAddress = spine.get('macAddress')
            deployStatus = spine.get('deployStatus')    #default is 'provision' set on DB
            serialNumber = spine.get('serialNumber')
            device = Device(spine['name'], pod.spineDeviceType, username, password, 'spine', macAddress, None, pod, deployStatus, serialNumber)
            devices.append(device)
            
            portNames = self.deviceSku.getPortNamesForDeviceFamily(device.family, 'spine')
            for name in portNames['downlinkPorts']:     # spine does not have any uplink/downlink marked, it is just ports
                ifd = InterfaceDefinition(name, device, 'downlink')
                interfaces.append(ifd)
        self._dao.createObjects(session, devices)
        self._dao.createObjects(session, interfaces)
        
    def _createLeafAndIfds(self, session, pod, leaves):
        devices = []
        for leaf in leaves:
            username = leaf.get('username')
            password = leaf.get('password') #default is Pod level pass, set on constructor
            macAddress = leaf.get('macAddress')
            family = leaf.get('family') #default is 'unknown' set on DB
            deployStatus = leaf.get('deployStatus') #default is 'provision' set on DB
            serialNumber = leaf.get('serialNumber')
            device = Device(leaf['name'], family, username, password, 'leaf', macAddress, None, pod, deployStatus, serialNumber)
            devices.append(device)
            
        self._dao.createObjects(session, devices)
        for device in devices:
            self._createLeafIfds(session, pod, device)
        
    def _createLeafIfds(self, session, pod, device):
        interfaces = []

        if device.family is None or device.family == 'unknown':
            # temporary uplink ports, names will get fixed after 2-stage ztp
            for i in xrange(0, pod.spineCount):
                interfaces.append(InterfaceDefinition('uplink-' + str(i), device, 'uplink'))

        else:
            portNames = self.deviceSku.getPortNamesForDeviceFamily(device.family, 'leaf')
            interfaceCount = 0
            for name in portNames['uplinkPorts']:   # all uplink IFDs towards spine
                interfaces.append(InterfaceDefinition(name, device, 'uplink'))
                interfaceCount += 1
            
            # Hack plugNPlay-mixedLeaf: Create additional uplinks when spine count is more than available uplink ports
            # example: spine count=5, device=ex4300-24p
            while interfaceCount < pod.spineCount:
                interfaces.append(InterfaceDefinition('uplink-' + str(interfaceCount), device, 'uplink'))
                interfaceCount += 1
                        
        self._dao.createObjects(session, interfaces)
        self._createLeafDownlinkIfds(session, pod, device)
    
    def _createLeafDownlinkIfds(self, session, pod, device):
        '''
        leaf access/downlink ports are not used so far, no need to create them    
        '''

    def _deployInventory(self, pod, inventory, role):
        # return a list of devices whose family has changed
        devicesFamilyChanged = []
        for inv in inventory:
            for device in pod.devices:
                # find match by role/id/name
                if device.role == role and (device.id == inv.get('id') or device.name == inv['name']):
                    newDeployStatus = inv.get('deployStatus')
                    if newDeployStatus is None:
                        newDeployStatus = 'provision'
                        
                    if device.deployStatus == newDeployStatus:
                        logger.debug("Pod[id='%s', name='%s']: %s device '%s' deploy status unchanged" % (pod.id, pod.name, device.role, device.name))
                    elif device.deployStatus == 'deploy' and newDeployStatus == 'provision':
                        logger.debug("Pod[id='%s', name='%s']: %s device '%s' provisioned" % (pod.id, pod.name, device.role, device.name))
                    elif device.deployStatus == 'provision' and newDeployStatus == 'deploy':
                        logger.debug("Pod[id='%s', name='%s']: %s device '%s' deployed" % (pod.id, pod.name, device.role, device.name))
                    
                    # we need this list to fix interface names
                    if device.family != inv.get('family'):
                        logger.debug("Pod[id='%s', name='%s']: %s device '%s' family changed from %s to %s" % (pod.id, pod.name, device.role, device.name, device.family, inv.get('family')))
                        devicesFamilyChanged.append(device)
                    
                    # update all fields 
                    device.update(inv['name'], inv.get('family'), inv.get('username'), inv.get('password'), inv.get('macAddress'), newDeployStatus, inv.get('serialNumber'))
                    
                    # In case of ztpStage is true and this is a leaf and deployStatus changes to provision:
                    # The device is going to be removed and the management ip will eventually expire and go back to DHCP pool.
                    # We need to reset managementIp field for this device to None because soon another leaf will probably get the 
                    # same management ip address from DHCP. We want avoid the getDevices REST API returns 
                    # 2 devices having the same management ip.
                    # Note we don't do this when ztpStaged is false because all the management ip are pre-allocated 
                    # in that case and will never change.
                    if role == 'leaf' and self.isZtpStaged and device.deployStatus == 'provision':
                        device.managementIp = None

        return devicesFamilyChanged
        
    def _resolveInventory(self, podDict, inventoryDict):
        if podDict is None:
            raise InvalidRequest("podDict cannot be None")
            
        # typical use case for ND REST invocation is to provide an non-empty inventoryDict
        # typical use case for script invocation is to provide an non-empty podDict['inventory']
        inventoryData = None
        if inventoryDict is not None:
            inventoryData = inventoryDict
        elif 'inventory' in podDict and podDict['inventory'] is not None:
            json_inventory = open(os.path.join(propertyFileLocation, podDict['inventory']))
            inventoryData = json.load(json_inventory)
            json_inventory.close()

        return inventoryData

    def _validateAttribute(self, pod, attr, dct):
        if dct.get(attr) is None:
            raise MissingMandatoryAttribute("Pod[id='%s', name='%s']: device '%s' attribute '%s' cannot be None" % (pod.id, pod.name, dct.get('name'), attr))
    
    def _validateLoopbackPrefix(self, pod, podDict, inventoryData):
        inventoryDeviceCount = len(inventoryData['spines']) + len(inventoryData['leafs'])
        lo0Block = IPNetwork(podDict['loopbackPrefix'])
        lo0Ips = list(lo0Block.iter_hosts())
        availableIps = len(lo0Ips)
        cidr = 32 - int(math.ceil(math.log(inventoryDeviceCount, 2)))
        if availableIps < inventoryDeviceCount:
            raise InsufficientLoopbackIp("Pod[id='%s', name='%s']: loopbackPrefix minimum required: %s/%d" % (pod.id, pod.name, lo0Block.ip, cidr))

    def _validateVlanPrefix(self, pod, podDict, inventoryData):
        vlanBlock = IPNetwork(podDict['vlanPrefix'])
        numOfHostIpsPerSwitch = podDict['hostOrVmCountPerLeaf']
        numOfSubnets = len(inventoryData['leafs'])
        numOfIps = (numOfSubnets * (numOfHostIpsPerSwitch + 2)) # +2 for network and broadcast
        numOfBits = int(math.ceil(math.log(numOfIps, 2))) 
        cidr = 32 - numOfBits
        if vlanBlock.prefixlen > cidr:
            raise InsufficientVlanIp("Pod[id='%s', name='%s']: vlanPrefix minimum required: %s/%d" % (pod.id, pod.name, vlanBlock.ip, cidr))
    
    def _validateInterConnectPrefix(self, pod, podDict, inventoryData):
        interConnectBlock = IPNetwork(podDict['interConnectPrefix'])
        numOfIpsPerInterconnect = 2
        numOfSubnets = len(inventoryData['spines']) * len(inventoryData['leafs'])
        # no need to add +2 for network and broadcast, as junos supports /31
        # TODO: it should be configurable and come from property file
        bitsPerSubnet = int(math.ceil(math.log(numOfIpsPerInterconnect, 2)))    # value is 1  
        cidrForEachSubnet = 32 - bitsPerSubnet  # value is 31 as junos supports /31

        numOfIps = (numOfSubnets * (numOfIpsPerInterconnect)) # no need to add +2 for network and broadcast
        numOfBits = int(math.ceil(math.log(numOfIps, 2))) 
        cidr = 32 - numOfBits
        if interConnectBlock.prefixlen > cidr:
            raise InsufficientInterconnectIp("Pod[id='%s', name='%s']: interConnectPrefix minimum required: %s/%d" % (pod.id, pod.name, interConnectBlock.ip, cidr))
    
    def _validateManagementPrefix(self, pod, podDict, inventoryData):
        inventoryDeviceCount = len(inventoryData['spines']) + len(inventoryData['leafs'])
        prefix = podDict.get('managementPrefix')
        startingIp = podDict.get('managementStartingIP')
        mask = podDict.get('managementMask')
        managementIps = util.getMgmtIps(prefix, startingIp, mask, inventoryDeviceCount)
        availableIps = len(managementIps)
        cidr = 32 - int(math.ceil(math.log(inventoryDeviceCount, 2)))
        if availableIps < inventoryDeviceCount:
            if startingIp is not None:
                firstIp = startingIp
            elif prefix is not None:
                firstIp = prefix.split('/')[0]
            raise InsufficientManagementIp("Pod[id='%s', name='%s']: managementPrefix minimum required: %s/%d" % (pod.id, pod.name, firstIp, cidr))
    
    def _validatePod(self, pod, podDict, inventoryData):
        if inventoryData is None:
            raise InvalidRequest("Pod[id='%s', name='%s']: inventory cannot be empty" % (pod.id, pod.name))
        
        # if following data changed we need to reallocate resource
        if pod.spineCount != podDict['spineCount'] or pod.leafCount != podDict['leafCount']:
            raise CapacityCannotChange("Pod[id='%s', name='%s']: capacity cannot be changed" % (pod.id, pod.name))

        for spine in inventoryData['spines']:
            self._validateAttribute(pod, 'name', spine)
            #self._validateAttribute(pod, 'role', spine)
            #self._validateAttribute(pod, 'family', spine)
            #self._validateAttribute(pod, 'deployStatus', spine)
            
        for leaf in inventoryData['leafs']:
            self._validateAttribute(pod, 'name', leaf)
            #self._validateAttribute(pod, 'role', leaf)
            #self._validateAttribute(pod, 'family', leaf)
            #self._validateAttribute(pod, 'deployStatus', leaf)
                
        # inventory should contain exact same number of devices as capacity
        expectedDeviceCount = int(podDict['spineCount']) + int(podDict['leafCount'])
        inventoryDeviceCount = len(inventoryData['spines']) + len(inventoryData['leafs'])
        if expectedDeviceCount != inventoryDeviceCount:
            raise CapacityMismatch("Pod[id='%s', name='%s']: inventory device count %d does not match capacity %d" % (pod.id, pod.name, inventoryDeviceCount, expectedDeviceCount))

        # validate loopbackPrefix is big enough
        self._validateLoopbackPrefix(pod, podDict, inventoryData)

        # validate vlanPrefix is big enough
        self._validateVlanPrefix(pod, podDict, inventoryData)
        
        # validate interConnectPrefix is big enough
        self._validateInterConnectPrefix(pod, podDict, inventoryData)
        
        # validate managementPrefix is big enough
        self._validateManagementPrefix(pod, podDict, inventoryData)
                
        
    def _diffInventory(self, session, pod, inventoryData):
        # Compare new inventory that user provides against old inventory that we stored in the database.
        inventoryChanged = True
        
        # user provides an inventory. now check the inventory we already have in database
        if pod.inventoryData is not None:
            # restored to cleartext JSON format
            inventoryDataInDb = json.loads(zlib.decompress(base64.b64decode(pod.inventoryData)))
            if inventoryData == inventoryDataInDb:
                inventoryChanged = False
            
        if inventoryChanged == True:
            logger.debug("Pod[id='%s', name='%s']: inventory changed" % (pod.id, pod.name))
            # save the new inventory to database
            pod.inventoryData = base64.b64encode(zlib.compress(json.dumps(inventoryData)))

            # deploy the new spines and undeploy deleted spines
            self._deployInventory(pod, inventoryData['spines'], 'spine')
            
            # deploy the new leaves and undeploy deleted leaves
            leavesFamilyChanged = self._deployInventory(pod, inventoryData['leafs'], 'leaf')

            # fix interface names if any
            self.fixInterfaceNames(session, pod, leavesFamilyChanged)
        else:
            logger.debug("Pod[id='%s', name='%s']: inventory not changed" % (pod.id, pod.name))

    def _needToRebuild(self, pod, podDict):
        if pod.spineDeviceType != podDict.get('spineDeviceType') or \
           pod.spineCount != podDict.get('spineCount') or \
           pod.leafCount != podDict.get('leafCount') or \
           pod.interConnectPrefix != podDict.get('interConnectPrefix') or \
           pod.vlanPrefix != podDict.get('vlanPrefix') or \
           pod.loopbackPrefix != podDict.get('loopbackPrefix') or \
           pod.managementPrefix != podDict.get('managementPrefix'):
            return True
        else:
            return False
            
    def fixIfdIflName(self, ifd, name):
        if ifd is None:
            return []
        
        logger.debug("Fixing device: %s IFD port: %s -> %s" % (ifd.device.name, ifd.name, name))

        updateList = []
        newIFL = name + '.0'
        for ifl in ifd.layerAboves:
            logger.debug("Fixing device: %s IFL port: %s -> %s" % (ifd.device.name, ifl.name, newIFL))
            ifl.updateName(newIFL)
            updateList.append(ifl)

        # SQLAlchemy Hack: calling ifd.layerAboves causes premature session flush
        # which causes unique constrain with updateName() with sequence number 
        # so moving updateName down
        ifd.updateName(name)
        updateList.append(ifd)        

        return updateList

    def fixUplinkPorts(self, session, device):
        # we have 2 lists:
        # 1. all the uplink interfaces in the db for this leaf (number N)
        # 2. all the uplink interfaces based on this leaf's device family (number M)
        # the algorithm: 
        # for the N interfaces in list 1, replace the name with the name in list 2
        # if N <= M, we are done
        # if N > M, after we process first M interfaces, we replace the rest (N-M) interfaces with fake names 'uplink-'
        
        # list 1
        allocatedUplinkIfds = session.query(InterfaceDefinition).filter(InterfaceDefinition.device_id == device.id).\
            filter(InterfaceDefinition.role == 'uplink').order_by(InterfaceDefinition.sequenceNum).all()
            
        # list 2
        uplinkNamesBasedOnDeviceFamily = self.deviceSku.getPortNamesForDeviceFamily(device.family, 'leaf')['uplinkPorts']
        
        updateList = []
        # fake uplink port starts from 0
        listIndex = 0

        for allocatedIfd, uplinkNameBasedOnDeviceFamily in itertools.izip_longest(allocatedUplinkIfds, uplinkNamesBasedOnDeviceFamily):
            if uplinkNameBasedOnDeviceFamily:
                updateList += self.fixIfdIflName(allocatedIfd, uplinkNameBasedOnDeviceFamily)
            else:
                updateList += self.fixIfdIflName(allocatedIfd, 'uplink-' + str(listIndex))
            listIndex += 1
        
        #logger.debug('Number of uplink IFD + IFL for device %s fixed: %d' % (device.name, len(updateList)))
        self._dao.updateObjects(session, updateList)
        
    def fixInterfaceNames(self, session, pod, devices):
        for device in devices:
            # sanity check
            if device.role == 'leaf':
                self.fixUplinkPorts(session, device)
        
    def _updatePodData(self, session, pod, podDict, inventoryData):
        # if following data changed we need to reallocate resource
        if self._needToRebuild(pod, podDict) == True:
            logger.debug("Pod[id='%s', name='%s']: rebuilding required" % (pod.id, pod.name))
            if len(pod.devices) > 0:
                self._dao.deleteObjects(session, pod.devices)
                session.expire(pod)

        # update pod itself
        pod.update(pod.id, pod.name, podDict)
        
        # first time
        if len(pod.devices) == 0:
            logger.debug("Pod[id='%s', name='%s']: building inventory and resource..." % (pod.id, pod.name))
            self._createSpineAndIfds(session, pod, inventoryData['spines'])
            self._createLeafAndIfds(session, pod, inventoryData['leafs'])
            self._createLinks(session, pod)
            pod.devices.sort(key=lambda dev: dev.name) # Hack to order lists by name
            self._allocateResource(session, pod)
            # save the new inventory to database
            pod.inventoryData = base64.b64encode(zlib.compress(json.dumps(inventoryData)))
        else:        
            # compare new inventory that user provides against old inventory that we stored in the database
            self._diffInventory(session, pod, inventoryData)
            
        # commit everything to db
        self._dao.updateObjects(session, [pod])
            
        # TODO move the backup operation to CLI 
        # backup current database
        util.backupDatabase(self._conf)

    def updatePod(self, podId, podDict, inventoryDict = None):
        '''
        Modify an existing POD. As a sanity check, if we don't find the POD
        by UUID, a ValueException is thrown
        '''
        if podId is None: 
            raise InvalidRequest("Pod id cannot be None")

        with self._dao.getReadWriteSession() as session:        
            try:
                pod = self._dao.getObjectById(session, Pod, podId)
            except (exc.NoResultFound):
                raise PodNotFound(podId, exc) 

            # inventory can come from either podDict or inventoryDict
            inventoryData = self._resolveInventory(podDict, inventoryDict)
            # check inventory contains correct number of devices
            self._validatePod(pod, podDict, inventoryData)
    
            # update other fields
            self._updatePodData(session, pod, podDict, inventoryData)
            logger.info("Pod[id='%s', name='%s']: updated" % (pod.id, pod.name)) 
            podId = pod.id
        
        #Hack sqlalchemy: access object is REQUIRED after commit as session has expire_on_commit=True.
        with self._dao.getReadSession() as session:
            pod = self._dao.getObjectById(session, Pod, podId)
        return pod
    
    def deletePod(self, podId):
        '''
        Delete an existing POD. As a sanity check, if we don't find the POD
        by UUID, a ValueException is thrown
        '''
        if podId is None: 
            raise InvalidRequest("Pod id cannot be None")

        with self._dao.getReadWriteSession() as session:        
            try:
                pod = self._dao.getObjectById(session, Pod, podId)
            except (exc.NoResultFound):
                raise PodNotFound(podId, exc) 

            self._dao.deleteObject(session, pod)
            logger.info("Pod[id='%s', name='%s']: deleted" % (pod.id, pod.name)) 

    def createCablingPlan(self, podId):
        '''
        Finds Pod object by id and create cabling plan
        It also creates the output folders for pod
        '''
        if podId is None: 
            raise InvalidRequest("Pod id cannot be None")

        with self._dao.getReadWriteSession() as session:        
            try:
                pod = self._dao.getObjectById(session, Pod, podId)
            except (exc.NoResultFound):
                raise PodNotFound(podId, exc) 
 
            if len(pod.devices) > 0:
                cablingPlanWriter = CablingPlanWriter(self._conf, pod, self._dao)
                # create cabling plan in JSON format
                cablingPlanJson = cablingPlanWriter.writeJSON()
                pod.cablingPlan = CablingPlan(pod.id, cablingPlanJson)
                # create cabling plan in DOT format
                cablingPlanWriter.writeDOT()
                self._dao.updateObjects(session, [pod])
                
                return True
            else:
                logger.warning("Pod[id='%s', name='%s']: inventory is empty" % (pod.id, pod.name)) 
                return False
            
    def createDeviceConfig(self, podId):
        '''
        Finds Pod object by id and create device configurations
        It also creates the output folders for pod
        '''
        if podId is None: 
            raise InvalidRequest("Pod id cannot be None")

        with self._dao.getReadWriteSession() as session:        
            try:
                pod = self._dao.getObjectById(session, Pod, podId)
            except (exc.NoResultFound):
                raise PodNotFound(podId, exc) 
 
            if len(pod.devices) > 0:
                # create configuration
                self.generateConfig(session, pod)
        
                return True
            else:
                logger.warning("Pod[id='%s', name='%s']: inventory is empty" % (pod.id, pod.name)) 
                return False

    def _createLinks(self, session, pod):
        self._createInterconnectLinks(session, pod)
        
    def _createInterconnectLinks(self, session, pod):
        leaves = []
        spines = []
        for device in pod.devices:
            if (device.role == 'leaf'):
                logger.debug('device id: %s, name: %s, role: %s' % (device.id, device.name, device.role))
                leafUplinkPorts = session.query(InterfaceDefinition).filter(InterfaceDefinition.device_id == device.id).filter(InterfaceDefinition.role == 'uplink').order_by(InterfaceDefinition.sequenceNum).all()
                leaves.append({'leaf': device, 'leafUplinkPorts': leafUplinkPorts})
            elif (device.role == 'spine'):
                logger.debug('device id: %s, name: %s, role: %s' % (device.id, device.name, device.role))
                spinePorts = session.query(InterfaceDefinition).filter(InterfaceDefinition.device_id == device.id).filter(InterfaceDefinition.role == 'downlink').order_by(InterfaceDefinition.sequenceNum).all()
                spines.append({'spine': device, 'ports': spinePorts})
        
        leafIndex = 0
        spineIndex = 0
        modifiedObjects = []
        for leaf in leaves:
            for spine in spines:
                spinePort = spine['ports'][spineIndex]
                leafPort = leaf['leafUplinkPorts'][leafIndex]
                spinePort.peer = leafPort
                leafPort.peer = spinePort
                modifiedObjects.append(spinePort)
                modifiedObjects.append(leafPort)
                leafIndex += 1
            leafIndex = 0
            spineIndex += 1
        self._dao.updateObjects(session, modifiedObjects)
        
    def _getLeafSpineFromPod(self, pod):
        '''
        utility method to get list of spines and leafs of a pod
        returns dict with list for 'spines' and 'leafs'
        '''
        deviceDict = {}
        deviceDict['leafs'] = []
        deviceDict['spines'] = []
        for device in pod.devices:
            if (device.role == 'leaf'):
                deviceDict['leafs'].append(device)
            elif (device.role == 'spine'):
                deviceDict['spines'].append(device)
        return deviceDict
    
    def _allocateResource(self, session, pod):
        self._allocateLoopback(session, pod, pod.loopbackPrefix, pod.devices)
        leafSpineDict = self._getLeafSpineFromPod(pod)
        self._allocateIrb(session, pod, pod.vlanPrefix, leafSpineDict['leafs'])
        self._allocateInterconnect(session, pod.interConnectPrefix, leafSpineDict['spines'], leafSpineDict['leafs'])
        self._allocateAsNumberToSpines(session, pod.spineAS, leafSpineDict['spines'])
        self._allocateAsNumberToLeafs(session, pod.leafAS, leafSpineDict['leafs'])
        self._allocateManagement(session, pod.managementPrefix, pod.managementStartingIP, pod.managementMask, leafSpineDict['spines'], leafSpineDict['leafs'])
        
    def _allocateManagement(self, session, managementPrefix, managementStartingIP, managementMask, spines, leaves):
        deviceCount = len(spines)+len(leaves)
        managementIps = util.getMgmtIps(managementPrefix, managementStartingIP, managementMask, deviceCount)
        # don't do partial allocation
        if len(managementIps) == deviceCount:
            for spine, managementIp in zip(spines, managementIps[:len(spines)]):
                spine.managementIp = managementIp
            self._dao.updateObjects(session, spines)
            
            # for 2stage and leaf, don' fill in management ip
            if self.isZtpStaged == False:
                for leaf, managementIp in zip(leaves, managementIps[len(spines):]):
                    leaf.managementIp = managementIp
                self._dao.updateObjects(session, leaves)
        
    def _allocateLoopback(self, session, pod, loopbackPrefix, devices):
        loopbackIp = IPNetwork(loopbackPrefix).network
        numOfIps = len(devices) + 2 # +2 for network and broadcast
        numOfBits = int(math.ceil(math.log(numOfIps, 2))) 
        cidr = 32 - numOfBits
        lo0Block = IPNetwork(str(loopbackIp) + "/" + str(cidr))
        lo0Ips = list(lo0Block.iter_hosts())
        
        pod.allocatedLoopbackBlock = str(lo0Block.cidr)
        self._assignAllocatedLoopbackToDevices(session, devices, lo0Ips)

    def _assignAllocatedLoopbackToDevices(self, session, devices, lo0Ips):
        interfaces = []
        for device in devices:
            ifl = InterfaceLogical('lo0.0', device, str(lo0Ips.pop(0)) + '/32')
            interfaces.append(ifl)
        self._dao.createObjects(session, interfaces)

    def _allocateIrb(self, session, pod, irbPrefix, leafs):
        irbIp = IPNetwork(irbPrefix).network
        numOfHostIpsPerSwitch = pod.hostOrVmCountPerLeaf
        numOfSubnets = len(leafs)
        bitsPerSubnet = int(math.ceil(math.log(numOfHostIpsPerSwitch + 2, 2)))  # +2 for network and broadcast
        cidrForEachSubnet = 32 - bitsPerSubnet

        numOfIps = (numOfSubnets * (2 ** bitsPerSubnet))
        numOfBits = int(math.ceil(math.log(numOfIps, 2))) 
        cidr = 32 - numOfBits
        irbBlock = IPNetwork(str(irbIp) + "/" + str(cidr))
        irbSubnets = list(irbBlock.subnet(cidrForEachSubnet))
        
        pod.allocatedIrbBlock = str(irbBlock.cidr)
        self._assignAllocatedIrbToDevices(session, leafs, irbSubnets, cidrForEachSubnet)

    def _assignAllocatedIrbToDevices(self, session, leafs, irbSubnets, cidrForEachSubnet):
        interfaces = [] 
        for leaf in leafs:
            ipAddress = list(irbSubnets.pop(0).iter_hosts())[0]
            # TODO: would be better to get irb.1 from property file as .1 is VLAN ID
            ifl = InterfaceLogical('irb.1', leaf, str(ipAddress) + '/' + str(cidrForEachSubnet)) 
            interfaces.append(ifl)
        self._dao.createObjects(session, interfaces)

    def _allocateInterconnect(self, session, interConnectPrefix, spines, leafs):
        interConnectIp = IPNetwork(interConnectPrefix).network
        numOfIpsPerInterconnect = 2
        numOfSubnets = len(spines) * len(leafs)
        # no need to add +2 for network and broadcast, as junos supports /31
        # TODO: it should be configurable and come from property file
        bitsPerSubnet = int(math.ceil(math.log(numOfIpsPerInterconnect, 2)))    # value is 1  
        cidrForEachSubnet = 32 - bitsPerSubnet  # value is 31 as junos supports /31

        numOfIps = (numOfSubnets * (numOfIpsPerInterconnect)) # no need to add +2 for network and broadcast
        numOfBits = int(math.ceil(math.log(numOfIps, 2))) 
        cidr = 32 - numOfBits
        interconnectBlock = IPNetwork(str(interConnectIp) + "/" + str(cidr))
        interconnectSubnets = list(interconnectBlock.subnet(cidrForEachSubnet))

        interfaces = [] 
        spines[0].pod.allocatedInterConnectBlock = str(interconnectBlock.cidr)

        for spine in spines:
            ifdsHasPeer = session.query(InterfaceDefinition).filter(InterfaceDefinition.device_id == spine.id).filter(InterfaceDefinition.peer != None).filter(InterfaceDefinition.role == 'downlink').order_by(InterfaceDefinition.sequenceNum).all()
            for spineIfdHasPeer in ifdsHasPeer:
                subnet =  interconnectSubnets.pop(0)
                ips = list(subnet)
                
                spineEndIfl= InterfaceLogical(spineIfdHasPeer.name + '.0', spine, str(ips.pop(0)) + '/' + str(cidrForEachSubnet))
                spineIfdHasPeer.layerAboves.append(spineEndIfl)
                interfaces.append(spineEndIfl)
                
                leafEndIfd = spineIfdHasPeer.peer
                leafEndIfl= InterfaceLogical(leafEndIfd.name + '.0', leafEndIfd.device, str(ips.pop(0)) + '/' + str(cidrForEachSubnet))
                leafEndIfd.layerAboves.append(leafEndIfl)
                interfaces.append(leafEndIfl)
        self._dao.createObjects(session, interfaces)

    def _allocateAsNumberToSpines(self, session, spineAsn, spines):
        devices = []
        for spine in spines:
            spine.asn = spineAsn
            spineAsn += 1
            devices.append(spine)
        spines[0].pod.allocatedSpineAS = spineAsn - 1

        self._dao.updateObjects(session, devices)
        
    def _allocateAsNumberToLeafs(self, session, leafAsn, leafs):
        devices = []        
        for leaf in leafs:
            leaf.asn = leafAsn
            leafAsn += 1
            devices.append(leaf)
        leafs[0].pod.allocatefLeafAS = leafAsn - 1

        self._dao.updateObjects(session, devices)

    def generateConfig(self, session, pod):
        configWriter = ConfigWriter(self._conf, pod, self._dao)
        modifiedObjects = []
        
        for device in pod.devices:
            if device.role == 'leaf' and (self.isZtpStaged or device.family == 'unknown'):
                # leaf configs will get created when they are plugged in after 2Stage ztp
                continue
            
            config = self._createBaseConfig(device)
            config += self._createInterfaces(session, device)
            config += self._createRoutingOptionsStatic(session, device)
            config += self._createRoutingOptionsBgp(session, device)
            config += self._createProtocolBgp(session, device)
            config += self._createProtocolLldp(device)
            config += self._createPolicyOption(session, device)
            config += self._createSnmpTrapAndEvent(session, device)
            config += self._createVlan(device)
            device.config = DeviceConfig(device.id, config)
            modifiedObjects.append(device)
            logger.debug('Generated config for device name: %s, id: %s, storing in DB' % (device.name, device.id))
            configWriter.write(device)

        if self.isZtpStaged:
            pod.leafSettings = self._createLeafGenericConfigsFor2Stage(session, pod)
            modifiedObjects.append(pod)
            logger.debug('Generated %d leaf generic configs for pod: %s, storing in DB' % (len(pod.leafSettings), pod.name))
            configWriter.writeGenericLeaf(pod)
        
        self._dao.updateObjects(session, modifiedObjects)

    def _createBaseConfig(self, device):
        baseTemplate = self._templateEnv.get_template('baseTemplate.txt')
        return baseTemplate.render(hostName=device.name, hashedPassword=device.getHashPassword())

    def _createInterfaces(self, session, device): 
        lo0Stanza = self._templateEnv.get_template('lo0_stanza.txt')
        mgmtStanza = self._templateEnv.get_template('mgmt_interface.txt')
        rviStanza = self._templateEnv.get_template('rvi_stanza.txt')
            
        config = "interfaces {" + "\n" 
        # management interface
        config += mgmtStanza.render(mgmt_address=device.managementIp)
                
        #loopback interface
        loopbackIfl = session.query(InterfaceLogical).join(Device).filter(Device.id == device.id).filter(InterfaceLogical.name == 'lo0.0').one()
        config += lo0Stanza.render(address=loopbackIfl.ipaddress)
        
        # For Leaf add IRB and server facing interfaces        
        if device.role == 'leaf':
            irbIfl = session.query(InterfaceLogical).join(Device).filter(Device.id == device.id).filter(InterfaceLogical.name == 'irb.1').one()
            config += rviStanza.render(address=irbIfl.ipaddress)
            config += self._createAccessPortInterfaces(session, device)
                
        config += self._createInterconnectInterfaces(session, device)
        config += "}\n"
        return config

    def _createInterconnectInterfaces(self, session, device): 
        interfaceStanza = self._templateEnv.get_template('interface_stanza.txt')
        config = ''

        deviceInterconnectIfds = self._dao.getConnectedInterconnectIFDsFilterFakeOnes(session, device)
        for interconnectIfd in deviceInterconnectIfds:
            peerDevice = interconnectIfd.peer.device
            interconnectIfl = interconnectIfd.layerAboves[0]
            namePlusUnit = interconnectIfl.name.split('.')  # example et-0/0/0.0
            config += interfaceStanza.render(ifd_name=namePlusUnit[0],
                                             unit=namePlusUnit[1],
                                             description="facing_" + peerDevice.name,
                                             address=interconnectIfl.ipaddress)
        return config
    
    def _createAccessPortInterfaces(self, session, device):
        accessInterface = self._templateEnv.get_template('accessInterface.txt')
        
        ifdNames = self.deviceSku.getPortNamesForDeviceFamily(device.family, 'leaf')['downlinkPorts']
        return accessInterface.render(ifdNames=ifdNames)

    def _getOpenclosTrapTargetIpFromConf(self):
        snmpTrapConf = self._conf.get('snmpTrap')
        if snmpTrapConf is not None:
            openclosSnmpTrapConf = snmpTrapConf.get('openclos_trap_group') 
            if openclosSnmpTrapConf is not None:
                target = snmpTrapConf.get('openclos_trap_group').get('target')
                if target is not None:
                    return [target]
        return []

    def _getSnmpTrapTargets(self, session):
        if self.isZtpStaged:
            return self._getOpenclosTrapTargetIpFromConf()
        else:
            return []

    def _getParamsForOutOfBandNetwork(self, session, pod):
        '''
        add all trap-target to the OOB list
        '''
        oobList = self._getSnmpTrapTargets(session)
        oobNetworks = pod.outOfBandAddressList
        if oobNetworks is not None and len(oobNetworks) > 0:
            oobList += oobNetworks.split(',')

        # hack to make sure all address has cidr notation
        for i in xrange(len(oobList)):
            if '/' not in oobList[i]:
                oobList[i] += '/32'

        gateway = pod.outOfBandGateway
        if gateway is None:
            gateway = util.loadClosDefinition()['ztp'].get('dhcpOptionRoute')
       
        oobList = set(oobList)
        if oobList and gateway:
            return {'networks': oobList, 'gateway': gateway}
        else:
            return {}
    
    def _createRoutingOptionsStatic(self, session, device):
        routingOptions = self._templateEnv.get_template('routingOptionsStatic.txt')
        return routingOptions.render(oob = self._getParamsForOutOfBandNetwork(session, device.pod))

    def _createRoutingOptionsBgp(self, session, device):
        routingOptions = self._templateEnv.get_template('routingOptionsBgp.txt')

        loopbackIfl = session.query(InterfaceLogical).join(Device).filter(Device.id == device.id).filter(InterfaceLogical.name == 'lo0.0').one()
        loopbackIpWithNoCidr = loopbackIfl.ipaddress.split('/')[0]
        
        return routingOptions.render(routerId=loopbackIpWithNoCidr, asn=str(device.asn))

    def _createProtocolBgp(self, session, device):
        template = self._templateEnv.get_template('protocolBgp.txt')

        neighborList = []
        deviceInterconnectIfds = self._dao.getConnectedInterconnectIFDsFilterFakeOnes(session, device)
        for ifd in deviceInterconnectIfds:
            peerIfd = ifd.peer
            peerDevice = peerIfd.device
            peerInterconnectIfl = peerIfd.layerAboves[0]
            peerInterconnectIpNoCidr = peerInterconnectIfl.ipaddress.split('/')[0]
            neighborList.append({'peer_ip': peerInterconnectIpNoCidr, 'peer_asn': peerDevice.asn})

        return template.render(neighbors=neighborList)        
         
    def _createProtocolLldp(self, device):
        template = self._templateEnv.get_template('protocolLldp.txt')
        return template.render()        

    def _createPolicyOption(self, session, device):
        pod = device.pod
        
        template = self._templateEnv.get_template('policyOptions.txt')
        subnetDict = {}
        subnetDict['lo0_in'] = pod.allocatedLoopbackBlock
        subnetDict['irb_in'] = pod.allocatedIrbBlock
        
        if device.role == 'leaf':
            deviceLoopbackIfl = session.query(InterfaceLogical).join(Device).filter(Device.id == device.id).filter(InterfaceLogical.name == 'lo0.0').one()
            deviceIrbIfl = session.query(InterfaceLogical).join(Device).filter(Device.id == device.id).filter(InterfaceLogical.name == 'irb.1').one()
            subnetDict['lo0_out'] = deviceLoopbackIfl.ipaddress
            subnetDict['irb_out'] = deviceIrbIfl.ipaddress
        else:
            subnetDict['lo0_out'] = pod.allocatedLoopbackBlock
            subnetDict['irb_out'] = pod.allocatedIrbBlock
         
        return template.render(subnet=subnetDict)
        
    def _createVlan(self, device):
        if device.role == 'leaf':
            template = self._templateEnv.get_template('vlans.txt')
            return template.render()
        else:
            return ''

    def _getOpenclosTrapGroupSettings(self, session):
        '''
        :returns list: list of trap group settings
        '''
        if not self.isZtpStaged:
            return []
        
        trapGroups = []
        
        targets = self._dao.getObjectsByName(session, TrapGroup, 'openclos_trap_group')
        if targets:
            targetAddressList = [target.targetAddress for target in targets]
            trapGroups.append({'name': targets[0].name, 'port': targets[0].port, 'targetIp': targetAddressList })
            logger.debug('Added SNMP Trap setting for openclos_trap_group (from DB) with %d targets' % (len(targets)))
        else:
            snmpTrapConf = self._conf.get('snmpTrap')
            if snmpTrapConf is not None:
                openclosSnmpTrapConf = snmpTrapConf.get('openclos_trap_group') 
                if openclosSnmpTrapConf is not None:
                    targetList = self._getOpenclosTrapTargetIpFromConf()
                    trapGroups.append({'name': 'openclos_trap_group', 'port': openclosSnmpTrapConf['port'], 'targetIp': targetList })
                else:
                    logger.error('No SNMP Trap setting found for openclos_trap_group in openclos.yaml')
            else:
                logger.error('No SNMP Trap setting found for openclos_trap_group in openclos.yaml')
        
        return trapGroups

    def _getLeafTrapGroupSettings(self, session, generic = False):
        '''
        :param session: database session
        :param boolean: generic flag indicated if it is for leafGeneric config or leaf 2nd-stage config
        '''
        if self.isZtpStaged:
            if generic:
                return self._getOpenclosTrapGroupSettings(session)
            else:
                return []
        else:
            return []
        
    def _getSpineTrapGroupSettings(self, session):
        return []
    
    def _createSnmpTrapAndEvent(self, session, device):
        snmpTemplate = self._templateEnv.get_template('snmpTrap.txt')
        trapEventTemplate = self._templateEnv.get_template('eventOptionForTrap.txt')
        
        configlet = trapEventTemplate.render()
        
        if device.role == 'leaf':
            trapGroups = self._getLeafTrapGroupSettings(session)
            if trapGroups:
                configlet += snmpTemplate.render(trapGroups = trapGroups)
                return configlet

        elif device.role == 'spine':
            trapGroups = self._getSpineTrapGroupSettings(session)
            if trapGroups:
                configlet += snmpTemplate.render(trapGroups = trapGroups)
                return configlet

        return ''

    def _createSnmpTrapAndEventForLeafFor2ndStage(self, session, device):
        snmpTemplate = self._templateEnv.get_template('snmpTrap.txt')
        trapEventTemplate = self._templateEnv.get_template('eventOptionForTrap.txt')
        disableSnmpTemplate = self._templateEnv.get_template('snmpTrapDisable.txt')
        
        configlet = trapEventTemplate.render()
        
        if device.role == 'leaf':
            trapGroups = self._getOpenclosTrapGroupSettings(session)
            if trapGroups:
                configlet += disableSnmpTemplate.render(trapGroups = trapGroups)
            trapGroups = self._getLeafTrapGroupSettings(session)
            if trapGroups:
                configlet += snmpTemplate.render(trapGroups = trapGroups)
                
            return configlet

        elif device.role == 'spine':
            logger.debug('Device: %s, id: %s, role: spine, no 2ndStage trap/event connfig generated' % (device.name, device.id))
        return ''

    def _createLeafGenericConfigsFor2Stage(self, session, pod):
        '''
        :param Pod: pod
        :returns list: list of PodConfigs
        '''
        leafTemplate = self._templateEnv.get_template('leafGenericTemplate.txt')
        leafSettings = {}
        for leafSetting in pod.leafSettings:
            leafSettings[leafSetting.deviceFamily] = leafSetting

        trapGroups = self._getLeafTrapGroupSettings(session, True)

        outOfBandNetworkParams = self._getParamsForOutOfBandNetwork(session, pod)
        
        for deviceFamily in leafSettings.keys():
            if deviceFamily == 'qfx5100-24q-2p':
                continue
            
            ifdNames = []
            for ifdName in self.deviceSku.getPortNamesForDeviceFamily(deviceFamily, 'leaf')['downlinkPorts']:
                ifdNames.append(ifdName)

            leafSettings[deviceFamily].config = leafTemplate.render(deviceFamily = deviceFamily, oob = outOfBandNetworkParams, 
                trapGroups = trapGroups, hashedPassword=pod.getHashPassword(), ifdNames=ifdNames)
            
        return leafSettings.values()

    def createLeafConfigFor2Stage(self, device):
        configWriter = ConfigWriter(self._conf, device.pod, self._dao)
        
        with self._dao.getReadWriteSession() as session:
            config = self._createBaseConfig(device)
            config += self._createInterfaces(session, device)
            config += self._createRoutingOptionsStatic(session, device)
            config += self._createRoutingOptionsBgp(session, device)
            config += self._createProtocolBgp(session, device)
            config += self._createProtocolLldp(device)
            config += self._createPolicyOption(session, device)
            config += self._createSnmpTrapAndEventForLeafFor2ndStage(session, device)
            config += self._createVlan(device)
            device.config = DeviceConfig(device.id, config)
            self._dao.updateObjects(session, [device])
        logger.debug('Generated config for device name: %s, id: %s, storing in DB' % (device.name, device.id))
        configWriter.write(device)
        return config
Esempio n. 11
0
class RestServer():
    def __init__(self, conf={}, daoClass=Dao):
        if any(conf) == False:
            self._openclosProperty = OpenClosProperty(appName=moduleName)
            self._conf = self._openclosProperty.getProperties()

            global webServerRoot
            webServerRoot = self._conf['outputDir']
        else:
            self._conf = conf

        self.__daoClass = daoClass
        self.__dao = daoClass.getInstance()
        self.openclosDbSessionPlugin = OpenclosDbSessionPlugin(daoClass)

        if 'httpServer' in self._conf and 'ipAddr' in self._conf[
                'httpServer'] and self._conf['httpServer'][
                    'ipAddr'] is not None:
            self.host = self._conf['httpServer']['ipAddr']
        else:
            self.host = 'localhost'

        if 'httpServer' in self._conf and 'port' in self._conf['httpServer']:
            self.port = self._conf['httpServer']['port']
        else:
            self.port = 8080
        self.baseUrl = 'http://%s:%d' % (self.host, self.port)

        self.report = ResourceAllocationReport(self._conf, daoClass)
        # Create a single instance of l2Report as it holds thread-pool
        # for device connection. Don't create l2Report multiple times
        self.l2Report = L2Report(self._conf, daoClass)
        # Create a single instance of l3Report as it holds thread-pool
        # for device connection. Don't create l3Report multiple times
        self.l3Report = L3Report(self._conf, daoClass)
        self.deviceSku = DeviceSku()

    def initRest(self):
        self.addRoutes(self.baseUrl)
        self.app = bottle.app()
        self.app.install(loggingPlugin)
        self.app.install(self.openclosDbSessionPlugin)
        logger.info('RestServer initRest() done')

    def _reset(self):
        """
        Resets the state of the rest server and application
        Used for Test only
        """
        self.app.uninstall(loggingPlugin)
        self.app.uninstall(OpenclosDbSessionPlugin)

    def start(self):
        logger.info('REST server starting at %s:%d' % (self.host, self.port))
        debugRest = False
        if logger.isEnabledFor(logging.DEBUG):
            debugRest = True

        if self._openclosProperty.isSqliteUsed():
            bottle.run(self.app,
                       host=self.host,
                       port=self.port,
                       debug=debugRest)
        else:
            bottle.run(self.app,
                       host=self.host,
                       port=self.port,
                       debug=debugRest,
                       server='paste')

    @staticmethod
    @error(400)
    def error400(error):
        bottle.response.headers['Content-Type'] = 'application/json'
        if error.exception is not None:
            return json.dumps({
                'errorCode': error.exception.code,
                'errorMessage': error.exception.message
            })
        else:
            return json.dumps({
                'errorCode': 0,
                'errorMessage': 'A generic error occurred'
            })

    @staticmethod
    @error(404)
    def error404(error):
        bottle.response.headers['Content-Type'] = 'application/json'
        if error.exception is not None:
            return json.dumps({
                'errorCode': error.exception.code,
                'errorMessage': error.exception.message
            })
        else:
            return json.dumps({
                'errorCode': 0,
                'errorMessage': 'A generic error occurred'
            })

    def addRoutes(self, baseUrl):
        self.indexLinks = []

        # GET APIs
        bottle.route('/', 'GET', self.getIndex)
        bottle.route('/openclos', 'GET', self.getIndex)
        bottle.route('/openclos/conf', 'GET', self.getOpenClosConfigParams)
        bottle.route('/openclos/pods', 'GET', self.getPods)
        bottle.route('/openclos/images/<junosImageName>', 'GET',
                     self.getJunosImage)
        bottle.route('/openclos/pods/<podId>', 'GET', self.getPod)
        bottle.route('/openclos/pods/<podId>/cabling-plan', 'GET',
                     self.getCablingPlan)
        bottle.route('/openclos/pods/<podId>/ztp-configuration', 'GET',
                     self.getZtpConfig)
        bottle.route('/openclos/pods/<podId>/device-configuration', 'GET',
                     self.getDeviceConfigsInZip)
        bottle.route(
            '/openclos/pods/<podId>/leaf-generic-configurations/<deviceModel>',
            'GET', self.getLeafGenericConfiguration)
        bottle.route('/openclos/pods/<podId>/l2-report', 'GET',
                     self.getL2Report)
        bottle.route('/openclos/pods/<podId>/l3-report', 'GET',
                     self.getL3Report)
        bottle.route('/openclos/pods/<podId>/devices', 'GET', self.getDevices)
        bottle.route('/openclos/pods/<podId>/devices/<deviceId>', 'GET',
                     self.getDevice)
        bottle.route('/openclos/pods/<podId>/devices/<deviceId>/config', 'GET',
                     self.getDeviceConfig)

        # POST/PUT APIs
        bottle.route('/openclos/pods', 'POST', self.createPod)
        bottle.route('/openclos/pods/<podId>/cabling-plan', 'PUT',
                     self.createCablingPlan)
        bottle.route('/openclos/pods/<podId>/device-configuration', 'PUT',
                     self.createDeviceConfiguration)
        bottle.route('/openclos/pods/<podId>/ztp-configuration', 'PUT',
                     self.createZtpConfiguration)
        bottle.route('/openclos/pods/<podId>', 'PUT', self.reconfigPod)
        bottle.route('/openclos/conf/', 'PUT', self.setOpenClosConfigParams)

        # DELETE APIs
        bottle.route('/openclos/pods/<podId>', 'DELETE', self.deletePod)

        self.createLinkForConfigs()

    def createLinkForConfigs(self):
        # index page should show all top level URLs
        # users whould be able to drill down through navigation
        self.indexLinks.append(ResourceLink(self.baseUrl, '/openclos/pods'))
        self.indexLinks.append(ResourceLink(self.baseUrl, '/openclos/conf'))

    def getIndex(self, dbSession=None):
        if 'openclos' not in bottle.request.url:
            bottle.redirect(
                str(bottle.request.url).translate(None, ',') + 'openclos')

        jsonLinks = []
        for link in self.indexLinks:
            jsonLinks.append({'link': link.toDict()})

        jsonBody = \
            {'href': str(bottle.request.url).translate(None, ','),
             'links': jsonLinks
             }

        return jsonBody

    def getPods(self, dbSession):

        url = str(bottle.request.url).translate(None, ',')
        podsData = {}
        listOfIpFbarics = []
        pods = self.report.getPods(dbSession)
        logger.debug("count of pods: %d", len(pods))
        if not pods:
            logger.debug("There are no pods in the system ")

        for i in range(len(pods)):
            pod = {}
            pod['uri'] = url + '/' + pods[i]['id']
            pod['id'] = pods[i]['id']
            pod['name'] = pods[i]['name']
            pod['spineDeviceType'] = pods[i]['spineDeviceType']
            pod['spineCount'] = pods[i]['spineCount']
            pod['leafSettings'] = pods[i]['leafSettings']
            pod['leafCount'] = pods[i]['leafCount']
            pod['devicePassword'] = pods[i]['devicePassword']
            listOfIpFbarics.append(pod)
        podsData['pod'] = listOfIpFbarics
        podsData['total'] = len(listOfIpFbarics)
        podsData['uri'] = url
        return {'pods': podsData}

    def getPodFieldListToCopy(self):
        return [
            'id', 'name', 'description', 'spineAS', 'spineDeviceType',
            'spineCount', 'leafAS', 'leafCount', 'leafUplinkcountMustBeUp',
            'loopbackPrefix', 'vlanPrefix', 'interConnectPrefix',
            'managementPrefix', 'outOfBandAddressList', 'outOfBandGateway',
            'topologyType', 'spineJunosImage', 'hostOrVmCountPerLeaf'
        ]

    def getPod(self, dbSession, podId, requestUrl=None):
        if requestUrl is None:
            requestUrl = str(bottle.request.url).translate(None, ',')
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            outputDict = {}
            devices = pod.devices
            for field in self.getPodFieldListToCopy():
                outputDict[field] = pod.__dict__.get(field)
            '''
            outputDict['id'] = pod.id
            outputDict['name'] = pod.name
            outputDict['description'] = pod.description 
            outputDict['spineAS'] = pod.spineAS
            outputDict['spineDeviceType'] = pod.spineDeviceType
            outputDict['spineCount'] = pod.spineCount
            outputDict['leafAS'] = pod.leafAS
            outputDict['leafCount'] = pod.leafCount
            outputDict['loopbackPrefix'] = pod.loopbackPrefix 
            outputDict['vlanPrefix'] = pod.vlanPrefix
            outputDict['interConnectPrefix'] = pod.interConnectPrefix 
            outputDict['managementPrefix'] = pod.managementPrefix
            outputDict['outOfBandAddressList'] = pod.outOfBandAddressList
            outputDict['outOfBandGateway'] = pod.outOfBandGateway 
            outputDict['topologyType'] = pod.topologyType
            outputDict['spineJunosImage'] = pod.spineJunosImage
            outputDict['hostOrVmCountPerLeaf'] = pod.hostOrVmCountPerLeaf
            '''
            outputDict['leafSettings'] = []
            for leafSetting in pod.leafSettings:
                outputDict['leafSettings'].append({
                    'deviceType':
                    leafSetting.deviceFamily,
                    'junosImage':
                    leafSetting.junosImage
                })

            outputDict['devicePassword'] = pod.getCleartextPassword()
            outputDict['uri'] = requestUrl
            outputDict['devices'] = {
                'uri': requestUrl + '/devices',
                'total': len(devices)
            }
            outputDict['cablingPlan'] = {'uri': requestUrl + '/cabling-plan'}
            outputDict['deviceConfiguration'] = {
                'uri': requestUrl + '/device-configuration'
            }
            outputDict['ztpConfiguration'] = {
                'uri': requestUrl + '/ztp-configuration'
            }
            outputDict['l2Report'] = {'uri': requestUrl + '/l2-report'}
            outputDict['l3Report'] = {'uri': requestUrl + '/l3-report'}

            logger.debug('getPod: %s' % (podId))

            return {'pod': outputDict}

        else:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))

    def getCablingPlan(self, dbSession, podId):

        header = bottle.request.get_header('Accept')
        logger.debug('Accept header before processing: %s' % (header))
        # hack to remove comma character, must be a bug on Bottle
        header = header.translate(None, ',')
        logger.debug('Accept header after processing: %s' % (header))

        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            logger.debug('Pod name: %s' % (pod.name))

            if header == 'application/json':
                cablingPlan = pod.cablingPlan
                if cablingPlan is not None and cablingPlan.json is not None:
                    logger.debug('CablingPlan found in DB')
                    return cablingPlan.json
                else:
                    raise bottle.HTTPError(404,
                                           exception=CablingPlanNotFound(
                                               pod.id))

            else:
                podFolder = pod.id + '-' + pod.name
                fileName = os.path.join(podFolder, 'cablingPlan.dot')
                logger.debug(
                    'webServerRoot: %s, fileName: %s, exists: %s' %
                    (webServerRoot, fileName,
                     os.path.exists(os.path.join(webServerRoot, fileName))))
                logger.debug('Cabling file name: %s' % (fileName))
                cablingPlan = bottle.static_file(fileName, root=webServerRoot)

                if isinstance(cablingPlan, bottle.HTTPError):
                    raise bottle.HTTPError(
                        404, exception=CablingPlanNotFound(podFolder))
                return cablingPlan

        else:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))

    def getLeafGenericConfiguration(self, dbSession, podId, deviceModel):
        pod = self.report.getPod(dbSession, podId)
        if pod is None:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))

        logger.debug('Pod name: %s, id: %s' % (pod.name, podId))

        leafSetting = self.__dao.getLeafSetting(dbSession, podId, deviceModel)
        if leafSetting is None or leafSetting.config is None:
            raise bottle.HTTPError(
                404,
                exception=DeviceConfigurationNotFound(
                    "Pod exists but no leaf generic config found, probably configuration \
                was not created. deviceModel: %s, pod name: '%s', id: '%s'" %
                    (deviceModel, pod.name, podId)))

        bottle.response.headers['Content-Type'] = 'application/json'
        return leafSetting.config

    def getDeviceConfigsInZip(self, dbSession, podId):
        pod = self.report.getPod(dbSession, podId)
        if pod is None:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))

        logger.debug('Pod name: %s' % (pod.name))

        zippedConfigFiles = self.createZipArchive(pod)
        if zippedConfigFiles is not None:
            bottle.response.headers['Content-Type'] = 'application/zip'
            return zippedConfigFiles
        else:
            raise bottle.HTTPError(
                404,
                exception=DeviceConfigurationNotFound(
                    "Pod exists but no configs for devices.'%s " % (pod.name)))

    def createZipArchive(self, pod):

        buff = StringIO.StringIO()
        zipArchive = zipfile.ZipFile(buff, mode='w')
        for device in pod.devices:
            fileName = device.id + '__' + device.name + '.conf'
            if device.config is not None:
                zipArchive.writestr(fileName, device.config.config)

        if pod.leafSettings is not None:
            for leafSetting in pod.leafSettings:
                if leafSetting.config is not None:
                    zipArchive.writestr(leafSetting.deviceFamily + '.conf',
                                        leafSetting.config)

        zipArchive.close()
        logger.debug('zip file content:\n' + str(zipArchive.namelist()))
        return buff.getvalue()

    def copyAdditionalDeviceFields(self, dict, device):
        '''
        Hook to enhance Device object
        '''

    def getDevices(self, dbSession, podId):

        devices = {}
        listOfDevices = []
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            for device in pod.devices:
                outputDict = {}
                outputDict['id'] = device.id
                outputDict['name'] = device.name
                outputDict['role'] = device.role
                outputDict['family'] = device.family
                outputDict['macAddress'] = device.macAddress
                outputDict['managementIp'] = device.managementIp
                outputDict['serialNumber'] = device.serialNumber
                outputDict['deployStatus'] = device.deployStatus
                outputDict['configStatus'] = device.configStatus
                outputDict['l2Status'] = device.l2Status
                outputDict['l3Status'] = device.l3Status
                outputDict['uri'] = str(bottle.request.url).translate(
                    None, ',') + '/' + device.id
                self.copyAdditionalDeviceFields(outputDict, device)

                listOfDevices.append(outputDict)
            devices['device'] = listOfDevices
            devices['uri'] = str(bottle.request.url).translate(None, ',')
            devices['total'] = len(pod.devices)
            return {'devices': devices}
        else:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))

    def getDevice(self, dbSession, podId, deviceId):

        device = self.isDeviceExists(dbSession, podId, deviceId)
        #podUri is constructed from url
        url = str(bottle.request.url).translate(None, ',')
        uri = url.split("/")
        uri.pop()
        uri.pop()
        ipFbaricUri = "/".join(uri)

        if device is not None:
            outputDict = {}
            outputDict['id'] = device.id
            outputDict['name'] = device.name
            outputDict['role'] = device.role
            outputDict['family'] = device.family
            outputDict['username'] = device.username
            outputDict['password'] = device.getCleartextPassword()
            outputDict['macAddress'] = device.macAddress
            outputDict['managementIp'] = device.managementIp
            outputDict['asn'] = device.asn
            outputDict['configStatus'] = device.configStatus
            outputDict['configStatusReason'] = device.configStatusReason
            outputDict['l2Status'] = device.l2Status
            outputDict['l2StatusReason'] = device.l2StatusReason
            outputDict['l3Status'] = device.l3Status
            outputDict['l3StatusReason'] = device.l3StatusReason
            outputDict['serialNumber'] = device.serialNumber
            outputDict['deployStatus'] = device.deployStatus
            outputDict['uri'] = str(bottle.request.url).translate(None, ',')
            outputDict['pod'] = {'uri': ipFbaricUri}
            outputDict['config'] = {
                'uri': str(bottle.request.url).translate(None, ',') + '/config'
            }
            self.copyAdditionalDeviceFields(outputDict, device)

            return {'device': outputDict}
        else:
            raise bottle.HTTPError(
                404,
                exception=DeviceNotFound(
                    "No device found with podId: '%s', deviceId: '%s'" %
                    (podId, deviceId)))

    def getDeviceConfig(self, dbSession, podId, deviceId):

        device = self.isDeviceExists(dbSession, podId, deviceId)
        if device is None:
            raise bottle.HTTPError(
                404,
                exception=DeviceNotFound(
                    "No device found with podId: '%s', deviceId: '%s'" %
                    (podId, deviceId)))

        config = device.config
        if config is None:
            raise bottle.HTTPError(
                404,
                exception=DeviceConfigurationNotFound(
                    "Device exists but no config found, probably fabric script is not ran. podId: '%s', deviceId: '%s'"
                    % (podId, deviceId)))

        bottle.response.headers['Content-Type'] = 'application/json'
        return config.config

    def getZtpConfig(self, dbSession, podId):

        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            logger.debug('pod name: %s' % (pod.name))

            podFolder = pod.id + '-' + pod.name
            fileName = os.path.join(podFolder, "dhcpd.conf")
            logger.debug(
                'webServerRoot: %s, fileName: %s, exists: %s' %
                (webServerRoot, fileName,
                 os.path.exists(os.path.join(webServerRoot, fileName))))
            ztpConf = bottle.static_file(fileName, root=webServerRoot)
            if isinstance(ztpConf, bottle.HTTPError):
                raise bottle.HTTPError(
                    404,
                    exception=DeviceConfigurationNotFound(
                        "Pod exists but no ztp Config found. Pod name: '%s " %
                        (pod.name)))
            return ztpConf
        else:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))

    def isDeviceExists(self, dbSession, podId, deviceId):
        try:
            device = dbSession.query(Device).join(Pod).filter(
                Device.id == deviceId).filter(Pod.id == podId).one()
            return device
        except (exc.NoResultFound):
            raise bottle.HTTPError(
                404,
                exception=DeviceNotFound(
                    "No device found with podId: '%s', deviceId: '%s'" %
                    (podId, deviceId)))

    def getJunosImage(self, dbSession, junosImageName):

        fileName = os.path.join(junosImageRoot, junosImageName)
        logger.debug(
            'junosImageRoot: %s, image: %s, exists: %s' %
            (junosImageRoot, junosImageName, os.path.exists(fileName)))

        config = bottle.static_file(junosImageName, root=junosImageRoot)
        if isinstance(config, bottle.HTTPError):
            raise bottle.HTTPError(
                404,
                exception=ImageNotFound(
                    "Junos image file not found. name: '%s'" %
                    (junosImageName)))
        return config

    def getOpenClosConfigParams(self, dbSession):
        supportedDevices = []

        for deviceFamily, value in self.deviceSku.skuDetail.iteritems():
            for role, ports in value.iteritems():
                uplinks = ports.get('uplinkPorts')
                downlinks = ports.get('downlinkPorts')
                deviceDetail = {
                    'family': deviceFamily,
                    'role': role,
                    'uplinkPorts': uplinks,
                    'downlinkPorts': downlinks
                }
                supportedDevices.append(deviceDetail)

        confValues = {}
        confValues.update({'dbUrl': self._conf['dbUrl']})
        confValues.update({'supportedDevices': supportedDevices})
        confValues.update({'dotColors': self._conf['DOT']['colors']})
        confValues.update({'httpServer': self._conf['httpServer']})
        confValues.update({'snmpTrap': self._conf['snmpTrap']})

        return {'OpenClosConf': confValues}

    def createPod(self, dbSession):
        if bottle.request.json is None:
            raise bottle.HTTPError(
                400, exception=InvalidRequest("No json in request object"))
        else:
            pod = bottle.request.json.get('pod')
            if pod is None:
                raise bottle.HTTPError(
                    400, exception=InvalidRequest("POST body cannot be empty"))

        l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
        podDevices = self.getDevDictFromDict(pod)
        pod = self.getPodFromDict(pod)
        podName = pod.pop('name')
        try:
            createdPod = l3ClosMediation.createPod(podName, pod, podDevices)
            url = str(bottle.request.url).translate(None,
                                                    ',') + '/' + createdPod.id
            pod = self.getPod(dbSession, createdPod.id, url)
        except Exception as e:
            logger.debug('StackTrace: %s' % (traceback.format_exc()))
            raise bottle.HTTPError(400, exception=e)
        bottle.response.set_header('Location', url)
        bottle.response.status = 201

        return pod

    def createCablingPlan(self, dbSession, podId):
        try:
            l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
            if l3ClosMediation.createCablingPlan(podId) is True:
                return bottle.HTTPResponse(status=200)
        except PodNotFound as e:
            raise bottle.HTTPError(404, exception=e)
        except Exception as e:
            raise bottle.HTTPError(500, exception=e)

    def createDeviceConfiguration(self, dbSession, podId):
        try:
            l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
            if l3ClosMediation.createDeviceConfig(podId) is True:
                return bottle.HTTPResponse(status=200)
        except PodNotFound as e:
            raise bottle.HTTPError(404, exception=e)
        except Exception as e:
            raise bottle.HTTPError(500, exception=e)

    def createZtpConfiguration(self, dbSession, podId):
        try:
            ZtpServer().createPodSpecificDhcpConfFile(dbSession, podId)
        except PodNotFound as e:
            raise bottle.HTTPError(404, exception=e)
        except Exception as e:
            raise bottle.HTTPError(500, exception=e)

    def reconfigPod(self, dbSession, podId):
        if bottle.request.json is None:
            raise bottle.HTTPError(
                400, exception=InvalidRequest("No json in request object"))
        else:
            inPod = bottle.request.json.get('pod')
            if inPod is None:
                raise bottle.HTTPError(
                    400, exception=InvalidRequest("POST body cannot be empty"))

        l3ClosMediation = L3ClosMediation(self._conf, self.__daoClass)
        pod = self.getPodFromDict(inPod)
        #pod['id'] = podId
        #pod['uri'] = str(bottle.request.url).translate(None, ',')
        podDevices = self.getDevDictFromDict(inPod)
        # Pass the pod and podDevices dictionaries to config/update API, then return
        try:
            updatedPod = l3ClosMediation.updatePod(podId, pod, podDevices)
            url = str(bottle.request.url).translate(None,
                                                    ',') + '/' + updatedPod.id
            return self.getPod(dbSession, podId, url)
        except Exception as e:
            raise bottle.HTTPError(400, exception=e)

    def setOpenClosConfigParams(self):
        return bottle.HTTPResponse(status=200)

    def deletePod(self, dbSession, podId):
        pod = self.report.getPod(dbSession, podId)
        if pod is not None:
            self.__dao.deleteObject(dbSession, pod)
            util.deleteOutFolder(self._conf, pod)
            logger.debug("Pod with id: %s deleted" % (podId))
        else:
            raise bottle.HTTPError(404, exception=PodNotFound(podId))
        return bottle.HTTPResponse(status=204)

    def getPodFromDict(self, podDict):
        pod = {}
        '''
        # Need to revisit later on to make thing works as below.
        podDict.pop('devices')
        pod = Pod(**inPod)
        '''
        if podDict is None:
            raise bottle.HTTPError(
                400,
                exception=InvalidRequest("Invalid value in request body."))

        for field in self.getPodFieldListToCopy():
            pod[field] = podDict.get(field)
        '''
        pod['name'] = podDict.get('name')
        pod['description'] = podDict.get('description')
        pod['spineAS'] = podDict.get('spineAS')
        pod['spineDeviceType'] = podDict.get('spineDeviceType')
        pod['spineCount'] = podDict.get('spineCount')
        pod['leafAS'] = podDict.get('leafAS')
        pod['leafCount'] = podDict.get('leafCount')
        pod['leafUplinkcountMustBeUp'] = podDict.get('leafUplinkcountMustBeUp')
        pod['loopbackPrefix'] = podDict.get('loopbackPrefix')
        pod['vlanPrefix'] = podDict.get('vlanPrefix')
        pod['interConnectPrefix'] = podDict.get('interConnectPrefix')
        pod['managementPrefix'] = podDict.get('managementPrefix')
        pod['outOfBandAddressList'] = podDict.get('outOfBandAddressList')
        pod['outOfBandGateway'] = podDict.get('outOfBandGateway')
        pod['topologyType'] = podDict.get('topologyType')
        pod['topologyType'] = podDict.get('topologyType')
        pod['spineJunosImage'] = podDict.get('spineJunosImage')
        pod['hostOrVmCountPerLeaf'] = podDict.get('hostOrVmCountPerLeaf')
        '''

        pod['leafSettings'] = podDict.get('leafSettings')
        pod['devicePassword'] = podDict.get('devicePassword')

        return pod

    def getDevDictFromDict(self, podDict):
        if podDict is not None:
            devices = podDict.get('devices')
        else:
            raise bottle.HTTPError(
                400,
                exception=InvalidRequest("Invalid value in request body."))

        podDevices = {}
        spines = []
        leaves = []
        for device in devices:
            temp = {}
            temp['name'] = device.get('name')
            temp['macAddress'] = device.get('macAddress')
            temp['role'] = device.get('role')
            temp['username'] = device.get('username')
            temp['password'] = device.get('password')
            temp['family'] = device.get('family')
            temp['serialNumber'] = device.get('serialNumber')
            temp['deployStatus'] = device.get('deployStatus')
            if temp['role'] == 'spine':
                spines.append(temp)
            elif temp['role'] == 'leaf':
                leaves.append(temp)
            else:
                raise bottle.HTTPError(
                    400,
                    exception=InvalidRequest(
                        "Unexpected role value in device inventory list"))
            podDevices['spines'] = spines
            podDevices['leafs'] = leaves

        return podDevices

    def getL2Report(self, dbSession, podId):
        try:
            cached = bottle.request.query.get('cached', '1')
            if cached == '1':
                cachedData = True
            else:
                cachedData = False
            bottle.response.headers['Content-Type'] = 'application/json'
            return self.l2Report.generateReport(podId, cachedData)

        except Exception as e:
            raise bottle.HTTPError(404, exception=PodNotFound(podId, e))

    def getL3Report(self, dbSession, podId):
        try:
            cached = bottle.request.query.get('cached', '1')
            if cached == '1':
                cachedData = True
            else:
                cachedData = False
            bottle.response.headers['Content-Type'] = 'application/json'
            return self.l3Report.generateReport(podId, cachedData)

        except Exception as e:
            raise bottle.HTTPError(404, exception=PodNotFound(podId, e))