Beispiel #1
0
def setup(args=None):
    """
    This is the main setup function to establish the TCP listening logic for
    the API server. This code also takes into account development or unit test mode.
    """

    # Setup API server
    api = ParadropAPIServer(reactor)
    api.putChild('internal', Base(apiinternal, allowNone=True))
    site = Site(api, timeout=None)

    # Development mode
    if(args and args.development):
        thePort = settings.PDFCD_PORT + 10000
        out.info('Using DEVELOPMENT variables')
        # Disable sending the error traceback to the client
        site.displayTracebacks = True
    elif(args and args.unittest):
        thePort = settings.PDFCD_PORT + 20000
        out.info('Running under unittest mode')
        site.displayTracebacks = True
    else:
        thePort = settings.PDFCD_PORT
        site.displayTracebacks = False
        initializeSystem()

    # Setup the port we listen on
    reactor.listenTCP(thePort, site)

    # Never return from here
    reactor.run()
Beispiel #2
0
 def release(self, item):
     out.info("Trying to release {} from pool {}\n".format(str(item), self.__class__.__name__))
     if item in self.used:
         self.used.remove(item)
         self.recentlyReleased.append(item)
     else:
         raise Exception("Trying to release unreserved item")
Beispiel #3
0
def failAndCleanUpDocker(validImages, validContainers):
    """
    Clean up any intermediate containers that may have resulted from a failure and throw an Exception so that
    the abort process is called.

    :param validImages: A list of dicts containing the Id's of all the images that should exist on the system.
    :type validImages: list
    :param validContainers: A list of the Id's of all the containers that should exist on the system.
    :type validContainers: list
    :returns: None
    """
    c = docker.Client(base_url="unix://var/run/docker.sock", version='auto')

    #Clean up containers from failed build/start
    currContainers = c.containers(quiet=True, all=True)
    for cntr in currContainers:
        if not cntr in validContainers:
            out.info('Removing Invalid container with id: %s' % str(cntr.get('Id')))
            c.remove_container(container=cntr.get('Id'))

    #Clean up images from failed build
    currImages = c.images(quiet=True, all=False)
    for img in currImages:
        if not img in validImages:
            out.info('Removing Invalid image with id: %s' % str(img))
            c.remove_image(image=img)
    #Throw exception so abort plan is called and user is notified
    raise Exception('Building or starting of docker image failed check your Dockerfile for errors.')
Beispiel #4
0
def castSuccess(res):
    out.info("Completed API call (TODO: add details)")

    # screen out Objectids on mongo returns. The remote objects have no
    # need for them, and they confuse xmlrpc
    if isinstance(res, dict):
        res.pop('_id', None)

    return res
Beispiel #5
0
 def GET_test(self, request):
     """
     A Simple test method to ping if the API server is working properly.
     """
     request.setHeader('Access-Control-Allow-Origin', settings.PDFCD_HEADER_VALUE)
     ip = apiutils.getIP(request)
     out.info('Test called (%s)\n' % (ip))
     request.setResponseCode(*pdapi.getResponse(pdapi.OK))
     return "SUCCESS\n"
Beispiel #6
0
    def close(self):
        '''
        Close all connections in all realms. Stop all polling connections.
        '''

        out.info("Portal closing all connections")

        for k, v in self.realms.iteritems():
            for c in v.connections:
                c.destroy()
Beispiel #7
0
def getVirtPreamble(update):
    out.warn('TODO implement me\n')
    if update.updateType == 'create':
        if not hasattr(update, 'dockerfile'):
            return
        if update.dockerfile is None:
            return
        else:
            out.info('Using prexisting dockerfile.\n')
            update.dockerfile = BytesIO(update.dockerfile.encode('utf-8'))
Beispiel #8
0
def stopChute(update):
    """
    Stop a docker container based on the passed in update.

    :param update: The update object containing information about the chute.
    :type update: obj
    :returns: None
    """
    out.info('Attempting to stop chute %s\n' % (update.name))
    c = docker.Client(base_url='unix://var/run/docker.sock', version='auto')
    c.stop(container=update.name)
Beispiel #9
0
    def reserve(self, item, strict=True):
        """
        Mark item as used.

        If strict is True, raises an exception if the item is already used.
        """
        out.info("Trying to reserve {} from pool {}\n".format(str(item), self.__class__.__name__))
        if item in self.used:
            if strict:
                raise Exception("Trying to reserve a used item")
        else:
            self.used.add(item)
