def _setResourceAllocation(allocation): client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') for container_name, resources in six.iteritems(allocation): out.info("Update chute {} set cpu_shares={}\n".format( container_name, resources['cpu_shares'])) container = client.containers.get(container_name) container.update(cpu_shares=resources['cpu_shares']) # Using class id 1:1 for prioritized, 1:3 for best effort. # Prioritization is implemented in confd/qos.py. Class-ID is # represented in hexadecimal. # Reference: https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt if resources.get('prioritize_traffic', False): classid = "0x10001" else: classid = "0x10003" container = ChuteContainer(container_name) try: container_id = container.getID() fname = "/sys/fs/cgroup/net_cls/docker/{}/net_cls.classid".format( container_id) with open(fname, "w") as output: output.write(classid) except Exception as error: out.warn("Error setting traffic class: {}\n".format(error))
def onJoin(self, details): out.info('Router session joined') yield self.subscribe(self.updatesPending, self.uriPrefix + 'updatesPending') yield self.register(self.update, self.uriPrefix + 'update') yield BaseSession.onJoin(self, details)
def _setResourceAllocation(allocation): client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') for container_name, resources in six.iteritems(allocation): out.info("Update chute {} set cpu_shares={}\n".format( container_name, resources['cpu_shares'])) container = client.containers.get(container_name) container.update(cpu_shares=resources['cpu_shares']) # Using class id 1:1 for prioritized, 1:3 for best effort. # Prioritization is implemented in confd/qos.py. Class-ID is # represented in hexadecimal. # Reference: https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt if resources.get('prioritize_traffic', False): classid = "0x10001" else: classid = "0x10003" container = ChuteContainer(container_name) try: container_id = container.getID() fname = "/sys/fs/cgroup/net_cls/docker/{}/net_cls.classid".format(container_id) with open(fname, "w") as output: output.write(classid) except Exception as error: out.warn("Error setting traffic class: {}\n".format(error))
def onStop(self): if self.session is not None: self.session.leave() else: out.info('No WAMP session found!') super(Nexus, self).onStop()
def onConnect(self): out.info('Router session connected.') if (nexus.core.info.pdid and nexus.core.getKey('apitoken')): out.info('Starting WAMP-CRA authentication on realm "{}" as user "{}"...'\ .format(self.config.realm, nexus.core.info.pdid)) self.join(self.config.realm, [u'wampcra'], ensure_unicode(nexus.core.info.pdid))
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.DockerClient(base_url='unix://var/run/docker.sock', version='auto') repo = getImageName(update.old) name = update.name cleanup_net_interfaces(update.old) try: container = c.containers.get(name) container.remove(force=True) except Exception as e: update.progress(str(e)) try: c.images.remove(repo) except Exception as e: update.progress(str(e))
def onStart(self): super(Nexus, self).onStart() # onStart is called when the reactor starts, not when the connection is made. # Check for provisioning keys and attempt to connect if not self.provisioned() and not provisioning.can_provision(): out.warn("The node is not provisioned and is not configured to self-provision.") return while not self.provisioned(): yield provisioning.provision_self(self.update_manager) try: # Set up communication with pdserver. # 1. Create a report of the current system state and send that. # 2. Send the node public key. # 3. Poll for a list of updates that should be applied. # 4. Open WAMP session. yield sendStateReport() yield sendNodeIdentity() yield self.update_fetcher.start_polling() yield self.connect(WampSession) except Exception: out.warn('The router ID or password is invalid!') else: out.info('WAMP session is ready!')
def connectionMade(self): self.factory.transports.add(self.transport) out.info('sockjs /logs connected') self.log_provider = LogProvider(self.factory.chute) self.log_provider.attach() self.loop.start(1.0)
def call_retry(cmd, env, delay=3, tries=3): # Make sure each component of the command is a string. Otherwisew we will # get errors. clean_cmd = [str(v) for v in cmd] while tries >= 0: tries -= 1 out.info("Calling: {}\n".format(" ".join(clean_cmd))) try: proc = subprocess.Popen(clean_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) for line in proc.stdout: out.info("{}: {}\n".format(clean_cmd[0], line.strip())) for line in proc.stderr: out.warn("{}: {}\n".format(clean_cmd[0], line.strip())) return proc.returncode except OSError as e: out.warn('Command "{}" failed\n'.format(" ".join(clean_cmd))) if tries <= 0: out.exception(e, True) raise e time.sleep(delay)
def _update_complete(self, update): """ Internal: callback after an update operation has been completed (successfuly or otherwise) and send a notification to the server. """ # If delegated to an external program, we do not report to pdserver # that the update is complete. if update.delegated: return out.info("The update is done, report it to server...") update_id = update.external['update_id'] success = update.result.get('success', False) request = PDServerRequest('/api/routers/{router_id}/updates/' + str(update_id)) d = request.patch( { 'op': 'replace', 'path': '/completed', 'value': True }, { 'op': 'replace', 'path': '/success', 'value': success }) return d
def onConnect(self): out.info('Router session connected.') if (nexus.core.info.pdid and nexus.core.getKey('apitoken')): out.info('Starting WAMP-CRA authentication on realm "{}" as user "{}"...'\ .format(self.config.realm, nexus.core.info.pdid)) self.join(self.config.realm, [u'wampcra'], ensure_unicode(nexus.core.info.pdid))
def onStart(self): super(Nexus, self).onStart() # onStart is called when the reactor starts, not when the connection is made. # Check for provisioning keys and attempt to connect if not self.provisioned() and not provisioning.can_provision(): out.warn( "The node is not provisioned and is not configured to self-provision." ) return while not self.provisioned(): yield provisioning.provision_self(self.update_manager) try: # Set up communication with pdserver. # 1. Create a report of the current system state and send that. # 2. Send the node public key. # 3. Poll for a list of updates that should be applied. # 4. Open WAMP session. yield sendStateReport() yield sendNodeIdentity() yield self.update_fetcher.start_polling() yield self.connect(WampSession) except Exception: out.warn('The router ID or password is invalid!') else: out.info('WAMP session is ready!')
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], self.pid, line)) for line in proc.stderr: out.verbose("{} {}: {}".format(self.command[0], self.pid, line)) self.result = proc.wait() if self.result == 0: out.verbose('Command "{}" returned {}\n'.format( " ".join(self.command), self.result)) else: 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.ignoreFailure or self.result == 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], self.pid, line)) for line in proc.stderr: out.verbose("{} {}: {}".format(self.command[0], self.pid, line)) self.result = proc.wait() if self.result == 0: out.verbose('Command "{}" returned {}\n'.format( " ".join(self.command), self.result)) else: 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.ignoreFailure or self.result == 0)
def getVirtPreamble(update): """ Prepare various settings for Docker containers. """ extDataDir = settings.EXTERNAL_DATA_DIR.format(chute=update.new.name) extDataDir = os.path.expanduser(extDataDir) intDataDir = getattr(update.new, 'dataDir', settings.INTERNAL_DATA_DIR) extSystemDir = settings.EXTERNAL_SYSTEM_DIR.format(chute=update.new.name) extSystemDir = os.path.expanduser(extSystemDir) intSystemDir = getattr(update.new, 'systemDir', settings.INTERNAL_SYSTEM_DIR) volumes = { extDataDir: { 'bind': intDataDir, 'mode': 'rw' }, extSystemDir: { 'bind': intSystemDir, 'mode': 'ro' } } # Prepare port bindings while there might still be an old version of the # chute running so that we can inherit any dynamic port settings. for service in update.new.get_services(): bindings = dockerapi.prepare_port_bindings(service) update.cache_set("portBindings:{}".format(service.name), bindings) update.cache_set('volumes', volumes) update.cache_set('internalDataDir', intDataDir) update.cache_set('externalDataDir', extDataDir) update.cache_set('internalSystemDir', intSystemDir) update.cache_set('externalSystemDir', extSystemDir) # Reuse previous token if it exists. This is not ideal but works for # updates that do not recreate the container with updated environment # variables. Stop then start is one such sequence where environment # variables are not updated. token = None if update.old is not None: token = update.old.getCache('apiToken') if token is None: token = generateToken() update.cache_set('apiToken', token) # Deprecated: we set these values in the chute cache here because there is # still code that depends on reading this from the chute storage. This can # be removed when the corresponding call to getCache is removed. update.new.setCache('externalSystemDir', extSystemDir) update.new.setCache('apiToken', token) 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'))
def start_container(update, service): """ Start running a service in a new container. """ client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') container_name = service.get_container_name() image_name = service.get_image_name() out.info("Attempting to start container {} from image {}\n".format( container_name, image_name)) host_config = build_host_config(update, service) # TODO # Set environment variables for the new container. # PARADROP_ROUTER_ID can be used to change application behavior based on # what router it is running on. environment = prepare_environment(update, service) try: container = client.containers.run(detach=True, image=image_name, name=container_name, environment=environment, **host_config) out.info("Successfully started chute with Id: %s\n" % (str(container.id))) except Exception as e: raise e try: network = client.networks.get(update.new.name) network.connect(container_name, aliases=[service.name]) except docker.errors.NotFound: out.warn("Bridge network {} not found; connectivity between containers is limited.".format(update.new.name))
def _startChute(chute): """ Create a docker container based on the passed in chute object. """ out.info('Attempting to start new Chute %s \n' % (chute.name)) repo = getImageName(chute) name = chute.name c = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') host_config = build_host_config(chute) # Set environment variables for the new container. # PARADROP_ROUTER_ID can be used to change application behavior based on # what router it is running on. environment = prepare_environment(chute) try: container = c.containers.run(detach=True, image=repo, name=name, environment=environment, **host_config) out.info("Successfully started chute with Id: %s\n" % (str(container.id))) except Exception as e: raise e setup_net_interfaces(chute)
def _perform_update(self, update): """ Perform a single update, to be called by perform_updates. This is split from perform_updates for easier unit testing. """ try: # Mark update as having been started. update.started() out.info('Performing update %s\n' % (update)) # TESTING start # TODO: still need this? 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) return # TESTING end # Based on each update type execute could be different update.execute() except Exception as e: out.exception(e, True)
def getVirtPreamble(update): """ Prepare various settings for Docker containers. """ extDataDir = settings.EXTERNAL_DATA_DIR.format(chute=update.new.name) extDataDir = os.path.expanduser(extDataDir) intDataDir = getattr(update.new, 'dataDir', settings.INTERNAL_DATA_DIR) extSystemDir = settings.EXTERNAL_SYSTEM_DIR.format(chute=update.new.name) extSystemDir = os.path.expanduser(extSystemDir) intSystemDir = getattr(update.new, 'systemDir', settings.INTERNAL_SYSTEM_DIR) volumes = { extDataDir: { 'bind': intDataDir, 'mode': 'rw' }, extSystemDir: { 'bind': intSystemDir, 'mode': 'ro' } } # Prepare port bindings while there might still be an old version of the # chute running so that we can inherit any dynamic port settings. for service in update.new.get_services(): bindings = dockerapi.prepare_port_bindings(service) update.cache_set("portBindings:{}".format(service.name), bindings) update.cache_set('volumes', volumes) update.cache_set('internalDataDir', intDataDir) update.cache_set('externalDataDir', extDataDir) update.cache_set('internalSystemDir', intSystemDir) update.cache_set('externalSystemDir', extSystemDir) # Reuse previous token if it exists. This is not ideal but works for # updates that do not recreate the container with updated environment # variables. Stop then start is one such sequence where environment # variables are not updated. token = None if update.old is not None: token = update.old.getCache('apiToken') if token is None: token = generateToken() update.cache_set('apiToken', token) # Deprecated: we set these values in the chute cache here because there is # still code that depends on reading this from the chute storage. This can # be removed when the corresponding call to getCache is removed. update.new.setCache('externalSystemDir', extSystemDir) update.new.setCache('apiToken', token) 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'))
def onStop(self): if self.session is not None: self.session.leave() else: out.info('No WAMP session found!') super(Nexus, self).onStop()
def childDataReceived(self, childFd, data): if (childFd == 4): # Output of airshark analyzer #out.info(data) self.airshark_manager.on_analyzer_message(data) elif (childFd == 1 or childFd == 2): # stdout/stderr of airshark analyzer out.info('Airshark: ========\n%s==================' % data)
def decorated(self, request, *args, **kwargs): if not check_auth(request, self.password_manager, self.token_manager): out.info('HTTP {} {} {} {}'.format(request.getClientIP(), request.method, request.path, 401)) request.setResponseCode(401) request.setHeader("WWW-Authenticate", "Basic realm=\"Login Required\"") return return func(self, request, *args, **kwargs)
def connectionLost(self, reason): if self.transport in self.factory.transports: self.factory.transports.remove(self.transport) out.info('sockjs /logs disconnected') self.loop.stop() self.log_provider.detach() self.log_provider = None
def decorated(self, request, *args, **kwargs): if not check_auth(request, self.password_manager, self.token_manager): out.info('HTTP {} {} {} {}'.format(request.getClientIP(), request.method, request.path, 401)) request.setResponseCode(401) request.setHeader("WWW-Authenticate", "Basic realm=\"Login Required\"") return return func(self, request, *args, **kwargs)
def buildImage(update): """ Build the Docker image and monitor progress. """ out.info('Building image for {}\n'.format(update.new)) client = docker.APIClient(base_url="unix://var/run/docker.sock", version='auto') repo = getImageName(update.new) if hasattr(update.new, 'external_image'): # If the pull fails, we will fall through and attempt a local build. # Be aware, in that case, the image will be tagged as if it came from # the registry (e.g. registry.exis.io/image) but will have a different # image id from the published version. The build should be effectively # the same, though. pulled = _pullImage(update, client) if pulled: return None else: update.progress("Pull failed, attempting a local build.") if hasattr(update, 'dockerfile'): buildSuccess = _buildImage(update, client, True, rm=True, tag=repo, fileobj=update.dockerfile) elif hasattr(update, 'download'): # download field should be an object with at least 'url' but may also # contain 'user' and 'secret' for authentication. download_args = update.download with downloader(**download_args) as dl: workDir, meta = dl.fetch() buildSuccess = _buildImage(update, client, False, rm=True, tag=repo, path=workDir) elif hasattr(update, 'workdir'): buildSuccess = _buildImage(update, client, False, rm=True, tag=repo, path=update.workdir) else: raise Exception("No Dockerfile or download location supplied.") #If we failed to build skip creating and starting clean up and fail if not buildSuccess: raise Exception( "Building docker image failed; check your Dockerfile for errors.")
def wait_for_zerotier(max_delay=120): """ Wait for ZeroTier to start up and create the authtoken file. """ path = os.path.join(settings.ZEROTIER_LIB_DIR, "authtoken.secret") if not os.path.isfile(path): out.info("Waiting up to {} seconds for ZeroTier authtoken\n".format(max_delay)) while not os.path.isfile(path): time.sleep(1)
def wait_for_zerotier(max_delay=120): """ Wait for ZeroTier to start up and create the authtoken file. """ path = os.path.join(settings.ZEROTIER_LIB_DIR, "authtoken.secret") if not os.path.isfile(path): out.info("Waiting up to {} seconds for ZeroTier authtoken\n".format(max_delay)) while not os.path.isfile(path): time.sleep(1)
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.DockerClient(base_url='unix://var/run/docker.sock', version='auto') container = c.containers.get(update.name) container.start()
def _start(self): self.scanner.cmd_chanscan() self.scanner.start() if not self.analyzer_process.isRunning(): out.info("Launching airshark analyzer") cmd = [settings.AIRSHARK_INSTALL_DIR + "/analyzer", settings.AIRSHARK_INSTALL_DIR + "/specshape/specshape_v1_722N_5m.txt", "--spectrum-fd=3", "--output-fd=4"] spawnProcess(self.analyzer_process,\ cmd[0], cmd, env=None, \ childFDs={0:"w", 1:"r", 2:2, 3:"w", 4:"r"}) self.loop.start(0.2) return True
def _start(self): self.scanner.cmd_chanscan() self.scanner.start() if not self.analyzer_process.isRunning(): out.info("Launching airshark analyzer") cmd = [settings.AIRSHARK_INSTALL_DIR + "/analyzer", settings.AIRSHARK_INSTALL_DIR + "/specshape/specshape_v1_722N_5m.txt", "--spectrum-fd=3", "--output-fd=4"] spawnProcess(self.analyzer_process,\ cmd[0], cmd, env=None, \ childFDs={0:"w", 1:"r", 2:2, 3:"w", 4:"r"}) self.loop.start(0.2) return True
def _removeImage(chute): """ Remove the image for a chute. """ image = getImageName(chute) out.info("Removing image {}\n".format(image)) try: client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') client.images.remove(image=image) except Exception as error: out.warn("Error removing image: {}".format(error))
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.DockerClient(base_url='unix://var/run/docker.sock', version='auto') container = c.containers.get(update.name) container.start()
def remove_image(update, service): """ Remove a Docker image. """ client = docker.APIClient(base_url="unix://var/run/docker.sock", version='auto') image_name = service.get_image_name() out.info("Removing image {}\n".format(image_name)) try: client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') client.images.remove(image=image_name) except Exception as error: out.warn("Error removing image: {}".format(error))
def _perform_update(self, update): """ Perform a single update, to be called by perform_updates. This is split from perform_updates for easier unit testing. """ # Add to the active set when processing starts. It is a dictionary, so # it does not matter if this is the first time we see this update or # if we are resuming it. self.active_changes[update.change_id] = update try: # Mark update as having been started. update.started() out.info('Performing update %s\n' % (update)) # TESTING start # TODO: still need this? 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) return # TESTING end # Based on each update type execute could be different result = update.execute() if isinstance(result, defer.Deferred): # Update is not done, but it is yielding. When the deferred # fires, add it back to the work queue to resume it. # # TODO: We could handle errors separately and use that to break # the update pipeline, but then we need to propagate that # through the update.execute, executionplan chain. For now, # we have an update stage right after prepare_image that checks # if the build was successful or throws an exception. That # should work but is not very general. def resume(result): self.updateQueue.append(update) result.addBoth(resume) elif update.change_id in self.active_changes: # Update is done, so remove it from the active list. del self.active_changes[update.change_id] except Exception as e: out.exception(e, True)
def getVirtPreamble(update): extDataDir = settings.EXTERNAL_DATA_DIR.format(chute=update.new.name) intDataDir = getattr(update.new, 'dataDir', settings.INTERNAL_DATA_DIR) extSystemDir = settings.EXTERNAL_SYSTEM_DIR.format(chute=update.new.name) intSystemDir = getattr(update.new, 'systemDir', settings.INTERNAL_SYSTEM_DIR) volumes = { extDataDir: { 'bind': intDataDir, 'mode': 'rw' }, extSystemDir: { 'bind': intSystemDir, 'mode': 'ro' } } # Prepare port bindings while there might still be an old version of the # chute running so that we can inherit any dynamic port settings. bindings = dockerapi.prepare_port_bindings(update.new) update.new.setCache('volumes', volumes) update.new.setCache('internalDataDir', intDataDir) update.new.setCache('externalDataDir', extDataDir) update.new.setCache('internalSystemDir', intSystemDir) update.new.setCache('externalSystemDir', extSystemDir) update.new.setCache('portBindings', bindings) # Reuse previous token if it exists. This is not ideal but works for # updates that do not recreate the container with updated environment # variables. Stop then start is one such sequence where environment # variables are not updated. token = None if update.old is not None: token = update.old.getCache('apiToken') if token is None: token = generateToken() update.new.setCache('apiToken', token) 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'))
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 # Save a backup in case we need to restore. cfgFile.backup(backupToken="paradrop") 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
def execute(self): pid = self.getPid() if pid is None: self.result = 0 return True try: retval = kill(pid) self.result = 0 out.info('Command "kill {}" returned {}\n'.format(pid, retval)) except Exception as e: out.info('Command "kill {}" raised exception {}\n'.format(pid, e)) self.result = e return (self.result == 0)
def remove_image(update, service): """ Remove a Docker image. """ client = docker.APIClient(base_url="unix://var/run/docker.sock", version='auto') image_name = service.get_image_name() out.info("Removing image {}\n".format(image_name)) try: client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') client.images.remove(image=image_name) except Exception as error: out.warn("Error removing image: {}".format(error))
def execute(self): pid = self.getPid() if pid is None: self.result = 0 return True try: retval = kill(pid) self.result = 0 out.info('Command "kill {}" returned {}\n'.format(pid, retval)) except Exception as e: out.info('Command "kill {}" raised exception {}\n'.format(pid, e)) self.result = e return (self.result == 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)) cleanup_net_interfaces(update.old) c = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') container = c.containers.get(update.name) container.stop()
def _perform_update(self, update): """ Perform a single update, to be called by perform_updates. This is split from perform_updates for easier unit testing. """ # Add to the active set when processing starts. It is a dictionary, so # it does not matter if this is the first time we see this update or # if we are resuming it. self.active_changes[update.change_id] = update try: # Mark update as having been started. update.started() out.info('Performing update %s\n' % (update)) # TESTING start # TODO: still need this? 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) return # TESTING end # Based on each update type execute could be different result = update.execute() if isinstance(result, defer.Deferred): # Update is not done, but it is yielding. When the deferred # fires, add it back to the work queue to resume it. # # TODO: We could handle errors separately and use that to break # the update pipeline, but then we need to propagate that # through the update.execute, executionplan chain. For now, # we have an update stage right after prepare_image that checks # if the build was successful or throws an exception. That # should work but is not very general. def resume(result): self.updateQueue.append(update) result.addBoth(resume) elif update.change_id in self.active_changes: # Update is done, so remove it from the active list. del self.active_changes[update.change_id] except Exception as e: out.exception(e, True)
def remove_container(update, service): """ Remove a service's container. """ container_name = service.get_container_name() out.info("Removing container {}\n".format(container_name)) try: client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') # Grab the last 40 log messages to help with debugging. container = client.containers.get(container_name) logs = container.logs(stream=False, tail=40, timestamps=False) update.progress("{}: {}".format(container_name, logs.rstrip())) container.remove(force=True) except Exception as error: out.warn("Error removing container: {}".format(error))
def remove_container(update, service): """ Remove a service's container. """ container_name = service.get_container_name() out.info("Removing container {}\n".format(container_name)) try: client = docker.DockerClient(base_url="unix://var/run/docker.sock", version='auto') # Grab the last 40 log messages to help with debugging. container = client.containers.get(container_name) logs = container.logs(stream=False, tail=40, timestamps=False) update.progress("{}: {}".format(container_name, logs.rstrip())) container.remove(force=True) except Exception as error: out.warn("Error removing container: {}".format(error))
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: with open(self.filename, 'wb') as output: pickle.dump(pyld, output) os.fsync(output.fileno()) except Exception as e: out.err('Error writing to disk %s\n' % (str(e)))
def removeOldContainer(update): """ Remove the docker container for the old version of a chute. :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)) cleanup_net_interfaces(update.old) client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') try: container = client.containers.get(update.old.name) container.remove(force=True) except Exception as e: update.progress(str(e))
def _maybeWait(self, result): """ Wait for completed result if configured to wait on async calls. """ if result['type'] == 'sync' or not self.wait_async: return result['result'] if result['type'] == 'error': result = result['result'] if self.logging: out.warn("snapd error: {}".format(result['message'])) return result while True: change = self.get_change(result['change']) if change['ready']: if self.logging: out.info("{}: {}".format(change['summary'], change['status'])) return change time.sleep(1)
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: with open(self.filename, 'wb') as output: pickle.dump(pyld, output) os.fsync(output.fileno()) except Exception as e: out.err('Error writing to disk %s\n' % (str(e))) try: with open(self.filename + ".yaml", "w") as output: yaml.dump(pyld, output) except Exception as error: out.err("Error writing yaml file: {}".format(error))
def call_retry(cmd, env, delay=3, tries=3): # Make sure each component of the command is a string. Otherwisew we will # get errors. clean_cmd = [str(v) for v in cmd] while tries >= 0: tries -= 1 out.info("Calling: {}\n".format(" ".join(clean_cmd))) try: proc = subprocess.Popen(clean_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) for line in proc.stdout: out.info("{}: {}\n".format(clean_cmd[0], line.strip())) for line in proc.stderr: out.warn("{}: {}\n".format(clean_cmd[0], line.strip())) return proc.returncode except OSError as e: out.warn('Command "{}" failed\n'.format(" ".join(clean_cmd))) if tries <= 0: out.exception(e, True) raise e time.sleep(delay)
def onLeave(self, details): out.info("Router session left: {}".format(details)) nexus.core.wamp_connected = False self.disconnect()
def onClose(self, wasClean, code, reason): out.info('ws /airshark/analyzer disconnected: {}'.format(reason)) self.factory.airshark_manager.remove_analyzer_observer(self)
def onOpen(self): out.info('ws /airshark/analyzer connected') self.factory.airshark_manager.add_analyzer_observer(self)
def onOpen(self): out.info('ws /airshark/spectrum connected') self.factory.airshark_manager.add_spectrum_observer(self)
def save(self, backupToken="paradrop", 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 = "" output += "#" * 80 + "\n" output += "# Configuration file generated by paradrop-daemon\n".format(self.myname) output += "# Path: {}\n".format(self.filepath) output += "# Package: {}\n".format(self.myname) output += "#" * 80 + "\n" output += "\n" # Now generate what the file would look like for c, o in self.config: line = "config %s" % c['type'] # Check for optional name if 'name' in c: line += " %s" % c['name'] if 'comment' in c: line += " #%s" % c['comment'] output += "%s\n" % line for k, v in six.iteritems(o): if isinstance(v, list): # For list-valued options, emit one line for each item # in the list. for vals in v: # Now append a list set to the config line = "\tlist %s '%s'\n" % (k, vals) output += line # Skip options that are None rather than writing "None". elif v is not None: sv = stringifyOptionValue(v) line = "\toption %s '%s'\n" % (k, sv) 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))
def updatesPending(self, pdid): out.info('Notified of updates...') if WampSession.update_fetcher is not None: out.info('Pulling updates from the server...') yield WampSession.update_fetcher.pull_update()
def fulfillDeviceRequest(update, cfg, devices): """ Find a physical device that matches the requested device type. Raises an exception if one cannot be found. """ dtype, mode = split_interface_type(cfg['type']) # Get list of devices by requested type. devlist = devices.get(dtype, []) reservations = update.cache_get('deviceReservations') bestDevice = None bestScore = -1 for device in devlist: dname = device['name'] # If the configuration object includes a 'requests' object, then look # up configuration of the device and check additional constraints, e.g. # channel or hwmode. if 'requests' in cfg: phyconf = get_current_phy_conf(update, device['id']) if not satisfies_requirements(phyconf, cfg['requests']): continue # Monitor, station, and airshark mode interfaces require exclusive # access to the device. if dtype == "wifi" and mode in ["monitor", "sta", "airshark"]: if reservations[dname].count() > 0: continue # Choose the first one that matches. bestDevice = device break # AP mode interfaces can share a device, but not with monitor mode or # station mode. elif dtype == "wifi" and mode == "ap": if reservations[dname].count(mode="monitor") > 0: continue if reservations[dname].count(mode="sta") > 0: continue apcount = reservations[dname].count(mode="ap") # Avoid exceeding the max. number of AP interfaces. if apcount >= MAX_AP_INTERFACES: continue # Otherwise, prefer interfaces that have at least one AP already. # This preference leaves interfaces available for other purposes # (e.g. monitor mode). if apcount > bestScore: bestDevice = device bestScore = apcount else: # Handle other devices types, namely "lan". Assume they require # exclusive access. if reservations[dname].count() > 0: continue else: bestDevice = device break if bestDevice is not None: out.info("Assign device {} for requested type {}".format( bestDevice['name'], dtype)) reservations[bestDevice['name']].add(update.new.name, dtype, mode) return bestDevice raise Exception( "Could not satisfy requirement for device of type {}.".format(dtype))
def onDisconnect(self): out.info("Router session disconnected.")
def onClose(self, wasClean, code, reason): out.info('ws /paradrop_logs disconnected: {}'.format(reason)) self.factory.removeParadropLogObserver(self)
def onOpen(self): out.info('ws /paradrop_logs connected') self.factory.addParadropLogObserver(self)
def onJoin(self, details): out.info('Router session joined') yield self.subscribe(self.updatesPending, self.uriPrefix + 'updatesPending') yield self.register(self.update, self.uriPrefix + 'update') yield BaseSession.onJoin(self, details)
def execute(self): out.info("An error occurred: {}".format(self.error))