3
from __future__ import absolute_import, print_function
5
from .UpgradeTestBackendSSH import UpgradeTestBackendSSH
6
from .UpgradeTestBackend import UpgradeTestBackend
8
from DistUpgrade.sourceslist import SourcesList
10
from boto.ec2.connection import EC2Connection
15
import ConfigParser as configparser
24
# images created with EC2
25
class NoCredentialsFoundException(Exception):
28
class OptionError(Exception):
32
# Step to perform for a ec2 upgrade test
34
# 1. conn = EC2Connect()
35
# 2. reservation = conn.run_instances(image_id = image, security_groups = groups, key_name = key)
36
# 3. wait for instance.state == 'running':
38
# 4. ssh -i <key> root@instance.dns_name <command>
43
# Using ebs (elastic block storage) and snapshots for the test
44
# 1. ec2-create-volume -s 80 -z us-east-1a
45
# (check with ec2-describe-instance that its actually in
47
# 2. ec2-attach-volume vol-7bd23de2 -i i-3325ad4 -d /dev/sdh
48
# (do not name it anything but sd*)
49
# 3. mount/use the thing inside the instance
52
# Other useful things:
54
# - sda2: free space (~140G)
55
# - sda3: swapspace (~1G)
57
class UpgradeTestBackendEC2(UpgradeTestBackendSSH):
60
def __init__(self, profile):
61
UpgradeTestBackend.__init__(self, profile)
62
self.profiledir = os.path.abspath(os.path.dirname(profile))
63
# ami base name (e.g .ami-44bb5c2d)
64
self.ec2ami = self.config.get("EC2","AMI")
65
self.ssh_key = self.config.get("EC2","SSHKey")
67
self.access_key_id = (os.getenv("AWS_ACCESS_KEY_ID") or
68
self.config.get("EC2","access_key_id"))
69
self.secret_access_key = (os.getenv("AWS_SECRET_ACCESS_KEY") or
70
self.config.get("EC2","secret_access_key"))
71
except configparser.NoOptionError:
72
print("Either export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY or")
73
print("set access_key_id and secret_access_key in the profile config")
76
self._conn = EC2Connection(self.access_key_id, self.secret_access_key)
77
self.ubuntu_official_ami = False
78
if self.config.has_option("EC2","UbuntuOfficialAMI"):
79
self.ubuntu_official_ami = self.config.getboolean("EC2","UbuntuOfficialAMI")
82
self.security_groups = self.config.getlist("EC2","SecurityGroups")
83
except configparser.NoOptionError:
84
self.security_groups = []
86
if self.ssh_key.startswith("./"):
87
self.ssh_key = self.profiledir + self.ssh_key[1:]
91
# the public name of the instance, e.g.
92
# ec2-174-129-152-83.compute-1.amazonaws.com
93
self.ssh_hostname = ""
94
# the instance name (e.g. i-3325ad4)
96
if (self.config.has_option("NonInteractive","RealReboot") and
97
self.config.getboolean("NonInteractive","RealReboot")):
98
raise OptionError("NonInteractive/RealReboot option must be set to False for the ec2 upgrader")
99
atexit.register(self._cleanup)
102
print("_cleanup(): stopping running instance")
106
def _enableRootLogin(self):
108
"sed", "-i", "-e", "'s,\(.*\)\(ssh-rsa.*\),\\2,'",
109
"/root/.ssh/authorized_keys"]
110
ret = self._runInImageAsUser("ubuntu", command)
113
def bootstrap(self, force=False):
116
print("Building new image based on '%s'" % self.ec2ami)
119
basepkg = self.config.get("NonInteractive","BasePkg")
122
self.start_instance()
124
# prepare the sources.list (needed because a AMI may have any
126
sources = self.getSourcesListFile()
127
ret = self._copyToImage(sources.name, "/etc/apt/sources.list")
130
# install some useful stuff
131
ret = self._runInImage(["apt-get","update"])
133
# FIXME: instead of this retrying (for network errors with
134
# proxies) we should have a self._runAptInImage()
136
ret = self._runInImage(["DEBIAN_FRONTEND=noninteractive","apt-get","install", "--allow-unauthenticated", "-y",basepkg])
140
pkgs = self.config.getListFromFile("NonInteractive","AdditionalPkgs")
141
while(len(pkgs)) > 0:
142
print("installing additional: %s" % pkgs[:CMAX])
143
ret= self._runInImage(["DEBIAN_FRONTEND=noninteractive","apt-get","install","--reinstall", "--allow-unauthenticated", "-y"]+pkgs[:CMAX])
144
print("apt(2) returned: %s" % ret)
146
#self._cacheDebs(tmpdir)
147
print("apt returned an error, stopping")
152
if self.config.has_option("NonInteractive","PostBootstrapScript"):
153
script = self.config.get("NonInteractive","PostBootstrapScript")
154
print("have PostBootstrapScript: %s" % script)
155
if os.path.exists(script):
156
self._runInImage(["mkdir","/upgrade-tester"])
157
self._copyToImage(script, "/upgrade-tester")
158
print("running script: %s" % os.path.join("/tmp", script))
159
self._runInImage([os.path.join("/upgrade-tester",script)])
161
print("WARNING: %s not found" % script)
163
if self.config.getWithDefault("NonInteractive",
164
"UpgradeFromDistOnBootstrap", False):
165
print("running apt-get upgrade in from dist (after bootstrap)")
167
ret = self._runInImage(["DEBIAN_FRONTEND=noninteractive","apt-get","--allow-unauthenticated", "-y","dist-upgrade"])
170
print("Cleaning image")
171
ret = self._runInImage(["apt-get","clean"])
174
# done with the bootstrap
176
# FIXME: idealy we would reboot here, but its less important
177
# because we can't get a new kernel anyway in ec2 (yet)
178
# - the reboot thing is *not* yet reliable!
179
#self.reboot_instance()
181
# FIXME: support for caching/snapshoting the base image here
185
def start_instance(self):
186
print("Starting ec2 instance and wait until it's available")
189
reservation = self._conn.run_instances(image_id=self.ec2ami,
190
security_groups=self.security_groups,
191
key_name=self.ssh_key[:-4].split("/")[-1])
192
self.instance = reservation.instances[0]
193
while self.instance.state == "pending":
194
print("Waiting for instance %s to come up..." % self.instance.id)
196
self.instance.update()
198
print("It's up: hostname =", self.instance.dns_name)
199
self.ssh_hostname = self.instance.dns_name
200
self.ec2instance = self.instance.id
202
# now sping until ssh comes up in the instance
203
if self.ubuntu_official_ami:
210
print("instance available via ssh ping")
213
print("Could not connect to instance after 900s, exiting")
215
# re-enable root login if needed
216
if self.ubuntu_official_ami:
217
print("Re-enabling root login... ",)
218
ret = self._enableRootLogin()
222
print("Oops, failed to enable root login...")
224
# the official image seems to run a update on startup?!?
225
print("waiting for the official image to leave apt alone")
229
def reboot_instance(self):
230
" reboot a ec2 instance and wait until its available again "
231
self.instance.reboot()
232
# FIMXE: find a better way to know when the instance is
233
# down - maybe with "-v" ?
236
if self._runInImage(["/bin/true"]) == 0:
237
print("instance rebooted")
240
def stop_instance(self):
241
" permanently stop a instance (it can never be started again "
242
# terminates are final - all data is lost
244
# wait until its down
246
if self._runInImage(["/bin/true"]) != 0:
247
print("instance stopped")
253
# clean from any leftover pyc files
254
for f in glob.glob("%s/*.pyc" % self.upgradefilesdir):
257
print("Starting for upgrade")
259
assert(self.ec2instance)
260
assert(self.ssh_hostname)
263
if os.path.exists(self.profile):
264
print("Copying '%s' to image overrides" % self.profile)
265
self._runInImage(["mkdir","-p","/etc/update-manager/release-upgrades.d"])
266
self._copyToImage(self.profile, "/etc/update-manager/release-upgrades.d/")
268
# copy test repo sources.list (if needed)
269
test_repo = self.config.getWithDefault("NonInteractive","AddRepo","")
271
test_repo = os.path.join(os.path.dirname(self.profile), test_repo)
272
self._copyToImage(test_repo, "/etc/apt/sources.list.d")
273
sourcelist = self.getSourcesListFile()
274
apt_pkg.config.set("Dir::Etc", os.path.dirname(sourcelist.name))
275
apt_pkg.config.set("Dir::Etc::sourcelist",
276
os.path.basename(sourcelist.name))
277
sources = SourcesList(matcherPath=".")
278
sources.load(test_repo)
279
# add the uri to the list of valid mirros in the image
280
for entry in sources.list:
281
if (not (entry.invalid or entry.disabled) and
282
entry.type == "deb"):
283
print("adding %s to mirrors" % entry.uri)
284
self._runInImage(["echo '%s' >> /upgrade-tester/mirrors.cfg" % entry.uri])
286
# check if we have a bzr checkout dir to run against or
287
# if we should just run the normal upgrader
288
if (os.path.exists(self.upgradefilesdir) and
289
self.config.getWithDefault("NonInteractive",
290
"UseUpgraderFromBzr",
292
self._copyUpgraderFilesFromBzrCheckout()
293
ret = self._runBzrCheckoutUpgrade()
295
ret = self._runInImage(["do-release-upgrade","-d",
296
"-f","DistUpgradeViewNonInteractive"])
297
print("dist-upgrade.py returned: %i" % ret)
301
print("copying the result")
302
self._copyFromImage("/var/log/dist-upgrade/*",self.resultdir)
305
print("Shutting down the VM")
311
# FIXME: add some tests here to see if the upgrade worked
312
# this should include:
313
# - new kernel is runing (run uname -r in target)
314
# - did it sucessfully rebooted
316
# - generate diff of upgrade vs fresh install
321
# compatibility for the auto-install-tester
323
self.start_instance()
326
def saveVMSnapshot(self):
327
print("saveVMSnapshot not supported yet")
328
def restoreVMSnapshot(self):
329
print("restoreVMSnapshot not supported yet")