Beispiel #10
0
    def attach(self, avatar, mind):
        '''
        Completes the riffle association by attaching the avatar to its remote, adding
        it to the pool of connection stored here, and broadcasting the new connection.

        To listen for this 
        '''

        avatar.attached(mind)
        self.connections.add(avatar)
        out.info('Connected: ' + avatar.name)
        smokesignal.emit('%sConnected' % self.avatar.__name__, avatar, self)
Beispiel #11
0
def startChute(update):
    """
    Build and deploy a docker container based on the passed in update.

    :param update: The update object containing information about the chute.
    :type update: obj
    :returns: None
    """
    out.info('Attempting to start new Chute %s \n' % (update.name))

    repo = update.name + ":latest"
    dockerfile = update.dockerfile
    name = update.name

    host_config = build_host_config(update)

    c = docker.Client(base_url="unix://var/run/docker.sock", version='auto')

    #Get Id's of current images for comparison upon failure
    validImages = c.images(quiet=True, all=False)
    validContainers = c.containers(quiet=True, all=True)

    buildFailed = False
    for line in c.build(rm=True, tag=repo, fileobj=dockerfile):

        #if we encountered an error make note of it
        if 'errorDetail' in line:
            buildFailed = True

        for key, value in json.loads(line).iteritems():
            if isinstance(value, dict):
                continue
            elif key == 'stream':
                update.pkg.request.write(str(value))
            else:
                update.pkg.request.write(str(value) + '\n')

    #If we failed to build skip creating and starting clean up and fail
    if buildFailed:
        failAndCleanUpDocker(validImages, validContainers)

    try:
        container = c.create_container(
            image=repo, name=name, host_config=host_config
        )
        c.start(container.get('Id'))
        out.info("Successfully started chute with Id: %s\n" % (str(container.get('Id'))))
    except Exception as e:
        failAndCleanUpDocker(validImages, validContainers)
    

    setup_net_interfaces(update)
Beispiel #12
0
    def readConfig(self, files):
        """
        Load configuration files and return configuration objects.

        This method only loads the configuration files without making any
        changes to the system and returns configuration objects as a generator.
        """
        # Keep track of headers (section type and name) that have been
        # processed so far.  The dictionary maps them to filename, so that we
        # can print a useful warning message about duplicates.
        usedHeaders = dict()

        for fn in files:
            out.info("Reading file {}\n".format(fn))

            uci = UCIConfig(fn)
            config = uci.readConfig()

            for section, options in config:
                # Sections differ in where they put the name, if they have one.
                if "name" in section:
                    name = section['name']
                elif "name" in options:
                    name = options['name']
                else:
                    name = None

                # Get section comment string (optional, but Paradop uses it).
                comment = section.get('comment', None)

                try:
                    cls = configTypeMap[section['type']]
                except:
                    out.warn("Unsupported section type {} in {}\n".format(
                        section['type'], fn))
                    continue

                try:
                    obj = cls.build(self, fn, name, options, comment)
                except:
                    out.warn("Error building object from section {}:{} in "
                             "{}\n".format(section['type'], name, fn))
                    continue

                key = obj.getTypeAndName()
                if key in usedHeaders:
                    out.warn("Section {}:{} from {} overrides section in "
                             "{}\n".format(section['type'], name, fn,
                                           usedHeaders[key]))
                usedHeaders[key] = fn

                yield obj
Beispiel #13
0
def restartChute(update):
    """
    Start a docker container based on the passed in update.

    :param update: The update object containing information about the chute.
    :type update: obj
    :returns: None
    """
    out.info('Attempting to restart chute %s\n' % (update.name))
    c = docker.Client(base_url='unix://var/run/docker.sock', version='auto')
    c.start(container=update.name)

    setup_net_interfaces(update)
Beispiel #14
0
    def onJoin(self, details):
        out.info(str(self.__class__.__name__) + ' crossbar session connected')
        yield

        # Inform whoever created us that the session has finished connecting.
        # Useful in situations where you need to fire off a single call and not a
        # full wamplet
        try:
            if self.dee is not None:
                yield self.dee.callback(self)
        except:
            # print 'No onJoin deferred callback set.'
            pass
