infra: buildbot master configs and slave systemd service file

* master: master.cfg and example pass.cfg file
* worker: systemd service unit file.

  Setting up a slave/worker is trivial:

  su - buildbot
  buildslave create-slave . radia.quagga.net buildbot-<name> <password>
diff --git a/infra/buildbot/master/master.cfg b/infra/buildbot/master/master.cfg
new file mode 100644
index 0000000..2561234
--- /dev/null
+++ b/infra/buildbot/master/master.cfg
@@ -0,0 +1,391 @@
+# -*- python -*-
+# ex: set syntax=python:
+
+from buildbot.plugins import *
+from buildbot.plugins import buildslave, util
+
+# This is a sample buildmaster config file. It must be installed as
+# 'master.cfg' in your buildmaster's base directory.
+
+# This is the dictionary that the buildmaster pays attention to. We also use
+# a shorter alias to save typing.
+c = BuildmasterConfig = {}
+
+quaggagit = 'git://git.sv.gnu.org/quagga.git'
+
+# password defs
+execfile("pass.cfg")
+
+workers = {
+	"fedora-24": { 
+	  "os": "Fedora",
+	  "version": "24",
+	  "vm": False,
+	  "pkg": "rpm",
+	}, 
+	"centos-7": {
+	  "os": "CentOS",
+	  "version": "7",
+	  "vm": False,
+	  "pkg": "rpm",
+	},
+	"debian-8": {
+	  "os": "Debian",
+	  "version": "8",
+	  "vm": True,	
+  	  "pkg": "dpkg",
+  	  "latent": True,
+  	  "hd_image": "/var/lib/libvirt/images/debian8.qcow2",
+  	},
+  	"debian-9": { 
+	  "os": "Debian",
+	  "version": "9",
+	  "vm": True,	
+  	  "pkg": "dpkg",
+  	  "latent": True,
+  	  "hd_image": "/var/lib/libvirt/images/debian9.qcow2",
+  	},
+  	"freebsd-10": { 
+	  "os": "FreeBSD",
+	  "version": "10",
+	  "vm": True,	
+  	  "pkg": "",
+  	  "latent": True,
+  	  "hd_image": "/var/lib/libvirt/images/freebsd103.qcow2",
+  	}, 
+  	"freebsd-11": { 
+	  "os": "FreeBSD",
+	  "version": "11",
+	  "vm": True,	
+  	  "pkg": "",
+  	  "latent": True,
+  	  "hd_image": "/var/lib/libvirt/images/freebsd110.qcow2",
+  	},
+}
+
+# ensure "latent" is set to false, where not set.
+# add in the passwords
+for kw in workers:
+	w = workers[kw]
+	w["bot"] = "buildbot-" + kw
+	if "latent" not in w:
+		w["latent"] = False
+	w["pass"] = workers_pass[kw]
+
+analyses_builders = [ "clang-analyzer" ]
+
+# default Libvirt session
+for w in (w for w in workers.values () if ("latent" in w) 
+					and ("session" not in w)):
+	w["session"] = 'qemu+ssh://buildbot@sagan.jakma.org/system'
+
+osbuilders = list("build-" + kw for kw in workers)
+
+allbuilders =  []
+allbuilders += osbuilders
+allbuilders += analyses_builders
+allbuilders += ["commit-builder"]
+allbuilders += ["build-distcheck"]
+
+# Force merging of requests.
+c['mergeRequests'] = lambda *args, **kwargs: True
+
+####### BUILDSLAVES
+c['slaves'] = []
+
+# The 'slaves' list defines the set of recognized buildslaves. Each element is
+# a BuildSlave object, specifying a unique slave name and password.  The same
+# slave name and password must be configured on the slave.
+
+for w in (w for w in workers.values() if ("latent" not in w)
+				      or (w["latent"] == False)):
+	c['slaves'].append(buildslave.BuildSlave(w["bot"], w["pass"]))
+
+for w in (w for w in workers.values()
+		  if ("latent" in w) 
+		     and w["latent"]
+		     and "hd_image" in w):
+	c['slaves'].append(buildslave.LibVirtSlave(
+					w["bot"],
+					w["pass"],
+					util.Connection(w["session"]),
+					w["hd_image"],
+	))
+
+# 'protocols' contains information about protocols which master will use for
+# communicating with slaves.
+# You must define at least 'port' option that slaves could connect to your master
+# with this protocol.
+# 'port' must match the value configured into the buildslaves (with their
+# --master option)
+c['protocols'] = {'pb': {'port': 9989}}
+
+####### CHANGESOURCES
+
+# the 'change_source' setting tells the buildmaster how it should find out
+# about source code changes.  Here we point to the buildbot clone of pyflakes.
+
+c['change_source'] = []
+c['change_source'].append(changes.GitPoller(
+	quaggagit,
+        workdir='gitpoller-workdir', 
+	branches=['master','volatile/next'],
+        pollinterval=300))
+
+####### SCHEDULERS
+
+# Configure the Schedulers, which decide how to react to incoming changes. 
+
+# We want a first line of 'quick' builds, which then trigger further builds.
+#
+# A control-flow builder, "commit-builder", used to sequence the 'real'
+# sets of builders, via Triggers.
+
+c['schedulers'] = []
+c['schedulers'].append(schedulers.SingleBranchScheduler(
+                            name="master-change",
+                            change_filter=util.ChangeFilter(branch='master'),
+                            treeStableTimer=10,
+                            builderNames=[ "commit-builder" ]))
+
+c['schedulers'].append(schedulers.SingleBranchScheduler(
+                            name="next-change",
+                            change_filter=util.ChangeFilter(
+                            	branch='volatile/next'),
+                            treeStableTimer=10,
+                            builderNames=[ "commit-builder" ] ))
+
+# Initial build checks on faster, non-VM
+c['schedulers'].append(schedulers.Triggerable(
+	name="trigger-build-first",
+	builderNames=list("build-" + kw
+				for kw in workers
+					if workers[kw]["vm"] == False)))
+
+# Build using remaining builders, after firstbuilders.
+c['schedulers'].append(schedulers.Triggerable(
+	name="trigger-build-rest",
+	builderNames=list("build-" + kw
+				for w in workers
+					if workers[kw]["vm"] == True)))
+
+# Analyses tools, e.g. CLang Analyzer scan-build
+c['schedulers'].append(schedulers.Triggerable(
+		       name="trigger-build-analyses",	
+		       builderNames=analyses_builders))
+# Dist check
+c['schedulers'].append(schedulers.Triggerable(
+		       name="trigger-distcheck",	
+		       builderNames=["build-distcheck"]))
+
+# Try and force schedulers
+c['schedulers'].append(schedulers.ForceScheduler(
+                       name="force",
+                       builderNames=allbuilders))
+
+c['schedulers'].append(schedulers.Try_Userpass(
+		       name="try",
+		       builderNames=list("build-" + kw 
+					   for w in workers)
+				     + ["build-distcheck", 
+					 "clang-analyzer" ],
+			userpass=users,
+			port=8031))
+
+####### BUILDERS
+c['builders'] = []
+
+# The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
+# what steps, and which slaves can execute them.  Note that any particular build will
+# only take place on one slave.
+
+common_steps = [
+steps.Git(repourl=quaggagit, mode='incremental'),
+steps.ShellCommand(command=["./update-autotools"]),
+steps.Configure(),
+steps.ShellCommand(command=["make", "clean"]),
+steps.Compile(),
+]
+
+### Each OS specific builder
+
+factory = util.BuildFactory()
+# check out the source
+factory.addStep(steps.Git(repourl=quaggagit, mode='incremental'))
+factory.addStep(steps.ShellCommand(command=["./update-autotools"],
+				   description="generating autoconf",
+				   descriptionDone="autoconf generated"))
+factory.addStep(steps.Configure())
+factory.addStep(steps.ShellCommand(command=["make", "clean"],
+				   description="cleaning",
+				   descriptionDone="cleaned"))
+factory.addStep(steps.Compile(command=["make", "-j", "2", "all"]))
+factory.addStep(steps.ShellCommand(command=["make", "check"],
+				   description="testing",
+				   descriptionDone="tests"))
+
+for kw in workers:
+   c['builders'].append(util.BuilderConfig(
+	name="build-" + kw,
+	slavenames=workers[kw]["bot"],
+	factory=factory))
+
+### distcheck
+factory = util.BuildFactory()
+# check out the source
+factory.addStep(steps.Git(repourl=quaggagit, mode='incremental'))
+factory.addStep(steps.ShellCommand(command=["./update-autotools"],
+				   description="generating autoconf",
+				   descriptionDone="autoconf generated"))
+factory.addStep(steps.Configure())
+factory.addStep(steps.ShellCommand(command=["make", "clean"],
+				   description="cleaning",
+				   descriptionDone="cleaned"))
+factory.addStep(steps.ShellCommand(command=["make", "distcheck"],
+				   description="distcheck",
+				   descriptionDone="distcheck passes"))
+c['builders'].append(
+	util.BuilderConfig(name="build-distcheck",
+	slavenames=list(w["bot"] for w in workers.values()),
+	factory=factory,
+))
+
+### LLVM clang-analyzer build
+
+f = util.BuildFactory()
+# check out the source
+f.addStep(steps.Git(repourl=quaggagit, mode='incremental',
+		    getDescription=True))
+f.addStep(steps.ShellCommand(command=["./update-autotools"],
+			     description="autotools",
+			     descriptionDone="autoconf generated"))
+f.addStep(steps.Configure())
+f.addStep(steps.ShellCommand(command=["make", "clean"],
+			     description="cleaning",
+			     descriptionDone="cleaned"))
+
+f.addStep(steps.SetProperty(property="clang-id",
+	value=util.Interpolate("%(prop:commit-description)s-%(prop:buildnumber)s")))
+
+f.addStep(steps.SetProperty(property="clang-output-dir",
+	value=util.Interpolate("../CLANG-%(prop:clang-id)s")))
+f.addStep(steps.SetProperty(property="clang-uri",
+	value=util.Interpolate("/clang-analyzer/%(prop:clang-id)s")))
+# relative to buildbot master working directory
+f.addStep(steps.SetProperty(property="clang-upload-dir",
+	value=util.Interpolate("public_html/clang-analyzer/%(prop:clang-id)s")))
+
+f.addStep(steps.Compile(command=["scan-build",
+			      	 "-analyze-headers",
+			      	 "-o",
+			      	 util.Interpolate("%(prop:clang-output-dir)s"),
+				 "make", "-j", "all"]))
+f.addStep(steps.DirectoryUpload(
+	  slavesrc=util.Interpolate("%(prop:clang-output-dir)s"),
+	  masterdest = util.Interpolate("%(prop:clang-upload-dir)s"),
+	  compress = 'bz2',
+	  name = "clang report",
+	  url = util.Interpolate("%(prop:clang-uri)s"),
+))
+f.addStep(steps.RemoveDirectory(
+	dir=util.Interpolate("%(prop:clang-output-dir)s")
+))
+
+c['builders'].append(
+    util.BuilderConfig(name="clang-analyzer",
+      slavenames=list(w["bot"] for w in workers.values() if not w["vm"]),
+      factory=f))
+
+## Co-ordination builds used to sequence parallel builds via Triggerable
+f = util.BuildFactory()
+f.addStep(steps.Trigger (
+	schedulerNames = [ "trigger-build-first" ],
+	waitForFinish=True
+))
+f.addStep(steps.Trigger (
+	schedulerNames = [ "trigger-build-rest" ],
+	waitForFinish=True
+))
+f.addStep(steps.Trigger (
+	schedulerNames = [ "trigger-build-analyses", "trigger-distcheck" ],
+	waitForFinish=True
+))
+
+c['builders'].append(
+    util.BuilderConfig(name="commit-builder",
+      slavenames=["buildbot-fedora-24"],
+      factory=f
+))
+
+####### STATUS TARGETS
+
+# 'status' is a list of Status Targets. The results of each build will be
+# pushed to these targets. buildbot/status/*.py has a variety to choose from,
+# including web pages, email senders, and IRC bots.
+
+c['status'] = []
+
+from buildbot.status import html
+from buildbot.status.web import authz, auth
+
+authz_cfg=authz.Authz(
+    # change any of these to True to enable; see the manual for more
+    # options
+    #auth=auth.BasicAuth([("pyflakes","pyflakes")]),
+    auth=util.BasicAuth(users),
+    gracefulShutdown = False,
+    forceBuild = 'auth', # use this to test your slave once it is set up
+    forceAllBuilds = 'auth',  # ..or this
+    pingBuilder = 'auth',
+    stopBuild = 'auth',
+    stopAllBuilds = 'auth',
+    cancelPendingBuild = 'auth',
+    cancelAllPendingBuilds = 'auth',
+    pauseSlave = 'auth',    
+)
+c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg))
+
+c['status'].append(status.MailNotifier(
+	fromaddr="buildbot@quagga.net",
+	extraRecipients=["paul@jakma.org"],
+	sendToInterestedUsers=False,
+))
+
+c['status'].append (status.IRC(
+	"irc.freenode.net", "bb-quagga",
+	useColors=True,
+	channels=[{"channel": "#quagga"}],
+	notify_events={
+		'exception': 1,
+		'successToFailure': 1,
+		'failureToSuccess': 1,
+	},
+))
+
+####### PROJECT IDENTITY
+
+# the 'title' string will appear at the top of this buildbot
+# installation's html.WebStatus home page (linked to the
+# 'titleURL') and is embedded in the title of the waterfall HTML page.
+
+c['title'] = "Quagga"
+c['titleURL'] = "https://www.quagga.net/"
+
+# the 'buildbotURL' string should point to the location where the buildbot's
+# internal web server (usually the html.WebStatus page) is visible. This
+# typically uses the port number set in the Waterfall 'status' entry, but
+# with an externally-visible host name which the buildbot cannot figure out
+# without some help.
+
+c['buildbotURL'] = "http://buildbot.quagga.net/"
+
+####### DB URL
+
+c['db'] = {
+    # This specifies what database buildbot uses to store its state.  You can leave
+    # this at its default for all but the largest installations.
+    'db_url' : "sqlite:///state.sqlite",
+}
+
+#### debug
+c['debugPassword'] = debugPassword
diff --git a/infra/buildbot/master/pass.cfg b/infra/buildbot/master/pass.cfg
new file mode 100644
index 0000000..34a9340
--- /dev/null
+++ b/infra/buildbot/master/pass.cfg
@@ -0,0 +1,21 @@
+# -*- python -*-
+# ex: set syntax=python:
+
+# example pass.cfg
+
+# privileged users for webui and try building
+#users = [
+#    ('foo', 'password123'),
+#]
+
+workers_pass = {
+          "fedora-24":  "aaaaaaa",
+          "centos-7":   "bbbbbbb",
+          "debian-8":   "ccccccc",
+          "debian-9":   "ddddddd",
+          "freebsd-10": "eeeeeee", 
+          "freebsd-11": "fffffff",
+}
+
+#### debug
+#debugPassword = "abcdefghijklmnopqrstuvwxyz"
diff --git a/infra/buildbot/worker/buildbot-slave.service b/infra/buildbot/worker/buildbot-slave.service
new file mode 100644
index 0000000..dcb136f
--- /dev/null
+++ b/infra/buildbot/worker/buildbot-slave.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Buildbot slave Daemon
+
+[Service]
+WorkingDirectory=/home/buildbot
+User=buildbot
+Group=buildbot
+ExecStart=/usr/bin/buildslave start --nodaemon
+ExecStop=/usr/bin/buildslave stop
+ExecReload=/usr/bin/buildslave reconfig
+
+[Install]
+WantedBy=multi-user.target