diff --git a/bin/buildbot b/master/bin/buildbot similarity index 100% rename from bin/buildbot rename to master/bin/buildbot diff --git a/buildbot/__init__.py b/master/buildbot/__init__.py similarity index 100% rename from buildbot/__init__.py rename to master/buildbot/__init__.py diff --git a/buildbot/buildbot.png b/master/buildbot/buildbot.png similarity index 100% rename from buildbot/buildbot.png rename to master/buildbot/buildbot.png diff --git a/buildbot/buildrequest.py b/master/buildbot/buildrequest.py similarity index 100% rename from buildbot/buildrequest.py rename to master/buildbot/buildrequest.py diff --git a/buildbot/buildslave.py b/master/buildbot/buildslave.py similarity index 100% rename from buildbot/buildslave.py rename to master/buildbot/buildslave.py diff --git a/buildbot/changes/__init__.py b/master/buildbot/changes/__init__.py similarity index 100% rename from buildbot/changes/__init__.py rename to master/buildbot/changes/__init__.py diff --git a/buildbot/changes/base.py b/master/buildbot/changes/base.py similarity index 100% rename from buildbot/changes/base.py rename to master/buildbot/changes/base.py diff --git a/buildbot/changes/bonsaipoller.py b/master/buildbot/changes/bonsaipoller.py similarity index 100% rename from buildbot/changes/bonsaipoller.py rename to master/buildbot/changes/bonsaipoller.py diff --git a/buildbot/changes/changes.py b/master/buildbot/changes/changes.py similarity index 100% rename from buildbot/changes/changes.py rename to master/buildbot/changes/changes.py diff --git a/buildbot/changes/freshcvs.py b/master/buildbot/changes/freshcvs.py similarity index 100% rename from buildbot/changes/freshcvs.py rename to master/buildbot/changes/freshcvs.py diff --git a/buildbot/changes/hgbuildbot.py b/master/buildbot/changes/hgbuildbot.py similarity index 100% rename from buildbot/changes/hgbuildbot.py rename to master/buildbot/changes/hgbuildbot.py diff --git a/buildbot/changes/mail.py b/master/buildbot/changes/mail.py similarity index 100% rename from buildbot/changes/mail.py rename to master/buildbot/changes/mail.py diff --git a/buildbot/changes/maildir.py b/master/buildbot/changes/maildir.py similarity index 100% rename from buildbot/changes/maildir.py rename to master/buildbot/changes/maildir.py diff --git a/buildbot/changes/manager.py b/master/buildbot/changes/manager.py similarity index 100% rename from buildbot/changes/manager.py rename to master/buildbot/changes/manager.py diff --git a/buildbot/changes/monotone.py b/master/buildbot/changes/monotone.py similarity index 100% rename from buildbot/changes/monotone.py rename to master/buildbot/changes/monotone.py diff --git a/buildbot/changes/p4poller.py b/master/buildbot/changes/p4poller.py similarity index 100% rename from buildbot/changes/p4poller.py rename to master/buildbot/changes/p4poller.py diff --git a/buildbot/changes/pb.py b/master/buildbot/changes/pb.py similarity index 100% rename from buildbot/changes/pb.py rename to master/buildbot/changes/pb.py diff --git a/buildbot/changes/svnpoller.py b/master/buildbot/changes/svnpoller.py similarity index 100% rename from buildbot/changes/svnpoller.py rename to master/buildbot/changes/svnpoller.py diff --git a/buildbot/clients/__init__.py b/master/buildbot/clients/__init__.py similarity index 100% rename from buildbot/clients/__init__.py rename to master/buildbot/clients/__init__.py diff --git a/buildbot/clients/base.py b/master/buildbot/clients/base.py similarity index 100% rename from buildbot/clients/base.py rename to master/buildbot/clients/base.py diff --git a/buildbot/clients/debug.glade b/master/buildbot/clients/debug.glade similarity index 100% rename from buildbot/clients/debug.glade rename to master/buildbot/clients/debug.glade diff --git a/buildbot/clients/debug.py b/master/buildbot/clients/debug.py similarity index 100% rename from buildbot/clients/debug.py rename to master/buildbot/clients/debug.py diff --git a/buildbot/clients/gtkPanes.py b/master/buildbot/clients/gtkPanes.py similarity index 100% rename from buildbot/clients/gtkPanes.py rename to master/buildbot/clients/gtkPanes.py diff --git a/buildbot/clients/sendchange.py b/master/buildbot/clients/sendchange.py similarity index 100% rename from buildbot/clients/sendchange.py rename to master/buildbot/clients/sendchange.py diff --git a/buildbot/clients/tryclient.py b/master/buildbot/clients/tryclient.py similarity index 100% rename from buildbot/clients/tryclient.py rename to master/buildbot/clients/tryclient.py diff --git a/buildbot/config.py b/master/buildbot/config.py similarity index 100% rename from buildbot/config.py rename to master/buildbot/config.py diff --git a/buildbot/db/__init__.py b/master/buildbot/db/__init__.py similarity index 100% rename from buildbot/db/__init__.py rename to master/buildbot/db/__init__.py diff --git a/buildbot/db/connector.py b/master/buildbot/db/connector.py similarity index 100% rename from buildbot/db/connector.py rename to master/buildbot/db/connector.py diff --git a/buildbot/db/dbspec.py b/master/buildbot/db/dbspec.py similarity index 100% rename from buildbot/db/dbspec.py rename to master/buildbot/db/dbspec.py diff --git a/buildbot/db/exceptions.py b/master/buildbot/db/exceptions.py similarity index 100% rename from buildbot/db/exceptions.py rename to master/buildbot/db/exceptions.py diff --git a/buildbot/db/schema/__init__.py b/master/buildbot/db/schema/__init__.py similarity index 100% rename from buildbot/db/schema/__init__.py rename to master/buildbot/db/schema/__init__.py diff --git a/buildbot/db/schema/base.py b/master/buildbot/db/schema/base.py similarity index 100% rename from buildbot/db/schema/base.py rename to master/buildbot/db/schema/base.py diff --git a/buildbot/db/schema/manager.py b/master/buildbot/db/schema/manager.py similarity index 100% rename from buildbot/db/schema/manager.py rename to master/buildbot/db/schema/manager.py diff --git a/buildbot/db/schema/tables.sql b/master/buildbot/db/schema/tables.sql similarity index 100% rename from buildbot/db/schema/tables.sql rename to master/buildbot/db/schema/tables.sql diff --git a/buildbot/db/schema/v1.py b/master/buildbot/db/schema/v1.py similarity index 100% rename from buildbot/db/schema/v1.py rename to master/buildbot/db/schema/v1.py diff --git a/buildbot/db/schema/v2.py b/master/buildbot/db/schema/v2.py similarity index 100% rename from buildbot/db/schema/v2.py rename to master/buildbot/db/schema/v2.py diff --git a/buildbot/db/schema/v3.py b/master/buildbot/db/schema/v3.py similarity index 100% rename from buildbot/db/schema/v3.py rename to master/buildbot/db/schema/v3.py diff --git a/buildbot/db/schema/v4.py b/master/buildbot/db/schema/v4.py similarity index 100% rename from buildbot/db/schema/v4.py rename to master/buildbot/db/schema/v4.py diff --git a/buildbot/db/schema/v5.py b/master/buildbot/db/schema/v5.py similarity index 100% rename from buildbot/db/schema/v5.py rename to master/buildbot/db/schema/v5.py diff --git a/buildbot/db/util.py b/master/buildbot/db/util.py similarity index 100% rename from buildbot/db/util.py rename to master/buildbot/db/util.py diff --git a/buildbot/ec2buildslave.py b/master/buildbot/ec2buildslave.py similarity index 100% rename from buildbot/ec2buildslave.py rename to master/buildbot/ec2buildslave.py diff --git a/buildbot/interfaces.py b/master/buildbot/interfaces.py similarity index 100% rename from buildbot/interfaces.py rename to master/buildbot/interfaces.py diff --git a/buildbot/locks.py b/master/buildbot/locks.py similarity index 100% rename from buildbot/locks.py rename to master/buildbot/locks.py diff --git a/buildbot/manhole.py b/master/buildbot/manhole.py similarity index 100% rename from buildbot/manhole.py rename to master/buildbot/manhole.py diff --git a/buildbot/master.py b/master/buildbot/master.py similarity index 100% rename from buildbot/master.py rename to master/buildbot/master.py diff --git a/buildbot/pbutil.py b/master/buildbot/pbutil.py similarity index 100% rename from buildbot/pbutil.py rename to master/buildbot/pbutil.py diff --git a/buildbot/process/__init__.py b/master/buildbot/process/__init__.py similarity index 100% rename from buildbot/process/__init__.py rename to master/buildbot/process/__init__.py diff --git a/buildbot/process/base.py b/master/buildbot/process/base.py similarity index 100% rename from buildbot/process/base.py rename to master/buildbot/process/base.py diff --git a/buildbot/process/builder.py b/master/buildbot/process/builder.py similarity index 100% rename from buildbot/process/builder.py rename to master/buildbot/process/builder.py diff --git a/buildbot/process/buildstep.py b/master/buildbot/process/buildstep.py similarity index 100% rename from buildbot/process/buildstep.py rename to master/buildbot/process/buildstep.py diff --git a/buildbot/process/factory.py b/master/buildbot/process/factory.py similarity index 100% rename from buildbot/process/factory.py rename to master/buildbot/process/factory.py diff --git a/buildbot/process/mtrlogobserver.py b/master/buildbot/process/mtrlogobserver.py similarity index 100% rename from buildbot/process/mtrlogobserver.py rename to master/buildbot/process/mtrlogobserver.py diff --git a/buildbot/process/process_twisted.py b/master/buildbot/process/process_twisted.py similarity index 100% rename from buildbot/process/process_twisted.py rename to master/buildbot/process/process_twisted.py diff --git a/buildbot/process/properties.py b/master/buildbot/process/properties.py similarity index 100% rename from buildbot/process/properties.py rename to master/buildbot/process/properties.py diff --git a/buildbot/process/subunitlogobserver.py b/master/buildbot/process/subunitlogobserver.py similarity index 100% rename from buildbot/process/subunitlogobserver.py rename to master/buildbot/process/subunitlogobserver.py diff --git a/buildbot/scheduler.py b/master/buildbot/scheduler.py similarity index 100% rename from buildbot/scheduler.py rename to master/buildbot/scheduler.py diff --git a/buildbot/schedulers/__init__.py b/master/buildbot/schedulers/__init__.py similarity index 100% rename from buildbot/schedulers/__init__.py rename to master/buildbot/schedulers/__init__.py diff --git a/buildbot/schedulers/base.py b/master/buildbot/schedulers/base.py similarity index 100% rename from buildbot/schedulers/base.py rename to master/buildbot/schedulers/base.py diff --git a/buildbot/schedulers/basic.py b/master/buildbot/schedulers/basic.py similarity index 100% rename from buildbot/schedulers/basic.py rename to master/buildbot/schedulers/basic.py diff --git a/buildbot/schedulers/filter.py b/master/buildbot/schedulers/filter.py similarity index 100% rename from buildbot/schedulers/filter.py rename to master/buildbot/schedulers/filter.py diff --git a/buildbot/schedulers/manager.py b/master/buildbot/schedulers/manager.py similarity index 100% rename from buildbot/schedulers/manager.py rename to master/buildbot/schedulers/manager.py diff --git a/buildbot/schedulers/timed.py b/master/buildbot/schedulers/timed.py similarity index 100% rename from buildbot/schedulers/timed.py rename to master/buildbot/schedulers/timed.py diff --git a/buildbot/schedulers/triggerable.py b/master/buildbot/schedulers/triggerable.py similarity index 100% rename from buildbot/schedulers/triggerable.py rename to master/buildbot/schedulers/triggerable.py diff --git a/buildbot/schedulers/trysched.py b/master/buildbot/schedulers/trysched.py similarity index 100% rename from buildbot/schedulers/trysched.py rename to master/buildbot/schedulers/trysched.py diff --git a/buildbot/scripts/__init__.py b/master/buildbot/scripts/__init__.py similarity index 100% rename from buildbot/scripts/__init__.py rename to master/buildbot/scripts/__init__.py diff --git a/buildbot/scripts/checkconfig.py b/master/buildbot/scripts/checkconfig.py similarity index 100% rename from buildbot/scripts/checkconfig.py rename to master/buildbot/scripts/checkconfig.py diff --git a/buildbot/scripts/logwatcher.py b/master/buildbot/scripts/logwatcher.py similarity index 100% rename from buildbot/scripts/logwatcher.py rename to master/buildbot/scripts/logwatcher.py diff --git a/buildbot/scripts/reconfig.py b/master/buildbot/scripts/reconfig.py similarity index 100% rename from buildbot/scripts/reconfig.py rename to master/buildbot/scripts/reconfig.py diff --git a/buildbot/scripts/runner.py b/master/buildbot/scripts/runner.py similarity index 100% rename from buildbot/scripts/runner.py rename to master/buildbot/scripts/runner.py diff --git a/buildbot/scripts/sample.cfg b/master/buildbot/scripts/sample.cfg similarity index 100% rename from buildbot/scripts/sample.cfg rename to master/buildbot/scripts/sample.cfg diff --git a/buildbot/scripts/startup.py b/master/buildbot/scripts/startup.py similarity index 100% rename from buildbot/scripts/startup.py rename to master/buildbot/scripts/startup.py diff --git a/buildbot/sourcestamp.py b/master/buildbot/sourcestamp.py similarity index 100% rename from buildbot/sourcestamp.py rename to master/buildbot/sourcestamp.py diff --git a/buildbot/slave/__init__.py b/master/buildbot/status/__init__.py similarity index 100% rename from buildbot/slave/__init__.py rename to master/buildbot/status/__init__.py diff --git a/buildbot/status/base.py b/master/buildbot/status/base.py similarity index 100% rename from buildbot/status/base.py rename to master/buildbot/status/base.py diff --git a/buildbot/status/builder.py b/master/buildbot/status/builder.py similarity index 100% rename from buildbot/status/builder.py rename to master/buildbot/status/builder.py diff --git a/buildbot/status/client.py b/master/buildbot/status/client.py similarity index 100% rename from buildbot/status/client.py rename to master/buildbot/status/client.py diff --git a/buildbot/status/html.py b/master/buildbot/status/html.py similarity index 100% rename from buildbot/status/html.py rename to master/buildbot/status/html.py diff --git a/buildbot/status/mail.py b/master/buildbot/status/mail.py similarity index 100% rename from buildbot/status/mail.py rename to master/buildbot/status/mail.py diff --git a/buildbot/status/persistent_queue.py b/master/buildbot/status/persistent_queue.py similarity index 100% rename from buildbot/status/persistent_queue.py rename to master/buildbot/status/persistent_queue.py diff --git a/buildbot/status/progress.py b/master/buildbot/status/progress.py similarity index 100% rename from buildbot/status/progress.py rename to master/buildbot/status/progress.py diff --git a/buildbot/status/status_push.py b/master/buildbot/status/status_push.py similarity index 100% rename from buildbot/status/status_push.py rename to master/buildbot/status/status_push.py diff --git a/buildbot/status/tinderbox.py b/master/buildbot/status/tinderbox.py similarity index 100% rename from buildbot/status/tinderbox.py rename to master/buildbot/status/tinderbox.py diff --git a/buildbot/slave/commands/__init__.py b/master/buildbot/status/web/__init__.py similarity index 100% rename from buildbot/slave/commands/__init__.py rename to master/buildbot/status/web/__init__.py diff --git a/buildbot/status/web/about.py b/master/buildbot/status/web/about.py similarity index 100% rename from buildbot/status/web/about.py rename to master/buildbot/status/web/about.py diff --git a/buildbot/status/web/auth.py b/master/buildbot/status/web/auth.py similarity index 100% rename from buildbot/status/web/auth.py rename to master/buildbot/status/web/auth.py diff --git a/buildbot/status/web/authz.py b/master/buildbot/status/web/authz.py similarity index 100% rename from buildbot/status/web/authz.py rename to master/buildbot/status/web/authz.py diff --git a/buildbot/status/web/base.py b/master/buildbot/status/web/base.py similarity index 100% rename from buildbot/status/web/base.py rename to master/buildbot/status/web/base.py diff --git a/buildbot/status/web/baseweb.py b/master/buildbot/status/web/baseweb.py similarity index 100% rename from buildbot/status/web/baseweb.py rename to master/buildbot/status/web/baseweb.py diff --git a/buildbot/status/web/build.py b/master/buildbot/status/web/build.py similarity index 100% rename from buildbot/status/web/build.py rename to master/buildbot/status/web/build.py diff --git a/buildbot/status/web/builder.py b/master/buildbot/status/web/builder.py similarity index 100% rename from buildbot/status/web/builder.py rename to master/buildbot/status/web/builder.py diff --git a/buildbot/status/web/buildstatus.py b/master/buildbot/status/web/buildstatus.py similarity index 100% rename from buildbot/status/web/buildstatus.py rename to master/buildbot/status/web/buildstatus.py diff --git a/buildbot/status/web/changes.py b/master/buildbot/status/web/changes.py similarity index 100% rename from buildbot/status/web/changes.py rename to master/buildbot/status/web/changes.py diff --git a/buildbot/status/web/console.py b/master/buildbot/status/web/console.py similarity index 100% rename from buildbot/status/web/console.py rename to master/buildbot/status/web/console.py diff --git a/buildbot/status/web/feeds.py b/master/buildbot/status/web/feeds.py similarity index 100% rename from buildbot/status/web/feeds.py rename to master/buildbot/status/web/feeds.py diff --git a/buildbot/status/web/files/bg_gradient.jpg b/master/buildbot/status/web/files/bg_gradient.jpg similarity index 100% rename from buildbot/status/web/files/bg_gradient.jpg rename to master/buildbot/status/web/files/bg_gradient.jpg diff --git a/buildbot/status/web/files/default.css b/master/buildbot/status/web/files/default.css similarity index 100% rename from buildbot/status/web/files/default.css rename to master/buildbot/status/web/files/default.css diff --git a/buildbot/status/web/files/favicon.ico b/master/buildbot/status/web/files/favicon.ico similarity index 100% rename from buildbot/status/web/files/favicon.ico rename to master/buildbot/status/web/files/favicon.ico diff --git a/buildbot/status/web/files/robots.txt b/master/buildbot/status/web/files/robots.txt similarity index 100% rename from buildbot/status/web/files/robots.txt rename to master/buildbot/status/web/files/robots.txt diff --git a/buildbot/status/web/grid.py b/master/buildbot/status/web/grid.py similarity index 100% rename from buildbot/status/web/grid.py rename to master/buildbot/status/web/grid.py diff --git a/buildbot/status/web/logs.py b/master/buildbot/status/web/logs.py similarity index 100% rename from buildbot/status/web/logs.py rename to master/buildbot/status/web/logs.py diff --git a/buildbot/status/web/olpb.py b/master/buildbot/status/web/olpb.py similarity index 100% rename from buildbot/status/web/olpb.py rename to master/buildbot/status/web/olpb.py diff --git a/buildbot/status/web/root.py b/master/buildbot/status/web/root.py similarity index 100% rename from buildbot/status/web/root.py rename to master/buildbot/status/web/root.py diff --git a/buildbot/status/web/slaves.py b/master/buildbot/status/web/slaves.py similarity index 100% rename from buildbot/status/web/slaves.py rename to master/buildbot/status/web/slaves.py diff --git a/buildbot/status/web/status_json.py b/master/buildbot/status/web/status_json.py similarity index 100% rename from buildbot/status/web/status_json.py rename to master/buildbot/status/web/status_json.py diff --git a/buildbot/status/web/step.py b/master/buildbot/status/web/step.py similarity index 100% rename from buildbot/status/web/step.py rename to master/buildbot/status/web/step.py diff --git a/buildbot/status/web/templates/about.html b/master/buildbot/status/web/templates/about.html similarity index 100% rename from buildbot/status/web/templates/about.html rename to master/buildbot/status/web/templates/about.html diff --git a/buildbot/status/web/templates/authfail.html b/master/buildbot/status/web/templates/authfail.html similarity index 100% rename from buildbot/status/web/templates/authfail.html rename to master/buildbot/status/web/templates/authfail.html diff --git a/buildbot/status/web/templates/box_macros.html b/master/buildbot/status/web/templates/box_macros.html similarity index 100% rename from buildbot/status/web/templates/box_macros.html rename to master/buildbot/status/web/templates/box_macros.html diff --git a/buildbot/status/web/templates/build.html b/master/buildbot/status/web/templates/build.html similarity index 100% rename from buildbot/status/web/templates/build.html rename to master/buildbot/status/web/templates/build.html diff --git a/buildbot/status/web/templates/build_line.html b/master/buildbot/status/web/templates/build_line.html similarity index 100% rename from buildbot/status/web/templates/build_line.html rename to master/buildbot/status/web/templates/build_line.html diff --git a/buildbot/status/web/templates/builder.html b/master/buildbot/status/web/templates/builder.html similarity index 100% rename from buildbot/status/web/templates/builder.html rename to master/buildbot/status/web/templates/builder.html diff --git a/buildbot/status/web/templates/builders.html b/master/buildbot/status/web/templates/builders.html similarity index 100% rename from buildbot/status/web/templates/builders.html rename to master/buildbot/status/web/templates/builders.html diff --git a/buildbot/status/web/templates/buildslave.html b/master/buildbot/status/web/templates/buildslave.html similarity index 100% rename from buildbot/status/web/templates/buildslave.html rename to master/buildbot/status/web/templates/buildslave.html diff --git a/buildbot/status/web/templates/buildslaves.html b/master/buildbot/status/web/templates/buildslaves.html similarity index 100% rename from buildbot/status/web/templates/buildslaves.html rename to master/buildbot/status/web/templates/buildslaves.html diff --git a/buildbot/status/web/templates/buildstatus.html b/master/buildbot/status/web/templates/buildstatus.html similarity index 100% rename from buildbot/status/web/templates/buildstatus.html rename to master/buildbot/status/web/templates/buildstatus.html diff --git a/buildbot/status/web/templates/buildstep.html b/master/buildbot/status/web/templates/buildstep.html similarity index 100% rename from buildbot/status/web/templates/buildstep.html rename to master/buildbot/status/web/templates/buildstep.html diff --git a/buildbot/status/web/templates/change.html b/master/buildbot/status/web/templates/change.html similarity index 100% rename from buildbot/status/web/templates/change.html rename to master/buildbot/status/web/templates/change.html diff --git a/buildbot/status/web/templates/change_macros.html b/master/buildbot/status/web/templates/change_macros.html similarity index 100% rename from buildbot/status/web/templates/change_macros.html rename to master/buildbot/status/web/templates/change_macros.html diff --git a/buildbot/status/web/templates/change_sources.html b/master/buildbot/status/web/templates/change_sources.html similarity index 100% rename from buildbot/status/web/templates/change_sources.html rename to master/buildbot/status/web/templates/change_sources.html diff --git a/buildbot/status/web/templates/console.html b/master/buildbot/status/web/templates/console.html similarity index 100% rename from buildbot/status/web/templates/console.html rename to master/buildbot/status/web/templates/console.html diff --git a/buildbot/status/web/templates/directory.html b/master/buildbot/status/web/templates/directory.html similarity index 100% rename from buildbot/status/web/templates/directory.html rename to master/buildbot/status/web/templates/directory.html diff --git a/buildbot/status/web/templates/empty.html b/master/buildbot/status/web/templates/empty.html similarity index 100% rename from buildbot/status/web/templates/empty.html rename to master/buildbot/status/web/templates/empty.html diff --git a/buildbot/status/web/templates/feed_atom10.xml b/master/buildbot/status/web/templates/feed_atom10.xml similarity index 100% rename from buildbot/status/web/templates/feed_atom10.xml rename to master/buildbot/status/web/templates/feed_atom10.xml diff --git a/buildbot/status/web/templates/feed_description.html b/master/buildbot/status/web/templates/feed_description.html similarity index 100% rename from buildbot/status/web/templates/feed_description.html rename to master/buildbot/status/web/templates/feed_description.html diff --git a/buildbot/status/web/templates/feed_rss20.xml b/master/buildbot/status/web/templates/feed_rss20.xml similarity index 100% rename from buildbot/status/web/templates/feed_rss20.xml rename to master/buildbot/status/web/templates/feed_rss20.xml diff --git a/buildbot/status/web/templates/footer.html b/master/buildbot/status/web/templates/footer.html similarity index 100% rename from buildbot/status/web/templates/footer.html rename to master/buildbot/status/web/templates/footer.html diff --git a/buildbot/status/web/templates/forms.html b/master/buildbot/status/web/templates/forms.html similarity index 100% rename from buildbot/status/web/templates/forms.html rename to master/buildbot/status/web/templates/forms.html diff --git a/buildbot/status/web/templates/grid.html b/master/buildbot/status/web/templates/grid.html similarity index 100% rename from buildbot/status/web/templates/grid.html rename to master/buildbot/status/web/templates/grid.html diff --git a/buildbot/status/web/templates/grid_macros.html b/master/buildbot/status/web/templates/grid_macros.html similarity index 100% rename from buildbot/status/web/templates/grid_macros.html rename to master/buildbot/status/web/templates/grid_macros.html diff --git a/buildbot/status/web/templates/grid_transposed.html b/master/buildbot/status/web/templates/grid_transposed.html similarity index 100% rename from buildbot/status/web/templates/grid_transposed.html rename to master/buildbot/status/web/templates/grid_transposed.html diff --git a/buildbot/status/web/templates/jsonhelp.html b/master/buildbot/status/web/templates/jsonhelp.html similarity index 100% rename from buildbot/status/web/templates/jsonhelp.html rename to master/buildbot/status/web/templates/jsonhelp.html diff --git a/buildbot/status/web/templates/layout.html b/master/buildbot/status/web/templates/layout.html similarity index 100% rename from buildbot/status/web/templates/layout.html rename to master/buildbot/status/web/templates/layout.html diff --git a/buildbot/status/web/templates/logs.html b/master/buildbot/status/web/templates/logs.html similarity index 100% rename from buildbot/status/web/templates/logs.html rename to master/buildbot/status/web/templates/logs.html diff --git a/buildbot/status/web/templates/onelineperbuild.html b/master/buildbot/status/web/templates/onelineperbuild.html similarity index 100% rename from buildbot/status/web/templates/onelineperbuild.html rename to master/buildbot/status/web/templates/onelineperbuild.html diff --git a/buildbot/status/web/templates/onelineperbuildonebuilder.html b/master/buildbot/status/web/templates/onelineperbuildonebuilder.html similarity index 100% rename from buildbot/status/web/templates/onelineperbuildonebuilder.html rename to master/buildbot/status/web/templates/onelineperbuildonebuilder.html diff --git a/buildbot/status/web/templates/revmacros.html b/master/buildbot/status/web/templates/revmacros.html similarity index 100% rename from buildbot/status/web/templates/revmacros.html rename to master/buildbot/status/web/templates/revmacros.html diff --git a/buildbot/status/web/templates/root.html b/master/buildbot/status/web/templates/root.html similarity index 100% rename from buildbot/status/web/templates/root.html rename to master/buildbot/status/web/templates/root.html diff --git a/buildbot/status/web/templates/waterfall.html b/master/buildbot/status/web/templates/waterfall.html similarity index 100% rename from buildbot/status/web/templates/waterfall.html rename to master/buildbot/status/web/templates/waterfall.html diff --git a/buildbot/status/web/templates/waterfallhelp.html b/master/buildbot/status/web/templates/waterfallhelp.html similarity index 100% rename from buildbot/status/web/templates/waterfallhelp.html rename to master/buildbot/status/web/templates/waterfallhelp.html diff --git a/buildbot/status/web/waterfall.py b/master/buildbot/status/web/waterfall.py similarity index 100% rename from buildbot/status/web/waterfall.py rename to master/buildbot/status/web/waterfall.py diff --git a/buildbot/status/web/xmlrpc.py b/master/buildbot/status/web/xmlrpc.py similarity index 100% rename from buildbot/status/web/xmlrpc.py rename to master/buildbot/status/web/xmlrpc.py diff --git a/buildbot/status/words.py b/master/buildbot/status/words.py similarity index 100% rename from buildbot/status/words.py rename to master/buildbot/status/words.py diff --git a/buildbot/status/__init__.py b/master/buildbot/steps/__init__.py similarity index 100% rename from buildbot/status/__init__.py rename to master/buildbot/steps/__init__.py diff --git a/buildbot/steps/dummy.py b/master/buildbot/steps/dummy.py similarity index 100% rename from buildbot/steps/dummy.py rename to master/buildbot/steps/dummy.py diff --git a/buildbot/steps/master.py b/master/buildbot/steps/master.py similarity index 100% rename from buildbot/steps/master.py rename to master/buildbot/steps/master.py diff --git a/buildbot/steps/maxq.py b/master/buildbot/steps/maxq.py similarity index 100% rename from buildbot/steps/maxq.py rename to master/buildbot/steps/maxq.py diff --git a/buildbot/steps/package/__init__.py b/master/buildbot/steps/package/__init__.py similarity index 100% rename from buildbot/steps/package/__init__.py rename to master/buildbot/steps/package/__init__.py diff --git a/buildbot/steps/package/rpm/__init__.py b/master/buildbot/steps/package/rpm/__init__.py similarity index 100% rename from buildbot/steps/package/rpm/__init__.py rename to master/buildbot/steps/package/rpm/__init__.py diff --git a/buildbot/steps/package/rpm/rpmbuild.py b/master/buildbot/steps/package/rpm/rpmbuild.py similarity index 100% rename from buildbot/steps/package/rpm/rpmbuild.py rename to master/buildbot/steps/package/rpm/rpmbuild.py diff --git a/buildbot/steps/package/rpm/rpmlint.py b/master/buildbot/steps/package/rpm/rpmlint.py similarity index 100% rename from buildbot/steps/package/rpm/rpmlint.py rename to master/buildbot/steps/package/rpm/rpmlint.py diff --git a/buildbot/steps/package/rpm/rpmspec.py b/master/buildbot/steps/package/rpm/rpmspec.py similarity index 100% rename from buildbot/steps/package/rpm/rpmspec.py rename to master/buildbot/steps/package/rpm/rpmspec.py diff --git a/buildbot/steps/python.py b/master/buildbot/steps/python.py similarity index 100% rename from buildbot/steps/python.py rename to master/buildbot/steps/python.py diff --git a/buildbot/steps/python_twisted.py b/master/buildbot/steps/python_twisted.py similarity index 100% rename from buildbot/steps/python_twisted.py rename to master/buildbot/steps/python_twisted.py diff --git a/buildbot/steps/shell.py b/master/buildbot/steps/shell.py similarity index 100% rename from buildbot/steps/shell.py rename to master/buildbot/steps/shell.py diff --git a/buildbot/steps/source.py b/master/buildbot/steps/source.py similarity index 100% rename from buildbot/steps/source.py rename to master/buildbot/steps/source.py diff --git a/buildbot/steps/subunit.py b/master/buildbot/steps/subunit.py similarity index 100% rename from buildbot/steps/subunit.py rename to master/buildbot/steps/subunit.py diff --git a/buildbot/steps/transfer.py b/master/buildbot/steps/transfer.py similarity index 100% rename from buildbot/steps/transfer.py rename to master/buildbot/steps/transfer.py diff --git a/buildbot/steps/trigger.py b/master/buildbot/steps/trigger.py similarity index 100% rename from buildbot/steps/trigger.py rename to master/buildbot/steps/trigger.py diff --git a/buildbot/steps/vstudio.py b/master/buildbot/steps/vstudio.py similarity index 100% rename from buildbot/steps/vstudio.py rename to master/buildbot/steps/vstudio.py diff --git a/buildbot/test/__init__.py b/master/buildbot/test/__init__.py similarity index 100% rename from buildbot/test/__init__.py rename to master/buildbot/test/__init__.py diff --git a/buildbot/status/web/__init__.py b/master/buildbot/test/fake/__init__.py similarity index 100% rename from buildbot/status/web/__init__.py rename to master/buildbot/test/fake/__init__.py diff --git a/buildbot/test/fake/fakedb.py b/master/buildbot/test/fake/fakedb.py similarity index 100% rename from buildbot/test/fake/fakedb.py rename to master/buildbot/test/fake/fakedb.py diff --git a/buildbot/test/fake/state.py b/master/buildbot/test/fake/state.py similarity index 100% rename from buildbot/test/fake/state.py rename to master/buildbot/test/fake/state.py diff --git a/buildbot/steps/__init__.py b/master/buildbot/test/regressions/__init__.py similarity index 100% rename from buildbot/steps/__init__.py rename to master/buildbot/test/regressions/__init__.py diff --git a/buildbot/test/regressions/test_change_properties.py b/master/buildbot/test/regressions/test_change_properties.py similarity index 100% rename from buildbot/test/regressions/test_change_properties.py rename to master/buildbot/test/regressions/test_change_properties.py diff --git a/buildbot/test/regressions/test_import_unicode_changes.py b/master/buildbot/test/regressions/test_import_unicode_changes.py similarity index 100% rename from buildbot/test/regressions/test_import_unicode_changes.py rename to master/buildbot/test/regressions/test_import_unicode_changes.py diff --git a/buildbot/test/regressions/test_shell_command_properties.py b/master/buildbot/test/regressions/test_shell_command_properties.py similarity index 100% rename from buildbot/test/regressions/test_shell_command_properties.py rename to master/buildbot/test/regressions/test_shell_command_properties.py diff --git a/buildbot/test/fake/__init__.py b/master/buildbot/test/unit/__init__.py similarity index 100% rename from buildbot/test/fake/__init__.py rename to master/buildbot/test/unit/__init__.py diff --git a/buildbot/test/unit/test_db_connector.py b/master/buildbot/test/unit/test_db_connector.py similarity index 100% rename from buildbot/test/unit/test_db_connector.py rename to master/buildbot/test/unit/test_db_connector.py diff --git a/buildbot/test/unit/test_db_dbspec.py b/master/buildbot/test/unit/test_db_dbspec.py similarity index 100% rename from buildbot/test/unit/test_db_dbspec.py rename to master/buildbot/test/unit/test_db_dbspec.py diff --git a/buildbot/test/unit/test_db_schema_master.py b/master/buildbot/test/unit/test_db_schema_master.py similarity index 100% rename from buildbot/test/unit/test_db_schema_master.py rename to master/buildbot/test/unit/test_db_schema_master.py diff --git a/buildbot/test/unit/test_db_util.py b/master/buildbot/test/unit/test_db_util.py similarity index 100% rename from buildbot/test/unit/test_db_util.py rename to master/buildbot/test/unit/test_db_util.py diff --git a/buildbot/test/unit/test_master_cleanshutdown.py b/master/buildbot/test/unit/test_master_cleanshutdown.py similarity index 100% rename from buildbot/test/unit/test_master_cleanshutdown.py rename to master/buildbot/test/unit/test_master_cleanshutdown.py diff --git a/buildbot/test/unit/test_oldpaths.py b/master/buildbot/test/unit/test_oldpaths.py similarity index 100% rename from buildbot/test/unit/test_oldpaths.py rename to master/buildbot/test/unit/test_oldpaths.py diff --git a/buildbot/test/unit/test_persistent_queue.py b/master/buildbot/test/unit/test_persistent_queue.py similarity index 100% rename from buildbot/test/unit/test_persistent_queue.py rename to master/buildbot/test/unit/test_persistent_queue.py diff --git a/buildbot/test/unit/test_schedulers_basic_Scheduler.py b/master/buildbot/test/unit/test_schedulers_basic_Scheduler.py similarity index 100% rename from buildbot/test/unit/test_schedulers_basic_Scheduler.py rename to master/buildbot/test/unit/test_schedulers_basic_Scheduler.py diff --git a/buildbot/test/unit/test_schedulers_filter.py b/master/buildbot/test/unit/test_schedulers_filter.py similarity index 100% rename from buildbot/test/unit/test_schedulers_filter.py rename to master/buildbot/test/unit/test_schedulers_filter.py diff --git a/buildbot/test/unit/test_source_repourl.py b/master/buildbot/test/unit/test_source_repourl.py similarity index 100% rename from buildbot/test/unit/test_source_repourl.py rename to master/buildbot/test/unit/test_source_repourl.py diff --git a/buildbot/test/unit/test_status_mail_MailNotifier.py b/master/buildbot/test/unit/test_status_mail_MailNotifier.py similarity index 100% rename from buildbot/test/unit/test_status_mail_MailNotifier.py rename to master/buildbot/test/unit/test_status_mail_MailNotifier.py diff --git a/buildbot/test/unit/test_status_web_authz_Authz.py b/master/buildbot/test/unit/test_status_web_authz_Authz.py similarity index 100% rename from buildbot/test/unit/test_status_web_authz_Authz.py rename to master/buildbot/test/unit/test_status_web_authz_Authz.py diff --git a/buildbot/test/unit/test_status_web_links.py b/master/buildbot/test/unit/test_status_web_links.py similarity index 100% rename from buildbot/test/unit/test_status_web_links.py rename to master/buildbot/test/unit/test_status_web_links.py diff --git a/buildbot/test/unit/test_util.py b/master/buildbot/test/unit/test_util.py similarity index 100% rename from buildbot/test/unit/test_util.py rename to master/buildbot/test/unit/test_util.py diff --git a/buildbot/test/unit/test_util_ComparableMixin.py b/master/buildbot/test/unit/test_util_ComparableMixin.py similarity index 100% rename from buildbot/test/unit/test_util_ComparableMixin.py rename to master/buildbot/test/unit/test_util_ComparableMixin.py diff --git a/buildbot/test/unit/test_util_collections.py b/master/buildbot/test/unit/test_util_collections.py similarity index 100% rename from buildbot/test/unit/test_util_collections.py rename to master/buildbot/test/unit/test_util_collections.py diff --git a/buildbot/test/unit/test_util_eventual.py b/master/buildbot/test/unit/test_util_eventual.py similarity index 100% rename from buildbot/test/unit/test_util_eventual.py rename to master/buildbot/test/unit/test_util_eventual.py diff --git a/buildbot/test/unit/test_util_loop.py b/master/buildbot/test/unit/test_util_loop.py similarity index 100% rename from buildbot/test/unit/test_util_loop.py rename to master/buildbot/test/unit/test_util_loop.py diff --git a/buildbot/test/regressions/__init__.py b/master/buildbot/test/util/__init__.py similarity index 100% rename from buildbot/test/regressions/__init__.py rename to master/buildbot/test/util/__init__.py diff --git a/buildbot/util/__init__.py b/master/buildbot/util/__init__.py similarity index 100% rename from buildbot/util/__init__.py rename to master/buildbot/util/__init__.py diff --git a/buildbot/util/collections.py b/master/buildbot/util/collections.py similarity index 100% rename from buildbot/util/collections.py rename to master/buildbot/util/collections.py diff --git a/buildbot/util/eventual.py b/master/buildbot/util/eventual.py similarity index 100% rename from buildbot/util/eventual.py rename to master/buildbot/util/eventual.py diff --git a/buildbot/util/loop.py b/master/buildbot/util/loop.py similarity index 100% rename from buildbot/util/loop.py rename to master/buildbot/util/loop.py diff --git a/buildbot/util/monkeypatches.py b/master/buildbot/util/monkeypatches.py similarity index 100% rename from buildbot/util/monkeypatches.py rename to master/buildbot/util/monkeypatches.py diff --git a/setup.py b/master/setup.py similarity index 99% rename from setup.py rename to master/setup.py index 835872913a6..f7117a46e18 100644 --- a/setup.py +++ b/master/setup.py @@ -192,8 +192,6 @@ def finalize_options(self): "buildbot.steps.package.rpm", "buildbot.process", "buildbot.clients", - "buildbot.slave", - "buildbot.slave.commands", "buildbot.schedulers", "buildbot.scripts", "buildbot.db", diff --git a/slave/bbslave/__init__.py b/slave/bbslave/__init__.py new file mode 100644 index 00000000000..5cb91b6a099 --- /dev/null +++ b/slave/bbslave/__init__.py @@ -0,0 +1 @@ +version = "latest" diff --git a/buildbot/slave/bot.py b/slave/bbslave/bot.py similarity index 98% rename from buildbot/slave/bot.py rename to slave/bbslave/bot.py index 5127cdcf9b6..051e6a3b8d1 100644 --- a/buildbot/slave/bot.py +++ b/slave/bbslave/bot.py @@ -1,22 +1,20 @@ - import os.path import sys -import buildbot - from twisted.spread import pb from twisted.python import log from twisted.internet import reactor, defer from twisted.application import service, internet from twisted.cred import credentials -from buildbot.util import now -from buildbot.pbutil import ReconnectingPBClientFactory -from buildbot.slave.commands import registry +import bbslave +from bbslave.util import now +from bbslave.pbutil import ReconnectingPBClientFactory +from bbslave.commands import registry # make sure the standard commands get registered. This import is performed # for its side-effects. -from buildbot.slave.commands import base, transfer, vcs +from bbslave.commands import base, transfer, vcs class NoCommandRunning(pb.Error): pass @@ -343,7 +341,7 @@ def remote_getSlaveInfo(self): def remote_getVersion(self): """Send our version back to the Master""" - return buildbot.version + return bbslave.version @@ -480,7 +478,7 @@ class BuildSlave(service.MultiService): def __init__(self, buildmaster_host, port, name, passwd, basedir, keepalive, usePTY, keepaliveTimeout=30, umask=None, maxdelay=300, debugOpts={}, unicode_encoding=None): - log.msg("Creating BuildSlave -- buildbot.version: %s" % buildbot.version) + log.msg("Creating BuildSlave -- version: %s" % bbslave.version) service.MultiService.__init__(self) self.debugOpts = debugOpts.copy() bot = self.botClass(basedir, usePTY, unicode_encoding=unicode_encoding) diff --git a/buildbot/test/unit/__init__.py b/slave/bbslave/commands/__init__.py similarity index 100% rename from buildbot/test/unit/__init__.py rename to slave/bbslave/commands/__init__.py diff --git a/buildbot/slave/commands/base.py b/slave/bbslave/commands/base.py similarity index 99% rename from buildbot/slave/commands/base.py rename to slave/bbslave/commands/base.py index bdd035db778..05a04d4c15a 100644 --- a/buildbot/slave/commands/base.py +++ b/slave/bbslave/commands/base.py @@ -1,4 +1,3 @@ -# -*- test-case-name: buildbot.test.test_slavecommand -*- import os, signal, types, re, traceback from stat import ST_CTIME, ST_MTIME, ST_SIZE @@ -9,9 +8,9 @@ from twisted.internet import reactor, defer, task from twisted.python import log, runtime -from buildbot.slave.interfaces import ISlaveCommand -from buildbot.slave.commands.registry import registerSlaveCommand -from buildbot import util +from bbslave.interfaces import ISlaveCommand +from bbslave.commands.registry import registerSlaveCommand +from bbslave import util # this used to be a CVS $-style "Revision" auto-updated keyword, but since I # moved to Darcs as the primary repository, this is updated manually each diff --git a/buildbot/slave/commands/registry.py b/slave/bbslave/commands/registry.py similarity index 77% rename from buildbot/slave/commands/registry.py rename to slave/bbslave/commands/registry.py index 772aad33c67..21328da7e2e 100644 --- a/buildbot/slave/commands/registry.py +++ b/slave/bbslave/commands/registry.py @@ -7,9 +7,9 @@ def registerSlaveCommand(name, factory, version): @type name: string @param name: name under which the slave command will be registered; used - for L{buildbot.slave.bot.SlaveBuilder.remote_startCommand} + for L{bbslave.bot.SlaveBuilder.remote_startCommand} - @type factory: L{buildbot.slave.commands.Command} + @type factory: L{bbslave.commands.Command} @type version: string @param version: version string of the factory code """ diff --git a/buildbot/slave/commands/transfer.py b/slave/bbslave/commands/transfer.py similarity index 98% rename from buildbot/slave/commands/transfer.py rename to slave/bbslave/commands/transfer.py index 905a7a3ac6e..284f22f7cb9 100644 --- a/buildbot/slave/commands/transfer.py +++ b/slave/bbslave/commands/transfer.py @@ -3,8 +3,8 @@ from twisted.python import log from twisted.internet import defer -from buildbot.slave.commands.base import Command, command_version -from buildbot.slave.commands.registry import registerSlaveCommand +from bbslave.commands.base import Command, command_version +from bbslave.commands.registry import registerSlaveCommand class SlaveFileUploadCommand(Command): """ diff --git a/buildbot/slave/commands/utils.py b/slave/bbslave/commands/utils.py similarity index 100% rename from buildbot/slave/commands/utils.py rename to slave/bbslave/commands/utils.py diff --git a/buildbot/slave/commands/vcs.py b/slave/bbslave/commands/vcs.py similarity index 99% rename from buildbot/slave/commands/vcs.py rename to slave/bbslave/commands/vcs.py index b7aeb8137fc..46f0c3e68c2 100644 --- a/buildbot/slave/commands/vcs.py +++ b/slave/bbslave/commands/vcs.py @@ -5,10 +5,10 @@ from twisted.python import log, failure, runtime from twisted.internet import defer -from buildbot.slave.commands.base import Command, ShellCommand, AbandonChain, command_version, Obfuscated -from buildbot.slave.commands.registry import registerSlaveCommand -from buildbot.slave.commands.utils import getCommand, rmdirRecursive -from buildbot.util import remove_userpassword +from bbslave.commands.base import Command, ShellCommand, AbandonChain, command_version, Obfuscated +from bbslave.commands.registry import registerSlaveCommand +from bbslave.commands.utils import getCommand, rmdirRecursive +from bbslave.util import remove_userpassword class SourceBase(Command): """Abstract base class for Version Control System operations (checkout diff --git a/buildbot/slave/interfaces.py b/slave/bbslave/interfaces.py similarity index 100% rename from buildbot/slave/interfaces.py rename to slave/bbslave/interfaces.py diff --git a/slave/bbslave/pbutil.py b/slave/bbslave/pbutil.py new file mode 100644 index 00000000000..c53c5a03fdb --- /dev/null +++ b/slave/bbslave/pbutil.py @@ -0,0 +1,140 @@ + +"""Base classes handy for use with PB clients. +""" + +from twisted.spread import pb + +from twisted.spread.pb import PBClientFactory +from twisted.internet import protocol +from twisted.python import log + +class NewCredPerspective(pb.Avatar): + def attached(self, mind): + return self + def detached(self, mind): + pass + +class ReconnectingPBClientFactory(PBClientFactory, + protocol.ReconnectingClientFactory): + """Reconnecting client factory for PB brokers. + + Like PBClientFactory, but if the connection fails or is lost, the factory + will attempt to reconnect. + + Instead of using f.getRootObject (which gives a Deferred that can only + be fired once), override the gotRootObject method. + + Instead of using the newcred f.login (which is also one-shot), call + f.startLogin() with the credentials and client, and override the + gotPerspective method. + + Instead of using the oldcred f.getPerspective (also one-shot), call + f.startGettingPerspective() with the same arguments, and override + gotPerspective. + + gotRootObject and gotPerspective will be called each time the object is + received (once per successful connection attempt). You will probably want + to use obj.notifyOnDisconnect to find out when the connection is lost. + + If an authorization error occurs, failedToGetPerspective() will be + invoked. + + To use me, subclass, then hand an instance to a connector (like + TCPClient). + """ + + def __init__(self): + PBClientFactory.__init__(self) + self._doingLogin = False + self._doingGetPerspective = False + + def clientConnectionFailed(self, connector, reason): + PBClientFactory.clientConnectionFailed(self, connector, reason) + # Twisted-1.3 erroneously abandons the connection on non-UserErrors. + # To avoid this bug, don't upcall, and implement the correct version + # of the method here. + if self.continueTrying: + self.connector = connector + self.retry() + + def clientConnectionLost(self, connector, reason): + PBClientFactory.clientConnectionLost(self, connector, reason, + reconnecting=True) + RCF = protocol.ReconnectingClientFactory + RCF.clientConnectionLost(self, connector, reason) + + def clientConnectionMade(self, broker): + self.resetDelay() + PBClientFactory.clientConnectionMade(self, broker) + if self._doingLogin: + self.doLogin(self._root) + if self._doingGetPerspective: + self.doGetPerspective(self._root) + self.gotRootObject(self._root) + + # oldcred methods + + def getPerspective(self, *args): + raise RuntimeError, "getPerspective is one-shot: use startGettingPerspective instead" + + def startGettingPerspective(self, username, password, serviceName, + perspectiveName=None, client=None): + self._doingGetPerspective = True + if perspectiveName == None: + perspectiveName = username + self._oldcredArgs = (username, password, serviceName, + perspectiveName, client) + + def doGetPerspective(self, root): + # oldcred getPerspective() + (username, password, + serviceName, perspectiveName, client) = self._oldcredArgs + d = self._cbAuthIdentity(root, username, password) + d.addCallback(self._cbGetPerspective, + serviceName, perspectiveName, client) + d.addCallbacks(self.gotPerspective, self.failedToGetPerspective) + + + # newcred methods + + def login(self, *args): + raise RuntimeError, "login is one-shot: use startLogin instead" + + def startLogin(self, credentials, client=None): + self._credentials = credentials + self._client = client + self._doingLogin = True + + def doLogin(self, root): + # newcred login() + d = self._cbSendUsername(root, self._credentials.username, + self._credentials.password, self._client) + d.addCallbacks(self.gotPerspective, self.failedToGetPerspective) + + + # methods to override + + def gotPerspective(self, perspective): + """The remote avatar or perspective (obtained each time this factory + connects) is now available.""" + pass + + def gotRootObject(self, root): + """The remote root object (obtained each time this factory connects) + is now available. This method will be called each time the connection + is established and the object reference is retrieved.""" + pass + + def failedToGetPerspective(self, why): + """The login process failed, most likely because of an authorization + failure (bad password), but it is also possible that we lost the new + connection before we managed to send our credentials. + """ + log.msg("ReconnectingPBClientFactory.failedToGetPerspective") + if why.check(pb.PBConnectionLost): + log.msg("we lost the brand-new connection") + # retrying might help here, let clientConnectionLost decide + return + # probably authorization + self.stopTrying() # logging in harder won't help + log.err(why) diff --git a/buildbot/test/util/__init__.py b/slave/bbslave/scripts/__init__.py similarity index 100% rename from buildbot/test/util/__init__.py rename to slave/bbslave/scripts/__init__.py diff --git a/slave/bbslave/scripts/checkconfig.py b/slave/bbslave/scripts/checkconfig.py new file mode 100644 index 00000000000..d6fc9d0c597 --- /dev/null +++ b/slave/bbslave/scripts/checkconfig.py @@ -0,0 +1,38 @@ +import sys +import os +from shutil import copy, rmtree +from tempfile import mkdtemp +from os.path import isfile + +from buildbot import master + +class ConfigLoader(master.BuildMaster): + def __init__(self, basedir=os.getcwd(), configFileName="master.cfg"): + master.BuildMaster.__init__(self, basedir, configFileName) + configFileName = os.path.join(basedir, configFileName) + dir = os.getcwd() + # Use a temporary directory since loadConfig() creates a bunch of + # directories and compiles .py files + tempdir = mkdtemp() + try: + copy(configFileName, tempdir) + for entry in os.listdir("."): + # Any code in a subdirectory will _not_ be copied! This is a bug + if isfile(entry) and not entry.startswith("twistd.log"): + copy(entry, tempdir) + except: + raise + + try: + os.chdir(tempdir) + # Add the temp directory to the library path so local modules work + sys.path.append(tempdir) + configFile = open(configFileName, "r") + self.loadConfig(configFile, check_synchronously_only=True) + except: + os.chdir(dir) + configFile.close() + rmtree(tempdir) + raise + os.chdir(dir) + rmtree(tempdir) diff --git a/slave/bbslave/scripts/logwatcher.py b/slave/bbslave/scripts/logwatcher.py new file mode 100644 index 00000000000..20fb13292b7 --- /dev/null +++ b/slave/bbslave/scripts/logwatcher.py @@ -0,0 +1,101 @@ + +import os +from twisted.python.failure import Failure +from twisted.internet import defer, reactor, protocol, error +from twisted.protocols.basic import LineOnlyReceiver + +class FakeTransport: + disconnecting = False + +class BuildmasterTimeoutError(Exception): + pass +class BuildslaveTimeoutError(Exception): + pass +class ReconfigError(Exception): + pass +class BuildSlaveDetectedError(Exception): + pass + +class TailProcess(protocol.ProcessProtocol): + def outReceived(self, data): + self.lw.dataReceived(data) + def errReceived(self, data): + print "ERR: '%s'" % (data,) + + +class LogWatcher(LineOnlyReceiver): + POLL_INTERVAL = 0.1 + TIMEOUT_DELAY = 10.0 + delimiter = os.linesep + + def __init__(self, logfile): + self.logfile = logfile + self.in_reconfig = False + self.transport = FakeTransport() + self.pp = TailProcess() + self.pp.lw = self + self.processtype = "buildmaster" + self.timer = None + + def start(self): + # If the log file doesn't exist, create it now. + if not os.path.exists(self.logfile): + open(self.logfile, 'a').close() + + # return a Deferred that fires when the reconfig process has + # finished. It errbacks with TimeoutError if the finish line has not + # been seen within 10 seconds, and with ReconfigError if the error + # line was seen. If the logfile could not be opened, it errbacks with + # an IOError. + self.p = reactor.spawnProcess(self.pp, "/usr/bin/tail", + ("tail", "-f", "-n", "0", self.logfile), + env=os.environ, + ) + self.running = True + d = defer.maybeDeferred(self._start) + return d + + def _start(self): + self.d = defer.Deferred() + self.timer = reactor.callLater(self.TIMEOUT_DELAY, self.timeout) + return self.d + + def timeout(self): + self.timer = None + if self.processtype == "buildmaster": + e = BuildmasterTimeoutError() + else: + e = BuildslaveTimeoutError() + self.finished(Failure(e)) + + def finished(self, results): + try: + self.p.signalProcess("KILL") + except error.ProcessExitedAlready: + pass + if self.timer: + self.timer.cancel() + self.timer = None + self.running = False + self.in_reconfig = False + self.d.callback(results) + + def lineReceived(self, line): + if not self.running: + return + if "Log opened." in line: + self.in_reconfig = True + if "loading configuration from" in line: + self.in_reconfig = True + if "Creating BuildSlave" in line: + self.processtype = "buildslave" + + if self.in_reconfig: + print line + + if "message from master: attached" in line: + return self.finished("buildslave") + if "I will keep using the previous config file" in line: + return self.finished(Failure(ReconfigError())) + if "configuration update complete" in line: + return self.finished("buildmaster") diff --git a/slave/bbslave/scripts/reconfig.py b/slave/bbslave/scripts/reconfig.py new file mode 100644 index 00000000000..104214b5165 --- /dev/null +++ b/slave/bbslave/scripts/reconfig.py @@ -0,0 +1,69 @@ + +import os, signal, platform +from twisted.internet import reactor + +from buildbot.scripts.logwatcher import LogWatcher, BuildmasterTimeoutError, \ + ReconfigError + +class Reconfigurator: + def run(self, config): + # Returns "Microsoft" for Vista and "Windows" for other versions + if platform.system() in ("Windows", "Microsoft"): + print "Reconfig (through SIGHUP) is not supported on Windows." + print "The 'buildbot debugclient' tool can trigger a reconfig" + print "remotely, but requires Gtk+ libraries to run." + return + + basedir = config['basedir'] + quiet = config['quiet'] + os.chdir(basedir) + f = open("twistd.pid", "rt") + self.pid = int(f.read().strip()) + if quiet: + os.kill(self.pid, signal.SIGHUP) + return + + # keep reading twistd.log. Display all messages between "loading + # configuration from ..." and "configuration update complete" or + # "I will keep using the previous config file instead.", or until + # 10 seconds have elapsed. + + self.sent_signal = False + lw = LogWatcher("twistd.log") + d = lw.start() + d.addCallbacks(self.success, self.failure) + reactor.callLater(0.2, self.sighup) + reactor.run() + + def sighup(self): + if self.sent_signal: + return + print "sending SIGHUP to process %d" % self.pid + self.sent_signal = True + os.kill(self.pid, signal.SIGHUP) + + def success(self, res): + print """ +Reconfiguration appears to have completed successfully. +""" + reactor.stop() + + def failure(self, why): + if why.check(BuildmasterTimeoutError): + print "Never saw reconfiguration finish." + elif why.check(ReconfigError): + print """ +Reconfiguration failed. Please inspect the master.cfg file for errors, +correct them, then try 'buildbot reconfig' again. +""" + elif why.check(IOError): + # we were probably unable to open the file in the first place + self.sighup() + else: + print "Error while following twistd.log: %s" % why + reactor.stop() + +def reconfig(config): + r = Reconfigurator() + r.run(config) + diff --git a/slave/bbslave/scripts/runner.py b/slave/bbslave/scripts/runner.py new file mode 100644 index 00000000000..657eebc90f9 --- /dev/null +++ b/slave/bbslave/scripts/runner.py @@ -0,0 +1,1220 @@ +# -*- test-case-name: buildbot.test.test_runner -*- + +# N.B.: don't import anything that might pull in a reactor yet. Some of our +# subcommands want to load modules that need the gtk reactor. +import os, sys, stat, re, time +import traceback +from twisted.python import usage, util, runtime + +from buildbot.interfaces import BuildbotNotRunningError + +# the create/start/stop commands should all be run as the same user, +# preferably a separate 'buildbot' account. + +# Note that the terms 'options' and 'config' are used intechangeably here - in +# fact, they are intercanged several times. Caveat legator. + +class OptionsWithOptionsFile(usage.Options): + # subclasses should set this to a list-of-lists in order to source the + # .buildbot/options file. + # buildbotOptions = [ [ 'optfile-name', 'option-name' ], .. ] + buildbotOptions = None + + def __init__(self, *args): + # for options in self.buildbotOptions, optParameters, and the options + # file, change the default in optParameters *before* calling through + # to the parent constructor + + if self.buildbotOptions: + optfile = loadOptionsFile() + for optfile_name, option_name in self.buildbotOptions: + for i in range(len(self.optParameters)): + if self.optParameters[i][0] == option_name and optfile_name in optfile: + self.optParameters[i][2] = optfile[optfile_name] + usage.Options.__init__(self, *args) + +def loadOptionsFile(filename="options", here=None, home=None): + """Find the .buildbot/FILENAME file. Crawl from the current directory up + towards the root, and also look in ~/.buildbot . The first directory + that's owned by the user and has the file we're looking for wins. Windows + skips the owned-by-user test. + + @rtype: dict + @return: a dictionary of names defined in the options file. If no options + file was found, return an empty dict. + """ + + if here is None: + here = os.getcwd() + here = os.path.abspath(here) + + if home is None: + if runtime.platformType == 'win32': + # never trust env-vars, use the proper API + from win32com.shell import shellcon, shell + appdata = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) + home = os.path.join(appdata, "buildbot") + else: + home = os.path.expanduser("~/.buildbot") + + searchpath = [] + toomany = 20 + while True: + searchpath.append(os.path.join(here, ".buildbot")) + next = os.path.dirname(here) + if next == here: + break # we've hit the root + here = next + toomany -= 1 # just in case + if toomany == 0: + raise ValueError("Hey, I seem to have wandered up into the " + "infinite glories of the heavens. Oops.") + searchpath.append(home) + + localDict = {} + + for d in searchpath: + if os.path.isdir(d): + if runtime.platformType != 'win32': + if os.stat(d)[stat.ST_UID] != os.getuid(): + print "skipping %s because you don't own it" % d + continue # security, skip other people's directories + optfile = os.path.join(d, filename) + if os.path.exists(optfile): + try: + f = open(optfile, "r") + options = f.read() + exec options in localDict + except: + print "error while reading %s" % optfile + raise + break + + for k in localDict.keys(): + if k.startswith("__"): + del localDict[k] + return localDict + +class MakerBase(OptionsWithOptionsFile): + optFlags = [ + ['help', 'h', "Display this message"], + ["quiet", "q", "Do not emit the commands being run"], + ] + + longdesc = """ + Operates upon the specified (or the current directory, if not + specified). + """ + + opt_h = usage.Options.opt_help + + def parseArgs(self, *args): + if len(args) > 0: + self['basedir'] = args[0] + else: + # Use the current directory if no basedir was specified. + self['basedir'] = os.getcwd() + if len(args) > 1: + raise usage.UsageError("I wasn't expecting so many arguments") + + def postOptions(self): + self['basedir'] = os.path.abspath(self['basedir']) + +makefile_sample = """# -*- makefile -*- + +# This is a simple makefile which lives in a buildmaster/buildslave +# directory (next to the buildbot.tac file). It allows you to start/stop the +# master or slave by doing 'make start' or 'make stop'. + +# The 'reconfig' target will tell a buildmaster to reload its config file. + +start: + twistd --no_save -y buildbot.tac + +stop: + if [ -e twistd.pid ]; \\ + then kill `cat twistd.pid`; \\ + else echo "Nothing to stop."; \\ + fi + +reconfig: + if [ -e twistd.pid ]; \\ + then kill -HUP `cat twistd.pid`; \\ + else echo "Nothing to reconfig."; \\ + fi + +log: + if [ -e twistd.log ]; \\ + then tail -f twistd.log; \\ + else echo "Nothing to tail."; \\ + fi +""" + +class Maker: + def __init__(self, config): + self.config = config + self.basedir = config['basedir'] + self.force = config.get('force', False) + self.quiet = config['quiet'] + + def mkdir(self): + if os.path.exists(self.basedir): + if not self.quiet: + print "updating existing installation" + return + if not self.quiet: print "mkdir", self.basedir + os.mkdir(self.basedir) + + def mkinfo(self): + path = os.path.join(self.basedir, "info") + if not os.path.exists(path): + if not self.quiet: print "mkdir", path + os.mkdir(path) + created = False + admin = os.path.join(path, "admin") + if not os.path.exists(admin): + if not self.quiet: + print "Creating info/admin, you need to edit it appropriately" + f = open(admin, "wt") + f.write("Your Name Here \n") + f.close() + created = True + host = os.path.join(path, "host") + if not os.path.exists(host): + if not self.quiet: + print "Creating info/host, you need to edit it appropriately" + f = open(host, "wt") + f.write("Please put a description of this build host here\n") + f.close() + created = True + access_uri = os.path.join(path, "access_uri") + if not os.path.exists(access_uri): + if not self.quiet: + print "Not creating info/access_uri - add it if you wish" + if created and not self.quiet: + print "Please edit the files in %s appropriately." % path + + def chdir(self): + if not self.quiet: print "chdir", self.basedir + os.chdir(self.basedir) + + def makeTAC(self, contents, secret=False): + tacfile = "buildbot.tac" + if os.path.exists(tacfile): + oldcontents = open(tacfile, "rt").read() + if oldcontents == contents: + if not self.quiet: + print "buildbot.tac already exists and is correct" + return + if not self.quiet: + print "not touching existing buildbot.tac" + print "creating buildbot.tac.new instead" + tacfile = "buildbot.tac.new" + f = open(tacfile, "wt") + f.write(contents) + f.close() + if secret: + os.chmod(tacfile, 0600) + + def makefile(self): + target = "Makefile.sample" + if os.path.exists(target): + oldcontents = open(target, "rt").read() + if oldcontents == makefile_sample: + if not self.quiet: + print "Makefile.sample already exists and is correct" + return + if not self.quiet: + print "replacing Makefile.sample" + else: + if not self.quiet: + print "creating Makefile.sample" + f = open(target, "wt") + f.write(makefile_sample) + f.close() + + def sampleconfig(self, source): + target = "master.cfg.sample" + config_sample = open(source, "rt").read() + if os.path.exists(target): + oldcontents = open(target, "rt").read() + if oldcontents == config_sample: + if not self.quiet: + print "master.cfg.sample already exists and is up-to-date" + return + if not self.quiet: + print "replacing master.cfg.sample" + else: + if not self.quiet: + print "creating master.cfg.sample" + f = open(target, "wt") + f.write(config_sample) + f.close() + os.chmod(target, 0600) + + def public_html(self, files): + webdir = os.path.join(self.basedir, "public_html") + if os.path.exists(webdir): + if not self.quiet: + print "public_html/ already exists: not replacing" + return + else: + os.mkdir(webdir) + if not self.quiet: + print "populating public_html/" + for target, source in files.iteritems(): + target = os.path.join(webdir, target) + f = open(target, "wt") + f.write(open(source, "rt").read()) + f.close() + + def create_db(self): + from buildbot.db import dbspec, exceptions, schema + spec = dbspec.DBSpec.from_url(self.config["db"], self.basedir) + if not self.config['quiet']: print "creating database" + + # upgrade from "nothing" + sm = schema.DBSchemaManager(spec, self.basedir) + if sm.get_db_version() != 0: + raise exceptions.DBAlreadyExistsError + sm.upgrade() + + def populate_if_missing(self, target, source, overwrite=False): + new_contents = open(source, "rt").read() + if os.path.exists(target): + old_contents = open(target, "rt").read() + if old_contents != new_contents: + if overwrite: + if not self.quiet: + print "%s has old/modified contents" % target + print " overwriting it with new contents" + open(target, "wt").write(new_contents) + else: + if not self.quiet: + print "%s has old/modified contents" % target + print " writing new contents to %s.new" % target + open(target + ".new", "wt").write(new_contents) + # otherwise, it's up to date + else: + if not self.quiet: + print "populating %s" % target + open(target, "wt").write(new_contents) + + def move_if_present(self, source, dest): + if os.path.exists(source): + if os.path.exists(dest): + print "Notice: %s now overrides %s" % (dest, source) + print " as the latter is not used by buildbot anymore." + print " Decide which one you want to keep." + else: + try: + print "Notice: Moving %s to %s." % (source, dest) + print " You can (and probably want to) remove it if you haven't modified this file." + os.renames(source, dest) + except Exception, e: + print "Error moving %s to %s: %s" % (source, dest, str(e)) + + def upgrade_public_html(self, files): + webdir = os.path.join(self.basedir, "public_html") + if not os.path.exists(webdir): + if not self.quiet: + print "populating public_html/" + os.mkdir(webdir) + for target, source in files.iteritems(): + self.populate_if_missing(os.path.join(webdir, target), + source) + + def check_master_cfg(self): + from buildbot.master import BuildMaster + from twisted.python import log, failure + + master_cfg = os.path.join(self.basedir, "master.cfg") + if not os.path.exists(master_cfg): + if not self.quiet: + print "No master.cfg found" + return 1 + + # side-effects of loading the config file: + + # for each Builder defined in c['builders'], if the status directory + # didn't already exist, it will be created, and the + # $BUILDERNAME/builder pickle might be created (with a single + # "builder created" event). + + # we put basedir in front of sys.path, because that's how the + # buildmaster itself will run, and it is quite common to have the + # buildmaster import helper classes from other .py files in its + # basedir. + + if sys.path[0] != self.basedir: + sys.path.insert(0, self.basedir) + + m = BuildMaster(self.basedir) + # we need to route log.msg to stdout, so any problems can be seen + # there. But if everything goes well, I'd rather not clutter stdout + # with log messages. So instead we add a logObserver which gathers + # messages and only displays them if something goes wrong. + messages = [] + log.addObserver(messages.append) + try: + # this will raise an exception if there's something wrong with + # the config file. Note that this BuildMaster instance is never + # started, so it won't actually do anything with the + # configuration. + m.loadConfig(open(master_cfg, "r"), check_synchronously_only=True) + except: + f = failure.Failure() + if not self.quiet: + print + for m in messages: + print "".join(m['message']) + print f + print + print "An error was detected in the master.cfg file." + print "Please correct the problem and run 'buildbot upgrade-master' again." + print + return 1 + return 0 + +DB_HELP = """ + The --db string is evaluated to build the DB object, which specifies + which database the buildmaster should use to hold scheduler state and + status information. The default (which creates an SQLite database in + BASEDIR/state.sqlite) is equivalent to: + + --db='DBSpec("sqlite3", basedir+"/state.sqlite"))' + --db='sqlite:///state.sqlite' + + To use a remote MySQL database instead, use something like: + + --db='mysql://bbuser:bbpasswd@dbhost/bbdb' +""" + +class UpgradeMasterOptions(MakerBase): + optFlags = [ + ["replace", "r", "Replace any modified files without confirmation."], + ] + optParameters = [ + ["db", None, "sqlite:///state.sqlite", + "which DB to use for scheduler/status state. See below for syntax."], + ] + + def getSynopsis(self): + return "Usage: buildbot upgrade-master [options] []" + + longdesc = """ + This command takes an existing buildmaster working directory and + adds/modifies the files there to work with the current version of + buildbot. When this command is finished, the buildmaster directory should + look much like a brand-new one created by the 'create-master' command. + + Use this after you've upgraded your buildbot installation and before you + restart the buildmaster to use the new version. + + If you have modified the files in your working directory, this command + will leave them untouched, but will put the new recommended contents in a + .new file (for example, if index.html has been modified, this command + will create index.html.new). You can then look at the new version and + decide how to merge its contents into your modified file. +"""+DB_HELP+""" + When upgrading from a pre-0.8.0 release (which did not use a database), + this command will create the given database and migrate data from the old + pickle files into it, then move the pickle files out of the way (e.g. to + changes.pck.old). To revert to an older release, rename the pickle files + back. When you are satisfied with the new version, you can delete the old + pickle files. + """ + +def upgradeMaster(config): + basedir = os.path.expanduser(config['basedir']) + m = Maker(config) + # TODO: check Makefile + # TODO: check TAC file + # check web files: index.html, default.css, robots.txt + m.upgrade_public_html({ + 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"), + 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"), + 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"), + 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"), + }) + m.populate_if_missing(os.path.join(basedir, "master.cfg.sample"), + util.sibpath(__file__, "sample.cfg"), + overwrite=True) + # if index.html exists, use it to override the root page tempalte + m.move_if_present(os.path.join(basedir, "public_html/index.html"), + os.path.join(basedir, "templates/root.html")) + + from buildbot.db import connector, dbspec + spec = dbspec.DBSpec.from_url(config["db"], basedir) + # TODO: check that TAC file specifies the right spec + + # upgrade the db + from buildbot.db.schema import manager + sm = manager.DBSchemaManager(spec, basedir) + sm.upgrade() + + # check the configuration + rc = m.check_master_cfg() + if rc: + return rc + if not config['quiet']: print "upgrade complete" + return 0 + + +class MasterOptions(MakerBase): + optFlags = [ + ["force", "f", + "Re-use an existing directory (will not overwrite master.cfg file)"], + ["relocatable", "r", + "Create a relocatable buildbot.tac"], + ] + optParameters = [ + ["config", "c", "master.cfg", "name of the buildmaster config file"], + ["log-size", "s", "1000000", + "size at which to rotate twisted log files"], + ["log-count", "l", "None", + "limit the number of kept old twisted log files"], + ["db", None, "sqlite:///state.sqlite", + "which DB to use for scheduler/status state. See below for syntax."], + ] + def getSynopsis(self): + return "Usage: buildbot create-master [options] []" + + longdesc = """ + This command creates a buildmaster working directory and buildbot.tac file. + The master will live in and create various files there. If + --relocatable is given, then the resulting buildbot.tac file will be + written such that its containing directory is assumed to be the basedir. + This is generally a good idea. + + At runtime, the master will read a configuration file (named + 'master.cfg' by default) in its basedir. This file should contain python + code which eventually defines a dictionary named 'BuildmasterConfig'. + The elements of this dictionary are used to configure the Buildmaster. + See doc/config.xhtml for details about what can be controlled through + this interface. +""" + DB_HELP + """ + The --db string is stored verbatim in the buildbot.tac file, and + evaluated as 'buildbot start' time to pass a DBConnector instance into + the newly-created BuildMaster object. + """ + + def postOptions(self): + MakerBase.postOptions(self) + if not re.match('^\d+$', self['log-size']): + raise usage.UsageError("log-size parameter needs to be an int") + if not re.match('^\d+$', self['log-count']) and \ + self['log-count'] != 'None': + raise usage.UsageError("log-count parameter needs to be an int "+ + " or None") + + +masterTAC = """ +import os + +from twisted.application import service +from buildbot.master import BuildMaster + +basedir = r'%(basedir)s' +rotateLength = %(log-size)s +maxRotatedFiles = %(log-count)s + +# if this is a relocatable tac file, get the directory containing the TAC +if basedir == '.': + import os.path + basedir = os.path.abspath(os.path.dirname(__file__)) + +application = service.Application('buildmaster') +try: + from twisted.python.logfile import LogFile + from twisted.python.log import ILogObserver, FileLogObserver + logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, + maxRotatedFiles=maxRotatedFiles) + application.setComponent(ILogObserver, FileLogObserver(logfile).emit) +except ImportError: + # probably not yet twisted 8.2.0 and beyond, can't set log yet + pass + +configfile = r'%(config)s' + +m = BuildMaster(basedir, configfile) +m.setServiceParent(application) +m.log_rotation.rotateLength = rotateLength +m.log_rotation.maxRotatedFiles = maxRotatedFiles + +""" + +def createMaster(config): + m = Maker(config) + m.mkdir() + m.chdir() + if config['relocatable']: + config['basedir'] = '.' + contents = masterTAC % config + m.makeTAC(contents) + m.sampleconfig(util.sibpath(__file__, "sample.cfg")) + m.public_html({ + 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"), + 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"), + 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"), + 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"), + }) + m.makefile() + m.create_db() + + if not m.quiet: print "buildmaster configured in %s" % m.basedir + +class SlaveOptions(MakerBase): + optFlags = [ + ["force", "f", "Re-use an existing directory"], + ["relocatable", "r", + "Create a relocatable buildbot.tac"], + ] + optParameters = [ +# ["name", "n", None, "Name for this build slave"], +# ["passwd", "p", None, "Password for this build slave"], +# ["basedir", "d", ".", "Base directory to use"], +# ["master", "m", "localhost:8007", +# "Location of the buildmaster (host:port)"], + + ["keepalive", "k", 600, + "Interval at which keepalives should be sent (in seconds)"], + ["usepty", None, 0, + "(1 or 0) child processes should be run in a pty (default 0)"], + ["umask", None, "None", + "controls permissions of generated files. Use --umask=022 to be world-readable"], + ["maxdelay", None, 300, + "Maximum time between connection attempts"], + ["log-size", "s", "1000000", + "size at which to rotate twisted log files"], + ["log-count", "l", "None", + "limit the number of kept old twisted log files"], + ] + + longdesc = """ + This command creates a buildslave working directory and buildbot.tac + file. The bot will use the and arguments to authenticate + itself when connecting to the master. All commands are run in a + build-specific subdirectory of . is a string of the + form 'hostname:port', and specifies where the buildmaster can be reached. + + , , and will be provided by the buildmaster + administrator for your bot. You must choose yourself. + """ + + def getSynopsis(self): + return "Usage: buildbot create-slave [options] " + + def parseArgs(self, *args): + if len(args) < 4: + raise usage.UsageError("command needs more arguments") + basedir, master, name, passwd = args + if master[:5] == "http:": + raise usage.UsageError(" is not a URL - do not use URL") + self['basedir'] = basedir + self['master'] = master + self['name'] = name + self['passwd'] = passwd + + def postOptions(self): + MakerBase.postOptions(self) + self['usepty'] = int(self['usepty']) + self['keepalive'] = int(self['keepalive']) + self['maxdelay'] = int(self['maxdelay']) + if self['master'].find(":") == -1: + raise usage.UsageError("--master must be in the form host:portnum") + if not re.match('^\d+$', self['log-size']): + raise usage.UsageError("log-size parameter needs to be an int") + if not re.match('^\d+$', self['log-count']) and \ + self['log-count'] != 'None': + raise usage.UsageError("log-count parameter needs to be an int "+ + " or None") + +slaveTAC = """ +import os + +from twisted.application import service +from buildbot.slave.bot import BuildSlave + +basedir = r'%(basedir)s' +rotateLength = %(log-size)s +maxRotatedFiles = %(log-count)s + +# if this is a relocatable tac file, get the directory containing the TAC +if basedir == '.': + import os.path + basedir = os.path.abspath(os.path.dirname(__file__)) + +application = service.Application('buildslave') +try: + from twisted.python.logfile import LogFile + from twisted.python.log import ILogObserver, FileLogObserver + logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, + maxRotatedFiles=maxRotatedFiles) + application.setComponent(ILogObserver, FileLogObserver(logfile).emit) +except ImportError: + # probably not yet twisted 8.2.0 and beyond, can't set log yet + pass + +buildmaster_host = '%(host)s' +port = %(port)d +slavename = '%(name)s' +passwd = '%(passwd)s' +keepalive = %(keepalive)d +usepty = %(usepty)d +umask = %(umask)s +maxdelay = %(maxdelay)d + +s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir, + keepalive, usepty, umask=umask, maxdelay=maxdelay) +s.setServiceParent(application) + +""" + +def createSlave(config): + m = Maker(config) + m.mkdir() + m.chdir() + if config['relocatable']: + config['basedir'] = '.' + try: + master = config['master'] + host, port = re.search(r'(.+):(\d+)', master).groups() + config['host'] = host + config['port'] = int(port) + except: + print "unparseable master location '%s'" % master + print " expecting something more like localhost:8007" + raise + contents = slaveTAC % config + + m.makeTAC(contents, secret=True) + + m.makefile() + m.mkinfo() + + if not m.quiet: print "buildslave configured in %s" % m.basedir + + + +def stop(config, signame="TERM", wait=False): + import signal + basedir = config['basedir'] + quiet = config['quiet'] + os.chdir(basedir) + try: + f = open("twistd.pid", "rt") + except: + raise BuildbotNotRunningError + pid = int(f.read().strip()) + signum = getattr(signal, "SIG"+signame) + timer = 0 + try: + os.kill(pid, signum) + except OSError, e: + if e.errno != 3: + raise + + if not wait: + if not quiet: + print "sent SIG%s to process" % signame + return + time.sleep(0.1) + while timer < 10: + # poll once per second until twistd.pid goes away, up to 10 seconds + try: + os.kill(pid, 0) + except OSError: + if not quiet: + print "buildbot process %d is dead" % pid + return + timer += 1 + time.sleep(1) + if not quiet: + print "never saw process go away" + +def restart(config): + quiet = config['quiet'] + from buildbot.scripts.startup import start + try: + stop(config, wait=True) + except BuildbotNotRunningError: + pass + if not quiet: + print "now restarting buildbot process.." + start(config) + + +class StartOptions(MakerBase): + optFlags = [ + ['quiet', 'q', "Don't display startup log messages"], + ] + def getSynopsis(self): + return "Usage: buildbot start []" + +class StopOptions(MakerBase): + def getSynopsis(self): + return "Usage: buildbot stop []" + +class ReconfigOptions(MakerBase): + optFlags = [ + ['quiet', 'q', "Don't display log messages about reconfiguration"], + ] + def getSynopsis(self): + return "Usage: buildbot reconfig []" + + + +class RestartOptions(MakerBase): + optFlags = [ + ['quiet', 'q', "Don't display startup log messages"], + ] + def getSynopsis(self): + return "Usage: buildbot restart []" + +class DebugClientOptions(OptionsWithOptionsFile): + optFlags = [ + ['help', 'h', "Display this message"], + ] + optParameters = [ + ["master", "m", None, + "Location of the buildmaster's slaveport (host:port)"], + ["passwd", "p", None, "Debug password to use"], + ] + buildbotOptions = [ + [ 'debugMaster', 'passwd' ], + [ 'master', 'master' ], + ] + + def parseArgs(self, *args): + if len(args) > 0: + self['master'] = args[0] + if len(args) > 1: + self['passwd'] = args[1] + if len(args) > 2: + raise usage.UsageError("I wasn't expecting so many arguments") + +def debugclient(config): + from buildbot.clients import debug + + master = config.get('master') + if master is None: + raise usage.UsageError("master must be specified: on the command " + "line or in ~/.buildbot/options") + + passwd = config.get('passwd') + if passwd is None: + raise usage.UsageError("passwd must be specified: on the command " + "line or in ~/.buildbot/options") + + d = debug.DebugWidget(master, passwd) + d.run() + +class StatusClientOptions(OptionsWithOptionsFile): + optFlags = [ + ['help', 'h', "Display this message"], + ] + optParameters = [ + ["master", "m", None, + "Location of the buildmaster's status port (host:port)"], + ["username", "u", "statusClient", "Username performing the trial build"], + ["passwd", None, "clientpw", "password for PB authentication"], + ] + buildbotOptions = [ + [ 'masterstatus', 'master' ], + ] + + def parseArgs(self, *args): + if len(args) > 0: + self['master'] = args[0] + if len(args) > 1: + raise usage.UsageError("I wasn't expecting so many arguments") + +def statuslog(config): + from buildbot.clients import base + master = config.get('master') + if master is None: + raise usage.UsageError("master must be specified: on the command " + "line or in ~/.buildbot/options") + passwd = config.get('passwd') + username = config.get('username') + c = base.TextClient(master, username=username, passwd=passwd) + c.run() + +def statusgui(config): + from buildbot.clients import gtkPanes + master = config.get('master') + if master is None: + raise usage.UsageError("master must be specified: on the command " + "line or in ~/.buildbot/options") + c = gtkPanes.GtkClient(master) + c.run() + +class SendChangeOptions(OptionsWithOptionsFile): + def __init__(self): + OptionsWithOptionsFile.__init__(self) + self['properties'] = {} + + optParameters = [ + ("master", "m", None, + "Location of the buildmaster's PBListener (host:port)"), + ("username", "u", None, "Username performing the commit"), + ("repository", "R", None, "Repository specifier"), + ("project", "P", None, "Project specifier"), + ("branch", "b", None, "Branch specifier"), + ("category", "c", None, "Category of repository"), + ("revision", "r", None, "Revision specifier"), + ("revision_file", None, None, "Filename containing revision spec"), + ("property", "p", None, + "A property for the change, in the format: name:value"), + ("comments", "m", None, "log message"), + ("logfile", "F", None, + "Read the log messages from this file (- for stdin)"), + ("when", "w", None, "timestamp to use as the change time"), + ] + + buildbotOptions = [ + [ 'master', 'master' ], + [ 'username', 'username' ], + [ 'branch', 'branch' ], + [ 'category', 'category' ], + ] + + def getSynopsis(self): + return "Usage: buildbot sendchange [options] filenames.." + def parseArgs(self, *args): + self['files'] = args + def opt_property(self, property): + name,value = property.split(':') + self['properties'][name] = value + + +def sendchange(config, runReactor=False): + """Send a single change to the buildmaster's PBChangeSource. The + connection will be drpoped as soon as the Change has been sent.""" + from buildbot.clients.sendchange import Sender + + user = config.get('username') + master = config.get('master') + branch = config.get('branch') + category = config.get('category') + revision = config.get('revision') + properties = config.get('properties', {}) + repository = config.get('repository', '') + project = config.get('project', '') + if config.get('when'): + when = float(config.get('when')) + else: + when = None + if config.get("revision_file"): + revision = open(config["revision_file"],"r").read() + + comments = config.get('comments') + if not comments and config.get('logfile'): + if config['logfile'] == "-": + f = sys.stdin + else: + f = open(config['logfile'], "rt") + comments = f.read() + if comments is None: + comments = "" + + files = config.get('files', []) + + assert user, "you must provide a username" + assert master, "you must provide the master location" + + s = Sender(master, user) + d = s.send(branch, revision, comments, files, category=category, when=when, + properties=properties, repository=repository, project=project) + if runReactor: + d.addCallbacks(s.printSuccess, s.printFailure) + d.addBoth(s.stop) + s.run() + return d + + +class ForceOptions(OptionsWithOptionsFile): + optParameters = [ + ["builder", None, None, "which Builder to start"], + ["branch", None, None, "which branch to build"], + ["revision", None, None, "which revision to build"], + ["reason", None, None, "the reason for starting the build"], + ] + + def parseArgs(self, *args): + args = list(args) + if len(args) > 0: + if self['builder'] is not None: + raise usage.UsageError("--builder provided in two ways") + self['builder'] = args.pop(0) + if len(args) > 0: + if self['reason'] is not None: + raise usage.UsageError("--reason provided in two ways") + self['reason'] = " ".join(args) + + +class TryOptions(OptionsWithOptionsFile): + optParameters = [ + ["connect", "c", None, + "how to reach the buildmaster, either 'ssh' or 'pb'"], + # for ssh, use --tryhost, --username, and --trydir + ["tryhost", None, None, + "the hostname (used by ssh) for the buildmaster"], + ["trydir", None, None, + "the directory (on the tryhost) where tryjobs are deposited"], + ["username", "u", None, "Username performing the trial build"], + # for PB, use --master, --username, and --passwd + ["master", "m", None, + "Location of the buildmaster's PBListener (host:port)"], + ["passwd", None, None, "password for PB authentication"], + + ["diff", None, None, + "Filename of a patch to use instead of scanning a local tree. Use '-' for stdin."], + ["patchlevel", "p", 0, + "Number of slashes to remove from patch pathnames, like the -p option to 'patch'"], + + ["baserev", None, None, + "Base revision to use instead of scanning a local tree."], + + ["vc", None, None, + "The VC system in use, one of: cvs,svn,tla,baz,darcs,p4"], + ["branch", None, None, + "The branch in use, for VC systems that can't figure it out" + " themselves"], + + ["builder", "b", None, + "Run the trial build on this Builder. Can be used multiple times."], + ["properties", None, None, + "A set of properties made available in the build environment, format:prop=value,propb=valueb..."], + + ["try-topfile", None, None, + "Name of a file at the top of the tree, used to find the top. Only needed for SVN and CVS."], + ["try-topdir", None, None, + "Path to the top of the working copy. Only needed for SVN and CVS."], + + ] + + optFlags = [ + ["wait", None, "wait until the builds have finished"], + ["dryrun", 'n', "Gather info, but don't actually submit."], + ["get-builder-names", None, "Get the names of available builders. Doesn't submit anything. Only supported for 'pb' connections."], + ] + + # here it is, the definitive, quirky mapping of .buildbot/options names to + # command-line options. Design by committee, anyone? + buildbotOptions = [ + [ 'try_connect', 'connect' ], + #[ 'try_builders', 'builders' ], <-- handled in postOptions + [ 'try_vc', 'vc' ], + [ 'try_branch', 'branch' ], + [ 'try_topdir', 'try-topdir' ], + [ 'try_topfile', 'try-topfile' ], + [ 'try_host', 'tryhost' ], + [ 'try_username', 'username' ], + [ 'try_dir', 'trydir' ], + [ 'try_password', 'passwd' ], + [ 'try_master', 'master' ], + #[ 'try_wait', 'wait' ], <-- handled in postOptions + [ 'masterstatus', 'master' ], + ] + + def __init__(self): + OptionsWithOptionsFile.__init__(self) + self['builders'] = [] + self['properties'] = {} + + def opt_builder(self, option): + self['builders'].append(option) + + def opt_properties(self, option): + # We need to split the value of this option into a dictionary of properties + properties = {} + propertylist = option.split(",") + for i in range(0,len(propertylist)): + print propertylist[i] + splitproperty = propertylist[i].split("=") + properties[splitproperty[0]] = splitproperty[1] + self['properties'] = properties + + def opt_patchlevel(self, option): + self['patchlevel'] = int(option) + + def getSynopsis(self): + return "Usage: buildbot try [options]" + + def postOptions(self): + opts = loadOptionsFile() + if not self['builders']: + self['builders'] = opts.get('try_builders', []) + if opts.get('try_wait', False): + self['wait'] = True + +def doTry(config): + from buildbot.clients import tryclient + t = tryclient.Try(config) + t.run() + +class TryServerOptions(OptionsWithOptionsFile): + optParameters = [ + ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"], + ] + +def doTryServer(config): + import md5 + jobdir = os.path.expanduser(config["jobdir"]) + job = sys.stdin.read() + # now do a 'safecat'-style write to jobdir/tmp, then move atomically to + # jobdir/new . Rather than come up with a unique name randomly, I'm just + # going to MD5 the contents and prepend a timestamp. + timestring = "%d" % time.time() + jobhash = md5.new(job).hexdigest() + fn = "%s-%s" % (timestring, jobhash) + tmpfile = os.path.join(jobdir, "tmp", fn) + newfile = os.path.join(jobdir, "new", fn) + f = open(tmpfile, "w") + f.write(job) + f.close() + os.rename(tmpfile, newfile) + + +class CheckConfigOptions(OptionsWithOptionsFile): + optFlags = [ + ['quiet', 'q', "Don't display error messages or tracebacks"], + ] + + def getSynopsis(self): + return "Usage :buildbot checkconfig [configFile]\n" + \ + " If not specified, 'master.cfg' will be used as 'configFile'" + + def parseArgs(self, *args): + if len(args) >= 1: + self['configFile'] = args[0] + else: + self['configFile'] = 'master.cfg' + + +def doCheckConfig(config): + quiet = config.get('quiet') + configFileName = config.get('configFile') + try: + from buildbot.scripts.checkconfig import ConfigLoader + if os.path.isdir(configFileName): + ConfigLoader(basedir=configFileName) + else: + ConfigLoader(configFileName=configFileName) + except: + if not quiet: + # Print out the traceback in a nice format + t, v, tb = sys.exc_info() + traceback.print_exception(t, v, tb) + sys.exit(1) + + if not quiet: + print "Config file is good!" + + +class Options(usage.Options): + synopsis = "Usage: buildbot [command options]" + + subCommands = [ + # the following are all admin commands + ['create-master', None, MasterOptions, + "Create and populate a directory for a new buildmaster"], + ['upgrade-master', None, UpgradeMasterOptions, + "Upgrade an existing buildmaster directory for the current version"], + ['create-slave', None, SlaveOptions, + "Create and populate a directory for a new buildslave"], + ['start', None, StartOptions, "Start a buildmaster or buildslave"], + ['stop', None, StopOptions, "Stop a buildmaster or buildslave"], + ['restart', None, RestartOptions, + "Restart a buildmaster or buildslave"], + + ['reconfig', None, ReconfigOptions, + "SIGHUP a buildmaster to make it re-read the config file"], + ['sighup', None, ReconfigOptions, + "SIGHUP a buildmaster to make it re-read the config file"], + + ['sendchange', None, SendChangeOptions, + "Send a change to the buildmaster"], + + ['debugclient', None, DebugClientOptions, + "Launch a small debug panel GUI"], + + ['statuslog', None, StatusClientOptions, + "Emit current builder status to stdout"], + ['statusgui', None, StatusClientOptions, + "Display a small window showing current builder status"], + + #['force', None, ForceOptions, "Run a build"], + ['try', None, TryOptions, "Run a build with your local changes"], + + ['tryserver', None, TryServerOptions, + "buildmaster-side 'try' support function, not for users"], + + ['checkconfig', None, CheckConfigOptions, + "test the validity of a master.cfg config file"], + + # TODO: 'watch' + ] + + def opt_version(self): + import buildbot + print "Buildbot version: %s" % buildbot.version + usage.Options.opt_version(self) + + def opt_verbose(self): + from twisted.python import log + log.startLogging(sys.stderr) + + def postOptions(self): + if not hasattr(self, 'subOptions'): + raise usage.UsageError("must specify a command") + + +def run(): + config = Options() + try: + config.parseOptions() + except usage.error, e: + print "%s: %s" % (sys.argv[0], e) + print + c = getattr(config, 'subOptions', config) + print str(c) + sys.exit(1) + + command = config.subCommand + so = config.subOptions + + if command == "create-master": + createMaster(so) + elif command == "upgrade-master": + upgradeMaster(so) + elif command == "create-slave": + createSlave(so) + elif command == "start": + from buildbot.scripts.startup import start + start(so) + elif command == "stop": + stop(so, wait=True) + elif command == "restart": + restart(so) + elif command == "reconfig" or command == "sighup": + from buildbot.scripts.reconfig import Reconfigurator + Reconfigurator().run(so) + elif command == "sendchange": + sendchange(so, True) + elif command == "debugclient": + debugclient(so) + elif command == "statuslog": + statuslog(so) + elif command == "statusgui": + statusgui(so) + elif command == "try": + doTry(so) + elif command == "tryserver": + doTryServer(so) + elif command == "checkconfig": + doCheckConfig(so) + sys.exit(0) + diff --git a/slave/bbslave/scripts/sample.cfg b/slave/bbslave/scripts/sample.cfg new file mode 100644 index 00000000000..115356165b1 --- /dev/null +++ b/slave/bbslave/scripts/sample.cfg @@ -0,0 +1,206 @@ +# -*- python -*- +# ex: set syntax=python: + +# This is a sample buildmaster config file. It must be installed as +# 'master.cfg' in your buildmaster's base directory (although the filename +# can be changed with the --basedir option to 'mktap buildbot master'). + +# It has one job: define a dictionary named BuildmasterConfig. This +# dictionary has a variety of keys to control different aspects of the +# buildmaster. They are documented in docs/config.xhtml . + + +# This is the dictionary that the buildmaster pays attention to. We also use +# a shorter alias to save typing. +c = BuildmasterConfig = {} + +####### DB URL + +# This specifies what database buildbot uses to store change and scheduler +# state +c['db_url'] = "sqlite:///state.sqlite" + +####### BUILDSLAVES + +# the 'slaves' list defines the set of allowable buildslaves. Each element is +# a BuildSlave object, which is created with bot-name, bot-password. These +# correspond to values given to the buildslave's mktap invocation. +from buildbot.buildslave import BuildSlave +c['slaves'] = [BuildSlave("bot1name", "bot1passwd")] + +# to limit to two concurrent builds on a slave, use +# c['slaves'] = [BuildSlave("bot1name", "bot1passwd", max_builds=2)] + + +# 'slavePortnum' defines the TCP port to listen on. This must match the value +# configured into the buildslaves (with their --master option) + +c['slavePortnum'] = 9989 + +####### CHANGESOURCES + +# the 'change_source' setting tells the buildmaster how it should find out +# about source code changes. Any class which implements IChangeSource can be +# put here: there are several in buildbot/changes/*.py to choose from. + +from buildbot.changes.pb import PBChangeSource +c['change_source'] = PBChangeSource() + +# For example, if you had CVSToys installed on your repository, and your +# CVSROOT/freshcfg file had an entry like this: +#pb = ConfigurationSet([ +# (None, None, None, PBService(userpass=('foo', 'bar'), port=4519)), +# ]) + +# then you could use the following buildmaster Change Source to subscribe to +# the FreshCVS daemon and be notified on every commit: +# +#from buildbot.changes.freshcvs import FreshCVSSource +#fc_source = FreshCVSSource("cvs.example.com", 4519, "foo", "bar") +#c['change_source'] = fc_source + +# or, use a PBChangeSource, and then have your repository's commit script run +# 'buildbot sendchange', or use contrib/svn_buildbot.py, or +# contrib/arch_buildbot.py : +# +#from buildbot.changes.pb import PBChangeSource +#c['change_source'] = PBChangeSource() + +# If you wat to use SVNPoller, it might look something like +# # Where to get source code changes +# from buildbot.changes.svnpoller import SVNPoller +# source_code_svn_url='https://svn.myproject.org/bluejay/trunk' +# svn_poller = SVNPoller( +# svnurl=source_code_svn_url, +# pollinterval=60*60, # seconds +# histmax=10, +# svnbin='/usr/bin/svn', +## ) +# c['change_source'] = [ svn_poller ] + +####### SCHEDULERS + +## configure the Schedulers + +from buildbot.scheduler import Scheduler +c['schedulers'] = [] +c['schedulers'].append(Scheduler(name="all", branch=None, + treeStableTimer=2*60, + builderNames=["buildbot-full"])) + + +####### BUILDERS + +# the 'builders' list defines the Builders. Each one is configured with a +# dictionary, using the following keys: +# name (required): the name used to describe this builder +# slavename (required): which slave to use (must appear in c['slaves']) +# builddir (required): which subdirectory to run the builder in +# factory (required): a BuildFactory to define how the build is run +# periodicBuildTime (optional): if set, force a build every N seconds + +# buildbot/process/factory.py provides several BuildFactory classes you can +# start with, which implement build processes for common targets (GNU +# autoconf projects, CPAN perl modules, etc). The factory.BuildFactory is the +# base class, and is configured with a series of BuildSteps. When the build +# is run, the appropriate buildslave is told to execute each Step in turn. + +# the first BuildStep is typically responsible for obtaining a copy of the +# sources. There are source-obtaining Steps in buildbot/steps/source.py for +# CVS, SVN, and others. + +cvsroot = ":pserver:anonymous@cvs.sourceforge.net:/cvsroot/buildbot" +cvsmodule = "buildbot" + +from buildbot.process import factory +from buildbot.steps.source import CVS +from buildbot.steps.shell import Compile +from buildbot.steps.python_twisted import Trial +f1 = factory.BuildFactory() +f1.addStep(CVS(cvsroot=cvsroot, cvsmodule=cvsmodule, login="", mode="copy")) +f1.addStep(Compile(command=["python", "./setup.py", "build"])) +f1.addStep(Trial(testChanges=True, testpath=".")) + +b1 = {'name': "buildbot-full", + 'slavename': "bot1name", + 'builddir': "full", + 'factory': f1, + } +c['builders'] = [b1] + + +####### 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 auth, authz +authz_cfg=authz.Authz( + # change any of these to True to enable; see the manual for more + # options + gracefulShutdown = False, + forceBuild = False, + forceAllBuilds = False, + pingBuilder = False, + stopBuild = False, + stopAllBuilds = False, + cancelPendingBuild = False, +) +c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg)) + +# from buildbot.status import mail +# c['status'].append(mail.MailNotifier(fromaddr="buildbot@localhost", +# extraRecipients=["builds@example.com"], +# sendToInterestedUsers=False)) +# +# from buildbot.status import words +# c['status'].append(words.IRC(host="irc.example.com", nick="bb", +# channels=["#example"])) +# c['status'].append(words.IRC(host="irc.example.com", nick="bb", +# channels=["#example"], useSSL=True)) +# +# from buildbot.status import client +# c['status'].append(client.PBListener(9988)) + + +####### DEBUGGING OPTIONS + +# if you set 'debugPassword', then you can connect to the buildmaster with +# the diagnostic tool in contrib/debugclient.py . From this tool, you can +# manually force builds and inject changes, which may be useful for testing +# your buildmaster without actually committing changes to your repository (or +# before you have a functioning 'sources' set up). The debug tool uses the +# same port number as the slaves do: 'slavePortnum'. + +#c['debugPassword'] = "debugpassword" + +# if you set 'manhole', you can ssh into the buildmaster and get an +# interactive python shell, which may be useful for debugging buildbot +# internals. It is probably only useful for buildbot developers. You can also +# use an authorized_keys file, or plain telnet. +#from buildbot import manhole +#c['manhole'] = manhole.PasswordManhole("tcp:9999:interface=127.0.0.1", +# "admin", "password") + + +####### PROJECT IDENTITY + +# the 'projectName' string will be used to describe the project that this +# buildbot is working on. For example, it is used as the title of the +# waterfall HTML page. The 'projectURL' string will be used to provide a link +# from buildbot HTML pages to your project's home page. + +c['projectName'] = "Buildbot" +c['projectURL'] = "http://buildbot.sourceforge.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://localhost:8010/" diff --git a/slave/bbslave/scripts/startup.py b/slave/bbslave/scripts/startup.py new file mode 100644 index 00000000000..c0a46f0e589 --- /dev/null +++ b/slave/bbslave/scripts/startup.py @@ -0,0 +1,126 @@ + +import os, sys, time + +class Follower: + def follow(self): + from twisted.internet import reactor + from buildbot.scripts.reconfig import LogWatcher + self.rc = 0 + print "Following twistd.log until startup finished.." + lw = LogWatcher("twistd.log") + d = lw.start() + d.addCallbacks(self._success, self._failure) + reactor.run() + return self.rc + + def _success(self, processtype): + from twisted.internet import reactor + print "The %s appears to have (re)started correctly." % processtype + self.rc = 0 + reactor.stop() + + def _failure(self, why): + from twisted.internet import reactor + from buildbot.scripts.logwatcher import BuildmasterTimeoutError, \ + ReconfigError, BuildslaveTimeoutError, BuildSlaveDetectedError + if why.check(BuildmasterTimeoutError): + print """ +The buildmaster took more than 10 seconds to start, so we were unable to +confirm that it started correctly. Please 'tail twistd.log' and look for a +line that says 'configuration update complete' to verify correct startup. +""" + elif why.check(BuildslaveTimeoutError): + print """ +The buildslave took more than 10 seconds to start and/or connect to the +buildmaster, so we were unable to confirm that it started and connected +correctly. Please 'tail twistd.log' and look for a line that says 'message +from master: attached' to verify correct startup. If you see a bunch of +messages like 'will retry in 6 seconds', your buildslave might not have the +correct hostname or portnumber for the buildmaster, or the buildmaster might +not be running. If you see messages like + 'Failure: twisted.cred.error.UnauthorizedLogin' +then your buildslave might be using the wrong botname or password. Please +correct these problems and then restart the buildslave. +""" + elif why.check(ReconfigError): + print """ +The buildmaster appears to have encountered an error in the master.cfg config +file during startup. It is probably running with an empty configuration right +now. Please inspect and fix master.cfg, then restart the buildmaster. +""" + elif why.check(BuildSlaveDetectedError): + print """ +Buildslave is starting up, not following logfile. +""" + else: + print """ +Unable to confirm that the buildmaster started correctly. You may need to +stop it, fix the config file, and restart. +""" + print why + self.rc = 1 + reactor.stop() + + +def start(config): + os.chdir(config['basedir']) + if (not os.path.exists("buildbot.tac") and + not os.path.exists("Makefile.buildbot")): + print "This doesn't look like a buildbot base directory:" + print "No buildbot.tac or Makefile.buildbot file." + print "Giving up!" + sys.exit(1) + if config['quiet']: + return launch(config) + + # we probably can't do this os.fork under windows + from twisted.python.runtime import platformType + if platformType == "win32": + return launch(config) + + # fork a child to launch the daemon, while the parent process tails the + # logfile + if os.fork(): + # this is the parent + rc = Follower().follow() + sys.exit(rc) + # this is the child: give the logfile-watching parent a chance to start + # watching it before we start the daemon + time.sleep(0.2) + launch(config) + +def launch(config): + sys.path.insert(0, os.path.abspath(os.getcwd())) + if os.path.exists("/usr/bin/make") and os.path.exists("Makefile.buildbot"): + # Preferring the Makefile lets slave admins do useful things like set + # up environment variables for the buildslave. + cmd = "make -f Makefile.buildbot start" + if not config['quiet']: + print cmd + os.system(cmd) + else: + # see if we can launch the application without actually having to + # spawn twistd, since spawning processes correctly is a real hassle + # on windows. + from twisted.python.runtime import platformType + argv = ["twistd", + "--no_save", + "--logfile=twistd.log", # windows doesn't use the same default + "--python=buildbot.tac"] + sys.argv = argv + + # this is copied from bin/twistd. twisted-2.0.0 through 2.4.0 use + # _twistw.run . Twisted-2.5.0 and later use twistd.run, even for + # windows. + from twisted import __version__ + major, minor, ignored = __version__.split(".", 2) + major = int(major) + minor = int(minor) + if (platformType == "win32" and (major == 2 and minor < 5)): + from twisted.scripts import _twistw + run = _twistw.run + else: + from twisted.scripts import twistd + run = twistd.run + run() + diff --git a/slave/bbslave/test/__init__.py b/slave/bbslave/test/__init__.py new file mode 100644 index 00000000000..f1bfd2ee244 --- /dev/null +++ b/slave/bbslave/test/__init__.py @@ -0,0 +1,31 @@ +from twisted.trial import unittest + +def add_debugging_monkeypatches(): + """ + DO NOT CALL THIS DIRECTLY + + This adds a few "harmless" monkeypatches which make it easier to debug + failing tests. + """ + from twisted.application.service import Service + old_startService = Service.startService + old_stopService = Service.stopService + def startService(self): + assert not self.running + return old_startService(self) + def stopService(self): + assert self.running + return old_stopService(self) + Service.startService = startService + Service.stopService = stopService + + # make unittest.TestCase have a patch method, even if it just skips + # the test. + def nopatch(self, *args): + raise unittest.SkipTest('unittest.patch is not available') + if not hasattr(unittest.TestCase, 'patch'): + unittest.TestCase.patch = nopatch + +add_debugging_monkeypatches() + +__all__ = [] diff --git a/slave/bbslave/test/unit/__init__.py b/slave/bbslave/test/unit/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/buildbot/test/unit/test_slave_commands_base.py b/slave/bbslave/test/unit/test_slave_commands_base.py similarity index 99% rename from buildbot/test/unit/test_slave_commands_base.py rename to slave/bbslave/test/unit/test_slave_commands_base.py index 6a5935852dc..cc41515bb11 100644 --- a/buildbot/test/unit/test_slave_commands_base.py +++ b/slave/bbslave/test/unit/test_slave_commands_base.py @@ -3,9 +3,9 @@ from twisted.trial import unittest from twisted.internet import task, defer -from buildbot.slave.commands.base import ShellCommand, Obfuscated, \ +from bbslave.commands.base import ShellCommand, Obfuscated, \ DummyCommand, WaitCommand, waitCommandRegistry, AbandonChain -from buildbot.slave.commands.utils import getCommand +from bbslave.commands.utils import getCommand class FakeSlaveBuilder: debug = False diff --git a/buildbot/test/unit/test_slave_commands_utils.py b/slave/bbslave/test/unit/test_slave_commands_utils.py similarity index 99% rename from buildbot/test/unit/test_slave_commands_utils.py rename to slave/bbslave/test/unit/test_slave_commands_utils.py index 3a22ee96ca9..b6fa124bcee 100644 --- a/buildbot/test/unit/test_slave_commands_utils.py +++ b/slave/bbslave/test/unit/test_slave_commands_utils.py @@ -4,7 +4,7 @@ from twisted.trial import unittest import twisted.python.procutils -from buildbot.slave.commands import utils +from bbslave.commands import utils class GetCommand(unittest.TestCase): diff --git a/slave/bbslave/util.py b/slave/bbslave/util.py new file mode 100644 index 00000000000..651cb21624d --- /dev/null +++ b/slave/bbslave/util.py @@ -0,0 +1,22 @@ +import time + +def remove_userpassword(url): + if '@' not in url: + return url + if '://' not in url: + return url + + # urlparse would've been nice, but doesn't support ssh... sigh + protocol_url = url.split('://') + protocol = protocol_url[0] + repo_url = protocol_url[1].split('@')[-1] + + return protocol + '://' + repo_url + + +def now(_reactor=None): + if _reactor and hasattr(_reactor, "seconds"): + return _reactor.seconds() + else: + return time.time() + diff --git a/slave/setup.py b/slave/setup.py new file mode 100644 index 00000000000..58f771bc184 --- /dev/null +++ b/slave/setup.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" +Standard setup script. +""" + +import sys +import os +from distutils.core import setup + +from bbslave import version + +# TODO: slave script +scripts = ["bin/buildbot"] +if sys.platform == "win32": + scripts.append("contrib/windows/buildbot.bat") + scripts.append("contrib/windows/buildbot_service.py") + +setup_args = { + 'name': "bbslave", + 'version': version, + 'description': "BuildBot Slave Daemon", + 'long_description': "See the 'buildbot' project for details", + 'author': "Brian Warner", + 'author_email': "warner-buildbot@lothar.com", + 'maintainer': "Dustin J. Mitchell", + 'maintainer_email': "dustin@v.igoro.us", + 'url': "http://buildbot.net/", + 'license': "GNU GPL", + 'classifiers': [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: No Input/Output (Daemon)', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Topic :: Software Development :: Build Tools', + 'Topic :: Software Development :: Testing', + ], + + 'packages': ["bbslave", + "bbslave.commands", + # TODO: tests + ], + 'scripts': scripts, + } + +try: + # If setuptools is installed, then we'll add setuptools-specific arguments + # to the setup args. + import setuptools #@UnusedImport +except ImportError: + pass +else: + ## dependencies + setup_args['install_requires'] = [ + 'twisted >= 2.0.0', + ] + entry_points={ + 'console_scripts': [ + # TODO: conflicts with master + 'buildbot = buildbot.scripts.runner:run'], + }, + +setup(**setup_args)