Beispiel #15
0
    def performUpdates(self):
        """This is the main working function of the PDConfigurer class.
            It should be executed as a separate thread, it does the following:
                checks for any updates to perform
                does them
                responds to the server
                removes the update
                checks for more updates
                    if more exist it calls itself again more quickly
                    else it puts itself to sleep for a little while
        """

        #add any chutes that should already be running to the front of the update queue before processing any updates
        startQueue = reloadChutes()
        self.updateLock.acquire()
        # insert the data into the front of our update queue so that all old chutes restart befor new ones are processed
        for updateObj in startQueue:
            self.updateQueue.insert(0, updateObj)
        self.updateLock.release()

        # Always perform this work
        while(self.reactor.running):
            # Check for new updates
            updateObj = self.getNextUpdate()
            if(updateObj is None):
                time.sleep(1)
                continue

            try:
                # Take the object and identify the update type
                update = updateObject.parse(updateObj)
                out.info('Performing update %s\n' % (update))

                # TESTING start
                if(settings.FC_BOUNCE_UPDATE): # pragma: no cover
                    out.testing('Bouncing update %s, result: %s\n' % (
                        update, settings.FC_BOUNCE_UPDATE))
                    update.complete(success=True, message=settings.FC_BOUNCE_UPDATE)
                    continue
                # TESTING end

                # Based on each update type execute could be different
                update.execute()

            except Exception as e:
                out.exception(e, True)
Beispiel #16
0
def setConfig(chute, old, cacheKeys, filepath):
    """
    Helper function used to modify config file of each various setting in /etc/config/
    Returns:
        True: if it modified a file
        False: if it did NOT cause any modifications
    Raises exception if an error occurs.
    """
    # First pull out all the cache keys from the @new chute
    newconfigs = []
    for c in cacheKeys:
        t = chute.getCache(c)
        if(t):
            newconfigs += t

    if(len(newconfigs) == 0):
        out.info('no settings to add %r\n' % (chute))
        # We are no longer returning because we need to remove the old configs if necessary
        # return False

    # add comment to each config so we can differentiate between different chute specific configs
    for e in newconfigs:
        c, o = e
        c['comment'] = chute.name

    # Get the old configs from the file for this chuteid

    # Find the config file
    cfgFile = uci.UCIConfig(filepath)

    # Get all the configs that existed in the old version
    # Note we are getting the old configs from the etc/config/ file instead of the chute object
    # This is to improve reliability  - sometimes the file isn't changed it should be
    # because we have reset the board, messed with chutes, etc. and the new/old chuteobjects are identical
    oldconfigs = cfgFile.getChuteConfigs(chute.name)

    if (uci.chuteConfigsMatch(oldconfigs, newconfigs)):
        # configs match, skipping reloading
        return False
    else:
        # We need to make changes so delete old configs, load new configs
        # configs don't match, changing chutes and reloading
        cfgFile.delConfigs(oldconfigs)
        cfgFile.addConfigs(newconfigs)
        cfgFile.save(backupToken="paradrop", internalid=chute.name)
        return True
Beispiel #17
0
    def saveToDisk(self):
        """Saves the data to disk."""
        out.info("Saving to disk (%s)\n" % (self.filename))

        # Make sure they want to save
        if not self.attrSaveable():
            return

        # Get whatever the data is
        pyld = self.exportAttr(self.getAttr())

        # Write the file to disk, truncate if it exists
        try:
            pickle.dump(pyld, pdos.open(self.filename, "wb"))
            pdos.syncFS()

        except Exception as e:
            out.err("Error writing to disk %s\n" % (str(e)))
Beispiel #18
0
def removeChute(update):
    """
    Remove a docker container and the image it was built on based on the passed in update.

    :param update: The update object containing information about the chute.
    :type update: obj
    :returns: None
    """
    out.info('Attempting to remove chute %s\n' % (update.name))
    c = docker.Client(base_url='unix://var/run/docker.sock', version='auto')
    repo = update.name + ":latest"
    name = update.name
    try:
        c.remove_container(container=name, force=True)
        c.remove_image(image=repo)
    except Exception as e:
        #TODO: Might want to notify ourselves we could have removed container but failed to remove image for a number of reasons
        update.complete(success=False, message=e.message)
        raise e
