def _run(nodes, get_command, env, progress_status):
	# nodes: dictionary of node names mapped to node objects
	# Node objects can be anything. They're just passed to the get_command function

	# get_command: function that takes a node object and returns a command to execute via Popen
	
	# env: name of the environment

	# progress_status: the status to broadcast to the websockets when the command is executing

	env_greenlets = greenlets.get(env)
	if env_greenlets is None:
		greenlets[env] = env_greenlets = { }

	for node in nodes:
		try:
			del env_greenlets[node]
		except KeyError:
			pass

	for hostname in nodes:
		node_object = nodes[hostname]
		p = subprocess.Popen(get_command(node_object), shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
		p.chunks = [] # Chunks of stdout data
		fcntl.fcntl(p.stdout, fcntl.F_SETFL, os.O_NONBLOCK) # make the file nonblocking

		def read(host, process):
			broadcast(env, { 'host': host, 'status': progress_status })

			while True:
				chunk = None
				try:
					chunk = process.stdout.read(4096)
					if not chunk:
						break

				except __HOLE__, e:
					chunk = None
					if e[0] != errno.EAGAIN:
						raise
					sys.exc_clear()

				if chunk:
					process.chunks.append(chunk)
					broadcast(env, { 'host': host, 'data': chunk, })

				gevent.socket.wait_read(process.stdout.fileno())

			process.stdout.close()

			process.wait()

			errors = process.stderr.read()
			process.chunks.append(errors)

			broadcast(env, { 'host': host, 'status': 'ready' if process.returncode == 0 else 'error', 'data': errors })

			if len(processes(env, only_executing = True)) <= 1:
				broadcast(env, { 'status': 'ready' })

			return process.returncode

		greenlet = gevent.spawn(read, host = hostname, process = p)
		greenlet.process = p
		env_greenlets[hostname] = greenlet

	broadcast(env, { 'status': progress_status })

	return ujson.encode({ 'status': progress_status if len(nodes) > 0 else 'ready' })
dataset/ETHPy150Open sidebolt/chefdash/chefdash/__init__.py/_run