6 "github.com/Debian/dcs/rackspace"
7 "github.com/Debian/dcs/sshutil"
15 dcs0ServerId
= flag
.String("dcs0_server_id",
17 "Skip creating a new server and use the specified id as dcs0 vm.")
18 dcsIndexServerIds
= flag
.String("index_server_ids",
20 "comma-separated list of index server IDs.")
21 dcs0VolumeId
= flag
.String("dcs0_volume_id_dcs",
23 "Skip creating a new block storage volume and use the specified id.")
24 dcs0VolumeMirrorId
= flag
.String("dcs0_volume_id_mirror",
26 "Skip creating a new block storage volume and use the specified id.")
28 rs
*rackspace
.RackspaceClient
31 // All functions defined here directly use log.Fatal() without an OrDie suffix.
33 func attachBlockStorageVolume(serverId
, volumeId
, deviceNode
string) {
34 volume
, err
:= rs
.GetVolume(volumeId
)
38 volume
= volume
.BecomeAvailableOrDie(2 * time
.Hour
)
40 if volume
.Status() == "IN-USE" {
41 log
.Printf("Volume %s already attached, not attaching.\n", volumeId
)
45 log
.Printf("Attaching volume %s to %s\n", volumeId
, serverId
)
46 attachId
, err
:= rs
.AttachBlockStorage(serverId
,
47 rackspace
.VolumeAttachmentRequest
{
54 log
.Printf("attachment id = %s\n", attachId
)
56 volume
= volume
.BecomeInUseOrDie(30 * time
.Minute
)
59 // Returns an sshutil.Client because it reconnects on fdisk failures, creating
61 func mountBlockStorage(client
*sshutil
.Client
, partition
, mountpoint
string) *sshutil
.Client
{
62 // device is “/dev/xvdg” if partition is “/dev/xvdg1”
63 device
:= partition
[:len(partition
)-1]
65 // Unmount all mounts within this mountpoint, then the mountpoint itself.
66 grep
:= `cut -d ' ' -f 2 /proc/mounts | grep '^` + mountpoint
+ `' | tac`
67 client
.RunOrDie(`for mnt in $(` + grep
+ `); do umount $mnt; done || true`)
69 // Create a partition unless a partition already exists.
70 fdisk
:= `[ -e ` + partition
+ ` ] || echo "n\np\n\n\n\nw\n" | fdisk -S 32 -H 32 ` + device
72 // When re-partitioning the disk on which the root filesystem is stored,
73 // the kernel will not re-read the partition table and thus fdisk will
75 // We reboot the machine and wait until it comes back before continuing.
76 session
, err
:= client
.NewSession()
78 log
.Fatalf("Failed to create session in SSH connection: %v\n", err
)
81 log
.Printf(`[SSH] Running "%s"`, fdisk
)
82 err
= session
.Run(fdisk
)
84 log
.Printf(`Could not execute SSH command (%v) rebooting\n`, err
)
86 client
.RunOrDie(`reboot`)
87 // Save host because client may be garbage collected in the loop
90 for time
.Since(start
) < 10*time
.Minute
{
91 time
.Sleep(5 * time
.Second
)
92 log
.Printf("Trying to SSH in again…\n")
93 client
, err
= sshutil
.Connect(host
)
95 log
.Printf("SSH error (retrying): %v\n", err
)
101 // TODO: what has helped at least once is triggering a reboot via the rackspace web interface.
104 log
.Fatal("Machine did not come back within 10 minutes after rebooting.")
107 if !client
.Successful(`[ -e ` + partition
+ ` ]`) {
108 log
.Fatal("%s not there, even after a reboot!\n", partition
)
112 // Create (or overwrite!) an ext4 file system without lazy initialization
113 // (so that the volume is usable immediately) and with SSD-friendly
115 // TODO: ^has_journal, barrier=0
116 client
.RunOrDie(`mkfs.ext4 -b 4096 -m 0 -E stride=128,stripe-width=128,lazy_itable_init=0,lazy_journal_init=0 ` + partition
)
118 client
.RunOrDie(fmt
.Sprintf("echo %s %s ext4 defaults,nofail,noatime,nodiratime 0 0 >> /etc/fstab", partition
, mountpoint
))
119 client
.RunOrDie(`mkdir -p ` + mountpoint
)
120 client
.RunOrDie(`mount ` + mountpoint
)
129 rs
, err
= rackspace
.NewClient()
131 log
.Fatal("Could not create new rackspace client: %v\n", err
)
134 serverId
:= *dcs0ServerId
136 serverId
= rs
.ServerFromSnapshot("dcs-20g-systemd+dcs+psql",
137 rackspace
.CreateServerRequest
{
139 // This is a 4 GB standard instance
144 log
.Printf("-dcs0_server_id=%s\n", serverId
)
146 server
, err
:= rs
.GetServer(serverId
)
150 server
= server
.BecomeActiveOrDie(2 * time
.Hour
)
152 // Attach an SSD block storage volume (unless one was specified)
153 volumeId
:= *dcs0VolumeId
155 volumeId
, err
= rs
.CreateBlockStorage(
156 rackspace
.CreateBlockStorageRequest
{
157 DisplayName
: "NEW-dcs-src-0",
166 // Attach an SSD block storage volume (unless one was specified)
167 volumeMirrorId
:= *dcs0VolumeMirrorId
168 if volumeMirrorId
== "" {
169 volumeMirrorId
, err
= rs
.CreateBlockStorage(
170 rackspace
.CreateBlockStorageRequest
{
171 DisplayName
: "NEW-dcs-mirror-0",
172 // 100 GB is the minimum size. Last time we used 43 GB.
181 log
.Printf("-dcs0_volume_id_dcs=%s\n", volumeId
)
182 log
.Printf("-dcs0_volume_id_mirror=%s\n", volumeMirrorId
)
184 // We chose xvd[gh] so that it does not clash with xvda, xvdb and
185 // whichever new devices a Rackspace base image might use in the
186 // future :). We use a predictable path because it is easier than
187 // figuring out the path in the subsequent automation.
188 attachBlockStorageVolume(serverId
, volumeId
, "/dev/xvdg")
189 attachBlockStorageVolume(serverId
, volumeMirrorId
, "/dev/xvdh")
191 client
, err
:= sshutil
.Connect(server
.AccessIPv6
)
196 // The /dcs/NEW/index.*.idx files are the artifact of the first stage, so
197 // if they are present, skip the first stage entirely.
198 if !client
.Successful(`[ -s /dcs/NEW/index.0.idx ]`) {
199 log
.Printf("/dcs/NEW/index.0.idx not present, creating new index.\n")
201 // Partition the remaining capacity on the server (≈ 160G) as on-disk
202 // temporary space, since the temporary files exceed the 18G available in
203 // /tmp on our base image.
204 client
= mountBlockStorage(client
, "/dev/xvda2", "/tmp-disk")
206 // Attach the SSD Block Storage volumes.
207 client
= mountBlockStorage(client
, "/dev/xvdg1", "/dcs")
208 client
= mountBlockStorage(client
, "/dev/xvdh1", "/dcs/source-mirror")
210 client
.RunOrDie("chown -R dcs.dcs /dcs/source-mirror")
211 client
.RunOrDie("chown -R dcs.dcs /dcs/")
212 client
.RunOrDie(`chmod 1777 /tmp-disk`)
214 // TODO: timestamps after each step in update-index.sh would be nice
215 client
.WriteToFileOrDie("/dcs/update-index.sh", []byte(`#!/bin/sh
216 # Updates the source mirror, generates a new index, verifies it is serving and
217 # then swaps the old index with the new one.
219 # In case anything goes wrong, you can manually swap back the old index, see
224 /bin/rm -rf /dcs/NEW /dcs/OLD /dcs/unpacked-new
225 /bin/mkdir /dcs/NEW /dcs/OLD
227 [ -d ~/.gnupg ] || mkdir ~/.gnupg
228 [ -e ~/.gnupg/trustedkeys.gpg ] || cp /usr/share/keyrings/debian-archive-keyring.gpg ~/.gnupg/trustedkeys.gpg
230 GOMAXPROCS=2 /usr/bin/dcs-debmirror -tcp_conns=20 >/tmp/fdm.log 2>&1
231 /usr/bin/debmirror --diff=none --method=http --rsync-extra=none -a none --source -s main -h http.debian.net -r /debian /dcs/source-mirror >/dev/null
232 /usr/bin/debmirror --diff=none --method=http --rsync-extra=none --exclude-deb-section=.* --include golang-mode --nocleanup -a none --arch amd64 -s main -h http.debian.net -r /debian /dcs/source-mirror >/dev/null
235 if ! wget -q http://udd.debian.org/udd-popcon.sql.xz -O $POPCONDUMP
237 wget -q http://public-udd-mirror.xvm.mit.edu/snapshots/udd-popcon.sql.xz -O $POPCONDUMP
239 echo 'DROP TABLE popcon; DROP TABLE popcon_src;' | psql udd
240 xz -d -c $POPCONDUMP | psql udd
243 /usr/bin/compute-ranking \
244 -mirror_path=/dcs/source-mirror
246 /usr/bin/dcs-unpack \
247 -mirror_path=/dcs/source-mirror \
248 -new_unpacked_path=/dcs/unpacked-new \
249 -old_unpacked_path=/dcs/unpacked >/dev/null
252 -index_shard_path=/dcs/NEW/ \
253 -unpacked_path=/dcs/unpacked-new/ \
256 [ -d /dcs/unpacked ] && mv /dcs/unpacked /dcs/unpacked-old || true
257 mv /dcs/unpacked-new /dcs/unpacked
259 client
.RunOrDie(`chmod +x /dcs/update-index.sh`)
260 client
.RunOrDie(`TMPDIR=/tmp-disk nohup su dcs -c "/bin/sh -c 'sh -x /dcs/update-index.sh >/tmp/update.log 2>&1 &'"`)
262 // TODO: i also think we need some sort of lock here. perhaps let systemd run the updater?
265 log
.Printf("Now waiting until /dcs/NEW/index.*.idx appear and are > 0 bytes\n")
268 for time
.Since(start
) < 24*time
.Hour
{
269 pollclient
, err
:= sshutil
.Connect(server
.AccessIPv6
)
271 log
.Printf("Non-fatal polling connection error: %v\n", err
)
274 log
.Fatal("More than 30 connection errors connecting to %s, giving up.\n", server
.AccessIPv6
)
279 // TODO: flag for the number of shards
280 shardsFound
:= `[ $(find /dcs/NEW/ -iname "index.*.idx" -size +0 -mmin +15 | wc -l) -eq 6 ]`
281 if pollclient
.Successful(shardsFound
) {
282 log
.Printf("All shards present.\n")
286 time
.Sleep(15 * time
.Minute
)
289 var indexServerIds
[]string
290 indexServerIds
= strings
.Split(*dcsIndexServerIds
, ",")
291 if *dcsIndexServerIds
== "" {
292 // TODO: flag for the number of shards/servers
293 indexServerIds
= make([]string, 6)
295 for i
:= 0; i
< len(indexServerIds
); i
++ {
296 indexServerIds
[i
] = rs
.ServerFromSnapshot("dcs-20g-systemd+dcs",
297 rackspace
.CreateServerRequest
{
298 Name
: fmt
.Sprintf("NEW-dcs-index-%d", i
),
299 // This is a 2 GB standard instance
305 log
.Printf("-index_server_ids=%s\n", strings
.Join(indexServerIds
, ","))
307 done
:= make(chan bool)
308 indexServers
:= make([]rackspace
.Server
, len(indexServerIds
))
309 for i
, _
:= range indexServers
{
311 server
, err
:= rs
.GetServer(indexServerIds
[i
])
315 indexServers
[i
] = server
.BecomeActiveOrDie(2 * time
.Hour
)
320 log
.Printf("Waiting for all %d index servers to be available…\n",
322 for _
= range indexServers
{
326 log
.Printf("Index servers available. Copying index…")
328 pubkey
, err
:= ioutil
.ReadFile("/home/michael/.ssh/dcs-auto-rs")
332 client
.WriteToFileOrDie("~/.ssh/dcs-auto-rs", pubkey
)
333 client
.RunOrDie("chmod 600 ~/.ssh/dcs-auto-rs")
335 for i
, server
:= range indexServers
{
336 go func(i
int, server rackspace
.Server
) {
338 indexclient
, err
:= sshutil
.Connect(server
.AccessIPv6
)
340 log
.Fatal("Failed to dial: " + err
.Error())
342 indexclient
.RunOrDie("mkdir -p /dcs/")
343 if indexclient
.Successful(fmt
.Sprintf("[ -e /dcs/index.%d.idx ]", i
)) {
344 log
.Printf("Index already present, skipping.\n")
348 // “|| true” instead of “rm -f” because globbing fails when there are
349 // no matching files.
350 indexclient
.RunOrDie("rm /dcs/index.*.idx || true")
353 fmt
.Sprintf("scp -o StrictHostKeyChecking=no -i ~/.ssh/dcs-auto-rs /dcs/NEW/index.%d.idx root@%s:/dcs/",
355 server
.PrivateIPv4()))
356 indexclient
.RunOrDie(fmt
.Sprintf("systemctl restart dcs-index-backend@%d.service", i
))
357 indexclient
.RunOrDie(fmt
.Sprintf("systemctl enable dcs-index-backend@%d.service", i
))
361 log
.Printf("Waiting for the index to be copied to all %d index servers…\n",
363 for _
= range indexServers
{
366 log
.Printf("index copied!\n")
368 backends
:= []string{}
369 for i
, server
:= range indexServers
{
370 backends
= append(backends
, fmt
.Sprintf("%s:%d", server
.PrivateIPv4(), 29080+i
))
373 // TODO(longterm): configure firewall?
375 client
.RunOrDie("mkdir -p /etc/systemd/system/dcs-web.service.d/")
377 client
.WriteToFileOrDie(
378 "/etc/systemd/system/dcs-web.service.d/backends.conf",
380 Environment=GOMAXPROCS=2
382 ExecStart=/usr/bin/dcs-web \
383 -template_pattern=/usr/share/dcs/templates/* \
384 -listen_address=`+server
.PrivateIPv4()+`:28080 \
385 -use_sources_debian_net=true \
386 -index_backends=`+strings
.Join(backends
, ",")))
388 client
.RunOrDie("systemctl daemon-reload")
389 client
.RunOrDie("systemctl enable dcs-web.service")
390 client
.RunOrDie("systemctl enable dcs-source-backend.service")
391 client
.RunOrDie("systemctl restart dcs-source-backend.service")
392 client
.RunOrDie("systemctl restart dcs-web.service")
394 // Install and configure nginx.
395 client
.RunOrDie("DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true LC_ALL=C LANGUAGE=C LANG=C apt-get update")
396 client
.RunOrDie("DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true LC_ALL=C LANGUAGE=C LANG=C apt-get --force-yes -y install nginx")
397 client
.RunOrDie("rm /etc/nginx/sites-enabled/*")
398 client
.RunOrDie("mkdir -p /var/cache/nginx/cache")
399 client
.RunOrDie("mkdir -p /var/cache/nginx/tmp")
400 client
.RunOrDie("chown -R www-data.www-data /var/cache/nginx/")
401 nginxHost
, err
:= ioutil
.ReadFile("/home/michael/gocode/src/github.com/Debian/dcs/nginx.example")
405 // dcs-web is listening on the Rackspace ServiceNet (private) IP address.
406 nginxReplaced
:= strings
.Replace(string(nginxHost
), "localhost:28080", server
.PrivateIPv4()+":28080", -1)
407 client
.WriteToFileOrDie("/etc/nginx/sites-available/codesearch", []byte(nginxReplaced
))
408 client
.RunOrDie("ln -s /etc/nginx/sites-available/codesearch /etc/nginx/sites-enabled/codesearch")
409 client
.RunOrDie("systemctl restart nginx.service")
412 domainId
, err
:= rs
.GetDomainId("rackspace.zekjur.net")
417 records
, err
:= rs
.GetDomainRecords(domainId
)
422 var updates
[]rackspace
.Record
423 for _
, record
:= range records
{
424 if record
.Name
== "codesearch.rackspace.zekjur.net" {
425 log
.Printf("record %v\n", record
)
426 newIp
:= server
.AccessIPv4
427 if record
.Type
== "AAAA" {
428 newIp
= server
.AccessIPv6
430 updates
= append(updates
,
436 } else if record
.Name
== "int-dcs-web.rackspace.zekjur.net" {
437 // This record points to the private IPv4 address, used by our
439 log
.Printf("record %v\n", record
)
440 newIp
:= server
.PrivateIPv4()
441 updates
= append(updates
,
447 } else if record
.Name
== "int-dcs-source-backend.rackspace.zekjur.net" {
448 // This record points to the private IPv4 address, used by our
450 log
.Printf("record %v\n", record
)
451 newIp
:= server
.PrivateIPv4()
452 updates
= append(updates
,
461 if err
:= rs
.UpdateRecords(domainId
, updates
); err
!= nil {
465 // TODO: reverse dns for the server
468 codesearch was deployed to:
469 http://codesearch.rackspace.zekjur.net/
472 `, server
.AccessIPv6
, server
.AccessIPv4
)