Beispiel #19
0
 def postprocess(self, request, key, failureDict, logUsage):
     """
         If the client is successful in their request, we should:
         * reset their failure attempts if failureDict is not none.
         * set success response code
         * If usage is not none, add usage track info of the api call
     """
     request.setResponseCode(*pdapi.getResponse(pdapi.OK))
     if(logUsage):
         tictoc, ip, devid = logUsage
         duration = 0  # self.perf.toc(tictoc)
         # when devid is none, we log "Null" into the database
         if(devid is None):
             devid = "Null"
         # Log the info of this call
         # TODO self.usageTracker.addTrackInfo(ip, devid, request.path, self.usageTracker.SUCCESS, duration, request.content.getvalue())
     if(failureDict is not None):
         if(key in failureDict):
             out.info('Clearing %s from failure list\n' % (key))
             del failureDict[key]
Beispiel #20
0
    def POST_stopChute(self, apiPkg):
        """
           Description:
           Arguments:
               POST request:
           Returns:
               On success: SUCCESS object
               On failure: FAILURE object
        """

        out.info('Stopping chute...')

        # For now fake out a create chute message
        update = dict(updateClass='CHUTE', updateType='stop', name=apiPkg.inputArgs.get('name'),
                      tok=timeint(), pkg=apiPkg, func=self.rest.complete)

        self.rest.configurer.updateList(**update)

        # Tell our system we aren't done yet (the configurer will deal with
        # closing the connection)
        apiPkg.setNotDoneYet()
Beispiel #21
0
    def execute(self):
        try:
            proc = subprocess.Popen(self.command, stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE)
            self.pid = proc.pid
            for line in proc.stdout:
                out.verbose("{}: {}".format(self.command[0], line))
            for line in proc.stderr:
                out.verbose("{}: {}".format(self.command[0], line))
            self.result = proc.wait()
            out.info('Command "{}" returned {}\n'.format(
                     " ".join(self.command), self.result))
        except Exception as e:
            out.info('Command "{}" raised exception {}\n'.format(
                     " ".join(self.command), e))
            self.result = e

        if self.parent is not None:
            self.parent.executed.append(self)

        return (self.result == 0)
Beispiel #22
0
def setup_net_interfaces(update):
    """
    Link interfaces in the host to the internal interface in the docker container using pipework.

    :param update: The update object containing information about the chute.
    :type update: obj
    :returns: None
    """
    interfaces = update.new.getCache('networkInterfaces')
    for iface in interfaces:
        if iface.get('netType') == 'wifi':
            IP = iface.get('ipaddrWithPrefix')
            internalIntf = iface.get('internalIntf')
            externalIntf = iface.get('externalIntf')
        else: # pragma: no cover
            continue

        # Construct environment for pipework call.  It only seems to require
        # the PATH variable to include the directory containing the docker
        # client.  On Snappy this was not happening by default, which is why
        # this code is here.
        env = {"PATH": os.environ.get("PATH", "")}
        if settings.DOCKER_BIN_DIR not in env['PATH']:
            env['PATH'] += ":" + settings.DOCKER_BIN_DIR

        cmd = ['/apps/paradrop/current/bin/pipework', externalIntf, '-i',
               internalIntf, update.name,  IP]
        out.info("Calling: {}\n".format(" ".join(cmd)))
        try:
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, env=env)
            for line in proc.stdout:
                out.info("pipework: {}\n".format(line.strip()))
            for line in proc.stderr:
                out.warn("pipework: {}\n".format(line.strip()))
        except OSError as e:
            out.warn('Command "{}" failed\n'.format(" ".join(cmd)))
            out.exception(e, True)
            raise e
Beispiel #23
0
    def loadFromDisk(self):
        """Attempts to load the data from disk.
            Returns True if success, False otherwise."""

        if pdos.exists(self.filename):
            deleteFile = False
            out.info("Loading from disk\n")
            data = ""
            try:
                pyld = pickle.load(pdos.open(self.filename, "rb"))
                self.setAttr(self.importAttr(pyld))
                return True
            except Exception as e:
                out.err("Error loading from disk: %s\n" % (str(e)))
                deleteFile = True

            # Delete the file
            if deleteFile:
                try:
                    pdos.unlink(self.filename)
                except Exception as e:
                    out.err("Error unlinking %s\n" % (self.filename))

        return False
