Merge branch 'master' of ssh://git.cloud.com/var/lib/git/cloudstack-oss

This commit is contained in:
kelven 2010-08-12 11:07:19 -07:00
commit 64221eff7b
14 changed files with 397 additions and 40 deletions

2
.gitignore vendored
View File

@ -10,3 +10,5 @@ override
premium premium
.metadata .metadata
dist dist
*~
*.bak

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys, os, subprocess, errno, re, traceback import sys, os, subprocess, errno, re, traceback, getopt
# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ---- # ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ----
# ---- We do this so cloud_utils can be looked up in the following order: # ---- We do this so cloud_utils can be looked up in the following order:
@ -41,9 +41,30 @@ stderr("Welcome to the Cloud Agent setup")
stderr("") stderr("")
try: try:
# parse cmd line
opts, args = getopt.getopt(sys.argv[1:], "a", ["host=", "zone=", "pod=", "no-kvm"])
host=None
zone=None
pod=None
stderr(str(opts))
autoMode=False
do_check_kvm = True
for opt, arg in opts:
if opt == "--host":
if arg != "":
host = arg
elif opt == "--zone":
if arg != "":
zone = arg
elif opt == "--pod":
if arg != "":
pod = arg
elif opt == "--no-kvm":
do_check_kvm = False
elif opt == "-a":
autoMode=True
# pre-flight checks for things that the administrator must fix # pre-flight checks for things that the administrator must fix
do_check_kvm = not ( "--no-kvm" in sys.argv[1:] )
try: try:
for f,n in cloud_utils.preflight_checks( for f,n in cloud_utils.preflight_checks(
do_check_kvm=do_check_kvm do_check_kvm=do_check_kvm
@ -59,6 +80,8 @@ try:
try: try:
tasks = cloud_utils.config_tasks(brname) tasks = cloud_utils.config_tasks(brname)
for t in tasks:
t.setAutoMode(autoMode)
if all( [ t.done() for t in tasks ] ): if all( [ t.done() for t in tasks ] ):
stderr("All configuration tasks have been performed already") stderr("All configuration tasks have been performed already")
@ -83,7 +106,7 @@ try:
stderr(str(e)) stderr(str(e))
bail(cloud_utils.E_SETUPFAILED,"Cloud Agent setup failed") bail(cloud_utils.E_SETUPFAILED,"Cloud Agent setup failed")
setup_agent_config(configfile) setup_agent_config(configfile, host, zone, pod)
stderr("Enabling and starting the Cloud Agent") stderr("Enabling and starting the Cloud Agent")
stop_service(servicename) stop_service(servicename)
enable_service(servicename) enable_service(servicename)

View File

@ -35,7 +35,7 @@
<import file="${my.build.dir}/build-cloud.xml" optional="false"/> <import file="${my.build.dir}/build-cloud.xml" optional="false"/>
<import file="${my.build.dir}/build-docs.xml" optional="true"/> <import file="${my.build.dir}/build-docs.xml" optional="true"/>
<import file="${my.build.dir}/build-test.xml" optional="true"/> <import file="${my.build.dir}/build-tests.xml" optional="true"/>
<import file="${my.build.dir}/package.xml" optional="true"/> <import file="${my.build.dir}/package.xml" optional="true"/>
<import file="${my.build.dir}/developer.xml" optional="true"/> <import file="${my.build.dir}/developer.xml" optional="true"/>
</project> </project>

View File

@ -529,7 +529,7 @@
<!-- create a UTC build timestamp using ISO 8601 formatting --> <!-- create a UTC build timestamp using ISO 8601 formatting -->
<tstamp> <tstamp>
<format property="utc.build.timestamp" pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" timezone="GMT" /> <format property="utc.build.timestamp" pattern="yyyy-MM-dd'T'HH:mm:ss'Z'" timezone="GMT" />
</tstamp> </tstamp>
<!-- remember who/where did the build --> <!-- remember who/where did the build -->
@ -541,7 +541,7 @@
<!-- set build.number property, stored in eponymous file --> <!-- set build.number property, stored in eponymous file -->
<buildnumber file="${build.dir}/build.number" /> <buildnumber file="${build.dir}/build.number" />
<condition property="impl.version" value="${version}.${manual.build.number}" else="${version}.${build.number}"> <condition property="impl.version" value="${version}.${manual.build.number}" else="${version}.${utc.build.timestamp}">
<isset property="manual.build.number"/> <isset property="manual.build.number"/>
</condition> </condition>

View File

@ -44,6 +44,7 @@ intelligent cloud implementation.
%package utils %package utils
Summary: Cloud.com utility library Summary: Cloud.com utility library
Requires: java >= 1.6.0 Requires: java >= 1.6.0
Requires: python
Group: System Environment/Libraries Group: System Environment/Libraries
Obsoletes: vmops-utils < %{version}-%{release} Obsoletes: vmops-utils < %{version}-%{release}
%description utils %description utils
@ -447,6 +448,8 @@ fi
%defattr(0644,root,root,0755) %defattr(0644,root,root,0755)
%{_javadir}/%{name}-utils.jar %{_javadir}/%{name}-utils.jar
%{_javadir}/%{name}-api.jar %{_javadir}/%{name}-api.jar
%attr(755,root,root) %{_bindir}/cloud-sccs
%attr(755,root,root) %{_bindir}/cloud-gitrevs
%doc %{_docdir}/%{name}-%{version}/sccs-info %doc %{_docdir}/%{name}-%{version}/sccs-info
%doc %{_docdir}/%{name}-%{version}/version-info %doc %{_docdir}/%{name}-%{version}/version-info
%doc %{_docdir}/%{name}-%{version}/configure-info %doc %{_docdir}/%{name}-%{version}/configure-info

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
import sys, os, subprocess, errno, re import sys, os, subprocess, errno, re, getopt
# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ---- # ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ----
# ---- We do this so cloud_utils can be looked up in the following order: # ---- We do this so cloud_utils can be looked up in the following order:
@ -132,7 +132,22 @@ CentOS = os.path.exists("/etc/centos-release") or ( os.path.exists("/etc/redhat-
#--------------- procedure starts here ------------ #--------------- procedure starts here ------------
def main(): def main():
# parse cmd line
opts, args = getopt.getopt(sys.argv[1:], "", ["host=", "zone=", "pod="])
host=None
zone=None
pod=None
do_check_kvm = True
for opt, arg in opts:
if opt == "--host":
if arg != "":
host = arg
elif opt == "--zone":
if arg != "":
zone = arg
elif opt == "--pod":
if arg != "":
pod = arg
servicename = "@PACKAGE@-console-proxy" servicename = "@PACKAGE@-console-proxy"
stderr("Welcome to the Cloud Console Proxy setup") stderr("Welcome to the Cloud Console Proxy setup")
@ -176,7 +191,7 @@ def main():
print e.stdout+e.stderr print e.stdout+e.stderr
bail(E_FWRECONFIGFAILED,"Firewall could not be enabled") bail(E_FWRECONFIGFAILED,"Firewall could not be enabled")
cloud_utils.setup_consoleproxy_config("@CPSYSCONFDIR@/agent.properties") cloud_utils.setup_consoleproxy_config("@CPSYSCONFDIR@/agent.properties", host, zone, pod)
stderr("Enabling and starting the Cloud Console Proxy") stderr("Enabling and starting the Cloud Console Proxy")
cloud_utils.enable_service(servicename) cloud_utils.enable_service(servicename)
stderr("Cloud Console Proxy restarted") stderr("Cloud Console Proxy restarted")

View File

@ -3,3 +3,5 @@
/usr/share/doc/cloud/sccs-info /usr/share/doc/cloud/sccs-info
/usr/share/doc/cloud/version-info /usr/share/doc/cloud/version-info
/usr/share/doc/cloud/configure-info /usr/share/doc/cloud/configure-info
/usr/bin/cloud-sccs
/usr/bin/cloud-gitrevs

2
debian/control vendored
View File

@ -22,7 +22,7 @@ Provides: vmops-utils
Conflicts: vmops-utils Conflicts: vmops-utils
Replaces: vmops-utils Replaces: vmops-utils
Architecture: any Architecture: any
Depends: openjdk-6-jre Depends: openjdk-6-jre, python
Description: Cloud.com utility library Description: Cloud.com utility library
The Cloud.com utility libraries provide a set of Java classes used The Cloud.com utility libraries provide a set of Java classes used
in the Cloud.com Cloud Stack. in the Cloud.com Cloud Stack.

View File

@ -325,6 +325,7 @@ class TaskFailed(Exception): pass
class ConfigTask: class ConfigTask:
name = "generic config task" name = "generic config task"
autoMode=False
def __init__(self): pass def __init__(self): pass
def done(self): def done(self):
"""Returns true if the config task has already been done in the past, false if it hasn't""" """Returns true if the config task has already been done in the past, false if it hasn't"""
@ -342,6 +343,10 @@ class ConfigTask:
else: else:
for msg in it: stderr(msg) for msg in it: stderr(msg)
stderr("Completed %s"%self.name) stderr("Completed %s"%self.name)
def setAutoMode(self, autoMode):
self.autoMode = autoMode
def isAutoMode(self):
return self.autoMode
# ============== these are some configuration tasks ================== # ============== these are some configuration tasks ==================
@ -551,7 +556,9 @@ save"""%(automatic,self.brname,inconfigfile,self.brname,inconfigfile,dev)
raise TaskFailed("Network reconfiguration failed") raise TaskFailed("Network reconfiguration failed")
yield "We are going to restart network services now, to make the network changes take effect. Hit ENTER when you are ready." yield "We are going to restart network services now, to make the network changes take effect. Hit ENTER when you are ready."
raw_input() if self.isAutoMode(): pass
else:
raw_input()
# if we reach here, then if something goes wrong we should attempt to revert the runinng state # if we reach here, then if something goes wrong we should attempt to revert the runinng state
# if not, then no point # if not, then no point
@ -900,7 +907,7 @@ def prompt_for_hostpods(zonespods):
# this configures the agent # this configures the agent
def setup_agent_config(configfile): def setup_agent_config(configfile, host, zone, pod):
stderr("Examining Agent configuration") stderr("Examining Agent configuration")
fn = configfile fn = configfile
text = file(fn).read(-1) text = file(fn).read(-1)
@ -912,23 +919,29 @@ def setup_agent_config(configfile):
stderr("Generating GUID for this Agent") stderr("Generating GUID for this Agent")
confopts['guid'] = uuidgen().stdout.strip() confopts['guid'] = uuidgen().stdout.strip()
try: host = confopts["host"] if host == None:
except KeyError: host = "localhost" try: host = confopts["host"]
stderr("Please enter the host name of the management server that this agent will connect to: (just hit ENTER to go with %s)",host) except KeyError: host = "localhost"
newhost = raw_input().strip() stderr("Please enter the host name of the management server that this agent will connect to: (just hit ENTER to go with %s)",host)
if newhost: host = newhost newhost = raw_input().strip()
if newhost: host = newhost
confopts["host"] = host confopts["host"] = host
stderr("Querying %s for zones and pods",host) stderr("Querying %s for zones and pods",host)
try: try:
x = list_zonespods(confopts['host']) if zone == None or pod == None:
zoneandpod = prompt_for_hostpods(x) x = list_zonespods(confopts['host'])
if zoneandpod: zoneandpod = prompt_for_hostpods(x)
confopts["zone"],confopts["pod"] = zoneandpod if zoneandpod:
stderr("You selected zone %s pod %s",confopts["zone"],confopts["pod"]) confopts["zone"],confopts["pod"] = zoneandpod
stderr("You selected zone %s pod %s",confopts["zone"],confopts["pod"])
else:
stderr("Skipped -- using the previous zone %s pod %s",confopts["zone"],confopts["pod"])
else: else:
stderr("Skipped -- using the previous zone %s pod %s",confopts["zone"],confopts["pod"]) confopts["zone"] = zone
confopts["pod"] = pod
except (urllib2.URLError,urllib2.HTTPError),e: except (urllib2.URLError,urllib2.HTTPError),e:
stderr("Query failed: %s. Defaulting to zone %s pod %s",confopts["zone"],confopts["pod"]) stderr("Query failed: %s. Defaulting to zone %s pod %s",confopts["zone"],confopts["pod"])
@ -940,7 +953,7 @@ def setup_agent_config(configfile):
text = "\n".join(lines) text = "\n".join(lines)
file(fn,"w").write(text) file(fn,"w").write(text)
def setup_consoleproxy_config(configfile): def setup_consoleproxy_config(configfile, host, zone, pod):
stderr("Examining Console Proxy configuration") stderr("Examining Console Proxy configuration")
fn = configfile fn = configfile
text = file(fn).read(-1) text = file(fn).read(-1)
@ -952,23 +965,28 @@ def setup_consoleproxy_config(configfile):
stderr("Generating GUID for this Console Proxy") stderr("Generating GUID for this Console Proxy")
confopts['guid'] = uuidgen().stdout.strip() confopts['guid'] = uuidgen().stdout.strip()
try: host = confopts["host"] if host == None:
except KeyError: host = "localhost" try: host = confopts["host"]
stderr("Please enter the host name of the management server that this console-proxy will connect to: (just hit ENTER to go with %s)",host) except KeyError: host = "localhost"
newhost = raw_input().strip() stderr("Please enter the host name of the management server that this console-proxy will connect to: (just hit ENTER to go with %s)",host)
if newhost: host = newhost newhost = raw_input().strip()
if newhost: host = newhost
confopts["host"] = host confopts["host"] = host
stderr("Querying %s for zones and pods",host) stderr("Querying %s for zones and pods",host)
try: try:
x = list_zonespods(confopts['host']) if zone == None or pod == None:
zoneandpod = prompt_for_hostpods(x) x = list_zonespods(confopts['host'])
if zoneandpod: zoneandpod = prompt_for_hostpods(x)
confopts["zone"],confopts["pod"] = zoneandpod if zoneandpod:
stderr("You selected zone %s pod %s",confopts["zone"],confopts["pod"]) confopts["zone"],confopts["pod"] = zoneandpod
stderr("You selected zone %s pod %s",confopts["zone"],confopts["pod"])
else:
stderr("Skipped -- using the previous zone %s pod %s",confopts["zone"],confopts["pod"])
else: else:
stderr("Skipped -- using the previous zone %s pod %s",confopts["zone"],confopts["pod"]) confopts["zone"] = zone
confopts["pod"] = pod
except (urllib2.URLError,urllib2.HTTPError),e: except (urllib2.URLError,urllib2.HTTPError),e:
stderr("Query failed: %s. Defaulting to zone %s pod %s",e,confopts["zone"],confopts["pod"]) stderr("Query failed: %s. Defaulting to zone %s pod %s",e,confopts["zone"],confopts["pod"])

View File

@ -0,0 +1,172 @@
#! /bin/bash
# Did cloud-agent installed
set -x
install_cloud_agent() {
local dev=$1
local retry=10
which cloud-setup-agent
if [ $? -gt 0 ]
then
# download the repo
which wget
if [ $? -gt 0 ]
then
yum install wget -y
if [ $? -gt 0 ]
then
printf "failed to install wget"
exit 1
fi
fi
wget -N -P /etc/yum.repos.d/ http://download.cloud.com/foss/fedora/cloud.repo
if [ $? -gt 0 ]
then
printf "Failed to download repo"
exit 1
fi
if [ "$dev" == "1" ]
then
sed -i 's/\(baseurl\)\(.*\)/\1=http:\/\/yumrepo.lab.vmops.com\/repositories\/fedora\/vmdev\/oss\//' /etc/yum.repos.d/cloud.repo
fi
while [ "$retry" -gt "0" ]
do
yum clean all
yum install cloud-agent -y
if [ $? -gt 0 ]
then
let retry=retry-1
else
break
fi
done
else
# is there update?
while [ "$retry" -gt "0" ]
do
yum clean all
yum update cloud-agent -y
if [ $? -gt 0 ]
then
let retry=retry-1
else
break
fi
done
fi
if [ $? -gt 0 ]
then
printf "Failed to install agent"
exit 2
fi
}
install_cloud_consoleP() {
local dev=$1
local retry=10
which cloud-setup-console-proxy
if [ $? -gt 0 ]
then
# download the repo
which wget
if [ $? -gt 0 ]
then
yum install wget -y
if [ $? -gt 0 ]
then
printf "failed to install wget"
exit 1
fi
fi
wget -N -P=/etc/yum.repos.d/ http://download.cloud.com/foss/fedora/cloud.repo
if [ $? -gt 0 ]
then
printf "Failed to download repo"
exit 1
fi
if [ "$dev" == "1" ]
then
sed -i 's/\(baseurl\)\(.*\)/\1=http:\/\/yumrepo.lab.vmops.com\/repositories\/fedora\/vmdev\/oss\//' /etc/yum.repos.d/cloud.repo
fi
while [ "$retry" -gt "0" ]
do
yum clean all
yum install cloud-console-proxy -y
if [ $? -gt 0 ]
then
let retry=retry-1
else
break
fi
done
else
# is there update?
while [ "$retry" -gt "0" ]
do
yum clean all
yum update cloud-console-proxy -y
if [ $? -gt 0 ]
then
let retry=retry-1
else
break
fi
done
fi
if [ $? -gt 0 ]
then
printf "Failed to install console"
exit 2
fi
}
cloud_agent_setup() {
local host=$1
local zone=$2
local pod=$3
# disable selinux
selenabled=`cat /selinux/enforce`
if [ "$selenabled" == "1" ]
then
sed -i 's/\(SELINUX\)\(.*\)/\1=permissive/' /etc/selinux/config
setenforce 0
fi
cloud-setup-agent --host=$host --zone=$zone --pod=$pod -a
}
cloud_consoleP_setup() {
local host=$1
local zone=$2
local pod=$3
cloud-setup-console-proxy --host=$host --zone=$zone --pod=$pod
}
host=
zone=
pod=
dflag=
while getopts 'h:z:p:d' OPTION
do
case $OPTION in
h)
host="$OPTARG"
;;
z)
zone="$OPTARG"
;;
p)
pod="$OPTARG"
;;
d)
dflag=1
;;
*) ;;
esac
done
install_cloud_agent $dflag
install_cloud_consoleP $dflag
cloud_agent_setup $host $zone $pod
cloud_consoleP_setup $host $zone $pod

View File

@ -0,0 +1,86 @@
package com.cloud.hypervisor.kvm.discoverer;
import java.net.URI;
import java.util.List;
import java.util.Map;
import com.cloud.agent.Listener;
import com.cloud.agent.api.AgentControlAnswer;
import com.cloud.agent.api.AgentControlCommand;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.StartupCommand;
import com.cloud.exception.DiscoveryException;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.resource.Discoverer;
import com.cloud.resource.DiscovererBase;
import com.cloud.resource.ServerResource;
public class KvmServerDiscoverer extends DiscovererBase implements Discoverer,
Listener {
@Override
public boolean processAnswer(long agentId, long seq, Answer[] answers) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean processCommand(long agentId, long seq, Command[] commands) {
// TODO Auto-generated method stub
return false;
}
@Override
public AgentControlAnswer processControlCommand(long agentId,
AgentControlCommand cmd) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean processConnect(HostVO host, StartupCommand cmd) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean processDisconnect(long agentId, Status state) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRecurring() {
// TODO Auto-generated method stub
return false;
}
@Override
public int getTimeout() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean processTimeout(long agentId, long seq) {
// TODO Auto-generated method stub
return false;
}
@Override
public Map<? extends ServerResource, Map<String, String>> find(long dcId,
Long podId, Long clusterId, URI uri, String username,
String password) throws DiscoveryException {
// TODO Auto-generated method stub
return null;
}
@Override
public void postDiscovery(List<HostVO> hosts, long msId) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
import os, sys
path = os.path.join("@DOCDIR@","sccs-info")
try: text = file(path).read(-1)
except IOError,e:
if e.errno == 2:
sys.stderr.write("error: SCCS info file %r cannot be found\n"%path)
sys.exit(1)
else: raise
lines = [ s.strip() for s in text.split("\n") if s.startswith('Git Revision: ') ]
print " ".join( [ s[14:] for s in lines ] )

12
utils/bindir/cloud-sccs.in Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env python
import os, sys
path = os.path.join("@DOCDIR@","sccs-info")
try: text = file(path).read(-1)
except IOError,e:
if e.errno == 2:
sys.stderr.write("error: SCCS info file %r cannot be found\n"%path)
sys.exit(1)
else: raise
print text

19
wscript
View File

@ -144,8 +144,9 @@ def svninfo(*args):
else: url = "SVN " + url[0].strip() else: url = "SVN " + url[0].strip()
return rev + "\n" + url return rev + "\n" + url
def gitinfo(*args): def gitinfo(dir=None):
try: p = _Popen(['git','remote','show','-n','origin']+list(args),stdin=PIPE,stdout=PIPE,stderr=PIPE) if dir and not _isdir(dir): return ''
try: p = _Popen(['git','remote','show','-n','origin'],stdin=PIPE,stdout=PIPE,stderr=PIPE,cwd=dir)
except OSError,e: except OSError,e:
if e.errno == 2: return '' # svn command is not installed if e.errno == 2: return '' # svn command is not installed
raise raise
@ -158,7 +159,7 @@ def gitinfo(*args):
except IndexError: url = [ s[5:] for s in stdout if s.startswith("URL") ][0] except IndexError: url = [ s[5:] for s in stdout if s.startswith("URL") ][0]
assert url assert url
p = _Popen(['git','log','-1']+list(args),stdin=PIPE,stdout=PIPE,stderr=PIPE) p = _Popen(['git','log','-1'],stdin=PIPE,stdout=PIPE,stderr=PIPE,cwd=dir)
stdout,stderr = p.communicate('') stdout,stderr = p.communicate('')
retcode = p.wait() retcode = p.wait()
if retcode: return if retcode: return
@ -169,6 +170,15 @@ def gitinfo(*args):
return "Git Revision: %s"%commitid + "\n" + "Git URL: %s"%url return "Git Revision: %s"%commitid + "\n" + "Git URL: %s"%url
def allgitinfo():
t = gitinfo()
if not t: return t
u = gitinfo(_join(pardir,"cloudstack-proprietary"))
if not u: return t
return t + "\n\ncloustack-proprietary:\n" + u
def _getbuildnumber(): # FIXME implement for git def _getbuildnumber(): # FIXME implement for git
n = Options.options.BUILDNUMBER n = Options.options.BUILDNUMBER
if n: if n:
@ -230,6 +240,7 @@ def discover_ant_targets_and_properties(files):
propsinpropfiles = [ l.strip().split("=",1) for f in files if f.endswith(".properties") for l in file(f).readlines() if "=" in l and not l.startswith("#") ] propsinpropfiles = [ l.strip().split("=",1) for f in files if f.endswith(".properties") for l in file(f).readlines() if "=" in l and not l.startswith("#") ]
props = dict( propsinxml + propsinpropfiles ) props = dict( propsinxml + propsinpropfiles )
props["base.dir"] = '.' props["base.dir"] = '.'
props["p.base.dir"] = '.'
result = [] result = []
for name,target in targets.items(): for name,target in targets.items():
@ -528,7 +539,7 @@ def dist_hook():
if Options.options.OSS: if Options.options.OSS:
[ shutil.rmtree(f) for f in "cloudstack-proprietary".split() if _exists(f) ] [ shutil.rmtree(f) for f in "cloudstack-proprietary".split() if _exists(f) ]
stdout = svninfo("..") or gitinfo() stdout = svninfo("..") or allgitinfo()
if stdout: if stdout:
f = file("sccs-info","w") f = file("sccs-info","w")
f.write(stdout) f.write(stdout)