Beispiel #24
0
    def next(self):
        if len(self.used) >= self.numValues:
            raise Exception("No items left in pool")

        while len(self.recentlyReleased) > 0:
            item = self.recentlyReleased.pop(0)
            if item not in self.used:
                out.info("Claiming recently released {} from pool {}\n".format(str(item), self.__class__.__name__))
                self.used.add(item)
                return item

        # The for loop puts a limit on the number of iterations that we spend
        # looking for a free subnet.  Passing the check above should imply that
        # there is at least one available item, but we want to be extra
        # careful to avoid a busy loop.
        for i in range(self.numValues):
            item = self.cycle.next()
            if item not in self.used:
                out.info("Claiming new item {} from pool {}\n".format(str(item), self.__class__.__name__))
                self.used.add(item)
                return item

        # There is a bug if we hit this line.
        raise Exception("No items left in pool (BUG)")
Beispiel #25
0
 def stockSubscribe(self, handler, topic=None, options=None):
     out.info('cxbr: (%s) subscribe (%s)' % (self.pdid, topic,))
     return ApplicationSession.subscribe(self, handler, topic=topic, options=options)
Beispiel #26
0
 def stockCall(self, pdid, procedure, *args, **kwargs):
     out.info('cxbr: (%s) calling (%s)' % (self.pdid, procedure,))
     return ApplicationSession.call(self, procedure, *args, **kwargs)
Beispiel #27
0
    def save(self, backupToken=None, internalid=None):
        """
            Saves out the file in the proper format.
            
            Arguments:
                [backupPath] : Save a backup copy of the UCI file to the path provided.
                                Should be a token name like 'backup', it gets appended with a hyphen.
        """
        # Save original copy
        if(backupToken):
            self.backup(backupToken)
      
        output = ""
        # Now generate what the file would look like
        for c, o in self.config:
            #print("c: %s\n" % c.keys())
            line = "config %s" % c['type']
            # Check for optional name
            if('name' in c.keys()):
                line += " %s" % c['name']
            if('comment' in c.keys()):
                line += " #%s" % c['comment']
            output += "%s\n" % line
            
            # Get options
            # check for lists first, if they exist remove them first
            if('list' in o.keys()):
                theLists = o['list']
            else:
                theLists = None

            # Now process everything else quick
            for k,v in o.iteritems():
                # Make sure we skip the lists key
                if(k != 'list'):
                    line = "\toption %s '%s'\n" % (k,v)
                    output += line
            
            # Now process the list
            if(theLists):
                # theLists is a dict where the key is each list name
                # and the value is a list of the options we need to include
                for k,v in theLists.iteritems():
                    # so @v here is a list
                    for vals in v:
                        # Now append a list set to the config
                        line = "\tlist %s '%s'\n" % (k, vals)
                        output += line

            # Now add one extra newline before the next set
            output += "\n"
        
        # Now write to disk
        try:
            out.info('Saving %s to disk\n' % (self.filepath))
            fd = pdos.open(self.filepath, 'w')
            fd.write(output)
            
            # Guarantee that its written to disk before we close
            fd.flush()
            os.fsync(fd.fileno())
            fd.close()
        except Exception as e:
            out.err('Unable to save new config %s, %s\n' % (self.filepath, str(e)))
            out.err('Config may be corrupted, backup exists at /tmp/%s\n' % (self.myname))
Beispiel #28
0
 def stockRegister(self, endpoint, procedure=None, options=None):
     out.info('cxbr: (%s) registering (%s)' % (self.pdid, procedure,))
     return ApplicationSession.register(self, endpoint, procedure=procedure, options=options)
Beispiel #29
0
def castFailure(failure):
    ''' Converts an exception (or general failure) into an xmlrpc fault for transmission. '''
    out.info("Failed API call (TODO: categorize errors)")

    raise xmlrpc.Fault(123, failure.getErrorMessage())
Beispiel #30
0
 def connectionClosed(self, avatar):
     out.info('Disconnected: ' + str(avatar.name))
     smokesignal.emit('%sDisconnected' % self.avatar.__name__, avatar, self)
     self.connections.remove(avatar)