def configure_admin(self): """Configure the admin user.""" hookenv.log("Configuring user for jenkins") admin = self._admin_data() api = Api(packages=self._packages) api.update_password(admin.username, admin.password) # Save the password to a file. It's not used directly by this charm # but it's convenient for integration with third-party tools. host.write_file(paths.ADMIN_PASSWORD, admin.password.encode("utf-8"), owner="root", group="root", perms=0o0600) if not os.path.exists(paths.LAST_EXEC): # This mean it's the very first time we configure the user, # and we want to create this file in order to avoid Jenkins # presenting the setup wizard. host.write_file(paths.LAST_EXEC, "{}\n".format(api.version()).encode("utf-8"), owner="jenkins", group="nogroup", perms=0o0600)
def configure_admin(): remove_state("jenkins.configured.admin") api = Api() status_set("maintenance", "Configuring Jenkins public url") configuration = Configuration() needs_restart = configuration.set_url() if needs_restart: status_set("maintenance", "Restarting Jenkins") service_restart('jenkins') api.wait() status_set("maintenance", "Configuring proxy settings") configuration.configure_proxy() service_restart('jenkins') api.wait() status_set("maintenance", "Configuring admin user") users = Users() users.configure_admin() api.reload() api.wait() # Wait for the service to be fully up # Inform any extension that the username/password changed if get_state("extension.connected"): extension_relation = (RelationBase.from_state("extension.connected")) extension_relation.joined() set_state("jenkins.configured.admin")
def configure_admin(): remove_state("jenkins.configured.admin") status_set("maintenance", "Configuring admin user") users = Users() users.configure_admin() api = Api() api.reload() api.wait() # Wait for the service to be fully up set_state("jenkins.configured.admin")
def add_slaves(master): slaves = master.slaves() if not data_changed("master.slaves", slaves): log("Slaves are unchanged - no need to do anything") return api = Api() for slave in slaves: api.add_node( slave["slavehost"], slave["executors"], labels=slave["labels"] or ())
def add_slaves(master): slaves = master.slaves() if not data_changed("master.slaves", slaves): log("Slaves are unchanged - no need to do anything") return api = Api() for slave in slaves: api.add_node(slave["slavehost"], slave["executors"], labels=slave["labels"] or ())
def departed(self): """Indicate the relation is no longer available and not connected.""" # Slave hostname is derived from unit name so # this is pretty safe slavehost = remote_unit() log("Deleting slave with hostname %s." % slavehost) api = Api() api.delete_node(slavehost.replace("/", "-")) self.remove_state("{relation_name}.available") self.remove_state("{relation_name}.connected")
def setUp(self): super(ApiTest, self).setUp() self.useFixture(JenkinsConfiguredAdmin(self.fakes)) self.fakes.jenkins.scripts[GET_LEGACY_TOKEN_SCRIPT.format( "admin")] = "abc\n" self.fakes.jenkins.scripts[GET_NEW_TOKEN_SCRIPT.format( "admin")] = "xyz\n" self.apt = AptStub() self.packages = Packages(apt=self.apt) self.api = Api(packages=self.packages)
def changed(self): """Install optional plugins.""" # extension subordinates may request the principle service install # specified jenkins plugins log("Installing required plugins as requested by jenkins-extension " "subordinate.") plugins = Plugins() plugins.install(relation_get("required_plugins")) api = Api() api.wait() # Wait for the service to be fully up
def broken(self): """Indicate the relation is no longer available and not connected.""" api = Api() for member in relation_ids(): member = member.replace("/", "-") log("Removing node %s from Jenkins master." % member) api.delete_node(member) self.remove_state("{relation_name}.available") self.remove_state("{relation_name}.connected") self.remove_state("{relation_name}.tls.available")
def configure_plugins(): if get_state("extension.connected"): # We've been driven by an extension, let it take control over # plugin. log("External relation detected - skip configuring plugins") return status_set("maintenance", "Configuring plugins") remove_state("jenkins.configured.plugins") plugins = Plugins() plugins.install(config("plugins")) api = Api() api.wait() # Wait for the service to be fully up set_state("jenkins.configured.plugins")
def upgrade_jenkins(): if config("release") == "bundle": packages = Packages() if packages.jenkins_upgradable(): status_set("maintenance", "Upgrading Jenkins") packages.install_jenkins() api = Api() api.wait() # Wait for the upgrade to finish packages.clean_old_plugins() unitdata.kv().set("jenkins.plugins.last_update", 0) update_plugins() else: log("No newer jenkins package is available")
def update_plugins(): last_update = unitdata.kv().get("jenkins.plugins.last_update") if last_update is None: unitdata.kv().set("jenkins.plugins.last_update", 0) last_update = 0 # Only try to update plugins when the interval configured has passed update_interval = time.time() - (config("plugins-auto-update-interval") * 60) if (last_update < update_interval): status_set("maintenance", "Updating plugins") plugins = Plugins() plugins.update(config("plugins")) api = Api() api.wait() # Wait for the service to be fully up unitdata.kv().set("jenkins.plugins.last_update", time.time())
def install(self, plugins): """Install the given plugins, optionally removing unlisted ones. @params plugins: A whitespace-separated list of plugins to install. """ hookenv.log("Starting plugins installation process") plugins = plugins or "" plugins = plugins.split() plugins = self._get_plugins_to_install(plugins) host.mkdir(paths.PLUGINS, owner="jenkins", group="jenkins", perms=0o0755) existing_plugins = set(glob.glob("%s/*.jpi" % paths.PLUGINS)) try: installed_plugins = self._install_plugins(plugins) except Exception: hookenv.log("Plugin installation failed, check logs for details") raise unlisted_plugins = existing_plugins - installed_plugins if unlisted_plugins: if hookenv.config()["remove-unlisted-plugins"] == "yes": self._remove_plugins(unlisted_plugins) else: hookenv.log("Unlisted plugins: (%s) Not removed. Set " "remove-unlisted-plugins to 'yes' to clear them " "away." % ", ".join(unlisted_plugins)) # Restarting jenkins to pickup configuration changes Api().restart() return installed_plugins
def test_check_ready_unavailable(self): """ If the backend keeps returning 5xx, an error is raised. """ api = Api() self.fakes.network.get(api.url, status_code=500) self.assertRaises(ServiceUnavailable, self.service.check_ready)
def configure_proxy(self): """Check whether the machine is configured to use an http(s) proxy and if it does - propagate the environment proxy settings to Jenkins.""" env_proxy = (os.environ['HTTP_PROXY'] or os.environ['HTTPS_PROXY'] or os.environ['http_proxy'] or os.environ['https_proxy']) if not env_proxy: hookenv.log("There are no environment proxy settings") return hookenv.log("There are environment proxy settings") url = urlparse(env_proxy) noproxy = os.environ['NO_PROXY'] or os.environ['no_proxy'] if noproxy: noproxy = ' '.join( [re.sub("^\.", '*.', x.strip()) for x in noproxy.split(',')]) api = Api() api.configure_proxy(url.hostname, url.port, url.username, url.password, noproxy)
def configure_admin(self): """Configure the admin user.""" hookenv.log("Configuring user for jenkins") admin = self._admin_data() api = Api() api.update_password(admin.username, admin.password) # Save the password to a file. It's not used directly by this charm # but it's convenient for integration with third-party tools. host.write_file( paths.ADMIN_PASSWORD, admin.password.encode("utf-8"), owner="root", group="root", perms=0o0600) if not os.path.exists(paths.LAST_EXEC): # This mean it's the very first time we configure the user, # and we want to create this file in order to avoid Jenkins # presenting the setup wizard. host.write_file( paths.LAST_EXEC, "{}\n".format(api.version()).encode("utf-8"), owner="jenkins", group="nogroup")
def _install_plugin(self, plugin, plugins_site, update): """ Verify if the plugin is not installed before installing it or if it needs an update . """ plugin_version = Api().get_plugin_version(plugin) latest_version = self._get_latest_version(plugin) if not plugin_version or (update and plugin_version != latest_version): hookenv.log("Installing plugin %s-%s" % (plugin, latest_version)) plugin_url = ("%s/%s.hpi" % (plugins_site, plugin)) return self._download_plugin(plugin, plugin_url) hookenv.log("Plugin %s-%s already installed" % (plugin, plugin_version))
def update_nrpe_config(nagios): unit_data = unitdata.kv() nagios_hostname = unit_data.get('nagios.hostname', None) nagios_host_context = unit_data.get('nagios.host_context', None) # require the nrpe-external-master relation to provide the host context if in_relation_hook() and relation_id().\ startswith('nrpe-external-master:'): rel = relation_get() if 'nagios_host_context' in rel: nagios_host_context = rel['nagios_host_context'] unit_data.set('nagios.host_context', nagios_host_context) # We have to strip the nagios host context from the nagios hostname # since the nagios.add_check will put it back again... nagios_hostname = rel['nagios_hostname'] if nagios_hostname.startswith(nagios_host_context + '-'): nagios_hostname = nagios_hostname[len(nagios_host_context + '-'):] unit_data.set('nagios.hostname', nagios_hostname) if not nagios_hostname or not nagios_host_context: return # The above boilerplate is needed until this issue is fixed: # # https://github.com/cmars/nrpe-external-master-interface/issues/6 status_set('maintenance', 'Updating Nagios configs') creds = Credentials() check = [ '/usr/lib/nagios/plugins/check_http', '-H', 'localhost', '-p', '8080', '-u', urlparse(Api().url).path, '-a', "{}:{}".format(creds.username(), creds.token()), ] nagios.add_check(check, name="check_jenkins_http", description="Verify Jenkins HTTP is up.", context=nagios_host_context, unit=nagios_hostname) status_set('active', 'Ready')
def update_plugins(): last_update = unitdata.kv().get("jenkins.plugins.last_update") if last_update is None: unitdata.kv().set("jenkins.plugins.last_update", 0) last_update = 0 # Only try to update plugins when the interval configured has passed update_interval = time.time() - (config("plugins-auto-update-interval") * 60) if (last_update < update_interval): status_set("maintenance", "Updating plugins") remove_state("jenkins.updated.plugins") plugins = Plugins() plugins.update(config("plugins")) api = Api() api.wait() # Wait for the service to be fully up # Restart jenkins if any plugin got updated last_restart = unitdata.kv().get("jenkins.last_restart") or 0 last_plugin_update_time = ( unitdata.kv().get("jenkins.plugins.last_plugin_update_time") or 0) if (last_restart < last_plugin_update_time): restart() unitdata.kv().set("jenkins.plugins.last_restart", time.time()) set_state("jenkins.updated.plugins") unitdata.kv().set("jenkins.plugins.last_update", time.time())
def test_check_ready_transient_failure(self): """ Transient failures are retried. """ start = time.time() def callback(requests, context): if time.time() - start >= 2: context.status_code = 503 else: context.status_code = 200 return "" api = Api() self.fakes.network.get(api.url, text=callback) self.assertIsNone(self.service.check_ready())
def _setUp(self): self.fakes.users.add("jenkins", 123) self.fakes.groups.add("nogroup", 456) self.fakes.fs.add(paths.HOME) os.makedirs(paths.SECRETS) with open(paths.INITIAL_PASSWORD, "w") as fd: fd.write(INITIAL_PASSWORD) self.fakes.fs.add(paths.DEFAULTS_CONFIG_FILE) os.makedirs('/etc/default') with open(paths.DEFAULTS_CONFIG_FILE, "wb") as fd: fd.write(b"# port for HTTP connector\nHTTP_PORT=8080\n") fd.write(b'JENKINS_ARGS="--httpPort=$HTTP_PORT"') api = Api() self.fakes.network.get(api.url, headers={"X-Jenkins": "2.0.0"})
def configure_admin(): remove_state("jenkins.configured.admin") status_set("maintenance", "Configuring admin user") users = Users() users.configure_admin() api = Api() api.reload() api.wait() # Wait for the service to be fully up # Inform any extension that the username/password changed if get_state("extension.connected"): extension_relation = (RelationBase.from_state("extension.connected")) extension_relation.joined() set_state("jenkins.configured.admin")
def update(self, plugins): """Try to update the given plugins. @params plugins: A whitespace-separated list of plugins to install. """ plugins = plugins or "" plugins = plugins.split() plugins = self._get_plugins_to_install(plugins) hookenv.log("Updating plugins") try: installed_plugins = self._install_plugins(plugins) except Exception: hookenv.log("Plugin update failed, check logs for details") raise if len(installed_plugins) == 0: hookenv.log("No plugins updated") return else: Api().restart() return installed_plugins
def check_ready(self): """Build a Jenkins client instance.""" api = Api() response = requests.get(api.url) if response.status_code >= 500: raise ServiceUnavailable()
class ApiTest(JenkinsTest): def setUp(self): super(ApiTest, self).setUp() self.useFixture(JenkinsConfiguredAdmin(self.fakes)) self.fakes.jenkins.scripts[GET_LEGACY_TOKEN_SCRIPT.format( "admin")] = "abc\n" self.fakes.jenkins.scripts[GET_NEW_TOKEN_SCRIPT.format( "admin")] = "xyz\n" self.apt = AptStub() self.packages = Packages(apt=self.apt) self.api = Api(packages=self.packages) def test_wait_transient_failure(self): """ Wait for Jenkins to be fully up, even in spite of transient failures. """ self.apt._set_jenkins_version('2.120.1') get_whoami = self.fakes.jenkins.get_whoami tries = [] def transient_failure(): try: if not tries: raise JenkinsException("error") get_whoami() finally: tries.append(True) self.fakes.jenkins.get_whoami = transient_failure self.assertIsNone(self.api.wait()) def test_update_password(self): """ The update_password() method runs a groovy script to update the password for the given user. """ self.apt._set_jenkins_version('2.120.1') username = "******" password = "******" script = UPDATE_PASSWORD_SCRIPT.format(username=username, password=password) self.fakes.jenkins.scripts[script] = "" self.assertIsNone(self.api.update_password(username, password)) def test_version(self): """The version() method returns the version of the Jenkins server.""" self.apt._set_jenkins_version('2.120.1') self.assertEqual("2.0.0", self.api.version()) def test_new_token_script(self): self.apt._set_jenkins_version('2.150.1') self.assertEqual("2.0.0", self.api.version()) def test_add(self): """ A slave node can be added by specifying executors and labels. """ self.apt._set_jenkins_version('2.120.1') self.api.add_node("slave-0", 1, labels=["python"]) [node] = self.fakes.jenkins.nodes self.assertEqual("slave-0", node.host) self.assertEqual(1, node.executors) self.assertEqual("slave-0", node.description) self.assertEqual(["python"], node.labels) self.assertEqual("hudson.slaves.JNLPLauncher", node.launcher) def test_add_exists(self): """ If a node already exists, nothing is done. """ self.apt._set_jenkins_version('2.120.1') self.fakes.jenkins.create_node("slave-0", 1, "slave-0") self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual(1, len(self.fakes.jenkins.nodes)) def test_add_transient_failure(self): """ Transient failures get retried. """ self.apt._set_jenkins_version('2.120.1') create_node = self.fakes.jenkins.create_node tries = [] def transient_failure(*args, **kwargs): try: if not tries: raise JenkinsException("error") create_node(*args, **kwargs) finally: tries.append(True) self.fakes.jenkins.create_node = transient_failure self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual(1, len(self.fakes.jenkins.nodes)) def test_add_retry_give_up(self): """ If errors persist, we give up. """ self.apt._set_jenkins_version('2.120.1') def failure(*args, **kwargs): raise JenkinsException("error") self.fakes.jenkins.create_node = failure self.assertRaises(JenkinsException, self.api.add_node, "slave-0", 1) def test_add_spurious(self): """ If adding a node apparently succeeds, but actually didn't then we log an error. """ self.apt._set_jenkins_version('2.120.1') self.fakes.jenkins.create_node = lambda *args, **kwargs: None self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual("ERROR: Failed to create node 'slave-0'", self.fakes.juju.log[-1]) def test_deleted(self): """ A slave node can be deleted by specifyng its host name. """ self.apt._set_jenkins_version('2.120.1') self.api.add_node("slave-0", 1, labels=["python"]) self.api.delete_node("slave-0") self.assertEqual([], self.fakes.jenkins.nodes) def test_deleted_no_present(self): """ If a slave node doesn't exists, deleting it is a no-op. """ self.apt._set_jenkins_version('2.120.1') self.api.delete_node("slave-0") self.assertEqual([], self.fakes.jenkins.nodes) def _make_httperror(self, url, status_code, reason): response = Response() response.reason = reason response.status_code = status_code response.url = url return HTTPError(request=Request('POST', url), response=response) def test_reload(self): """ The reload method POSTs a request to the '/reload' URL, expecting a 503 on the homepage (which happens after redirection). """ self.apt._set_jenkins_version('2.120.1') error = self._make_httperror(self.api.url, 503, "Service Unavailable") self.fakes.jenkins.responses[urljoin(self.api.url, "reload")] = error self.api.reload() def test_restart(self): """ The reload method POSTs a request to the '/reload' URL, expecting a 503 on the homepage (which happens after redirection). """ self.apt._set_jenkins_version('2.120.1') error = self._make_httperror(self.api.url, 503, "Service Unavailable") self.fakes.jenkins.responses[urljoin(self.api.url, "safeRestart")] = error self.api.restart() def test_reload_unexpected_error(self): """ If the error code is not 403, the error is propagated. """ self.apt._set_jenkins_version('2.120.1') error = self._make_httperror(self.api.url, 403, "Forbidden") self.fakes.jenkins.responses[urljoin(self.api.url, "reload")] = error self.assertRaises(HTTPError, self.api.reload) def test_reload_unexpected_url(self): """ If the error URL is not the root, the error is propagated. """ self.apt._set_jenkins_version('2.120.1') error = self._make_httperror(self.api.url, 503, "Service Unavailable") error.response.url = urljoin(self.api.url, "/foo") self.fakes.jenkins.responses[urljoin(self.api.url, "reload")] = error self.assertRaises(HTTPError, self.api.reload) def test_reload_unexpected_success(self): """ If the request unexpectedly succeeds, an error is raised. """ self.apt._set_jenkins_version('2.120.1') self.fakes.jenkins.responses[urljoin(self.api.url, "reload")] = "home" self.assertRaises(RuntimeError, self.api.reload) def test_url(self): """ Verify the url always ends in a / and has the expected prefix """ config = hookenv.config() orig_public_url = config["public-url"] try: config["public-url"] = "" self.assertEqual(self.api.url, 'http://localhost:8080/') config["public-url"] = "http://here:8080/jenkins" self.assertEqual(self.api.url, 'http://localhost:8080/jenkins/') finally: config["public-url"] = orig_public_url
def setUp(self): super(ApiTest, self).setUp() self.useFixture(JenkinsConfiguredAdmin(self.fakes)) self.fakes.jenkins.scripts[GET_TOKEN_SCRIPT.format("admin")] = "abc\n" self.api = Api()
def restart(): api = Api() api.restart() api.wait() # Wait for the service to be fully up unitdata.kv().set("jenkins.last_restart", time.time())
class ApiTest(JenkinsTest): def setUp(self): super(ApiTest, self).setUp() self.useFixture(JenkinsConfiguredAdmin(self.fakes)) self.fakes.jenkins.scripts[GET_TOKEN_SCRIPT.format("admin")] = "abc\n" self.api = Api() def test_wait_transient_failure(self): """ Wait for Jenkins to be fully up, even in spite of transient failures. """ get_whoami = self.fakes.jenkins.get_whoami tries = [] def transient_failure(): try: if not tries: raise JenkinsException("error") get_whoami() finally: tries.append(True) self.fakes.jenkins.get_whoami = transient_failure self.assertIsNone(self.api.wait()) def test_update_password(self): """ The update_password() method runs a groovy script to update the password for the given user. """ username = "******" password = "******" script = UPDATE_PASSWORD_SCRIPT.format(username=username, password=password) self.fakes.jenkins.scripts[script] = "" self.assertIsNone(self.api.update_password(username, password)) def test_version(self): """The version() method returns the version of the Jenkins server.""" self.assertEqual("2.0.0", self.api.version()) def test_add(self): """ A slave node can be added by specifying executors and labels. """ self.api.add_node("slave-0", 1, labels=["python"]) [node] = self.fakes.jenkins.nodes self.assertEqual("slave-0", node.host) self.assertEqual(1, node.executors) self.assertEqual("slave-0", node.description) self.assertEqual(["python"], node.labels) def test_add_exists(self): """ If a node already exists, nothing is done. """ self.fakes.jenkins.create_node("slave-0", 1, "slave-0") self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual(1, len(self.fakes.jenkins.nodes)) def test_add_transient_failure(self): """ Transient failures get retried. """ create_node = self.fakes.jenkins.create_node tries = [] def transient_failure(*args, **kwargs): try: if not tries: raise JenkinsException("error") create_node(*args, **kwargs) finally: tries.append(True) self.fakes.jenkins.create_node = transient_failure self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual(1, len(self.fakes.jenkins.nodes)) def test_add_retry_give_up(self): """ If errors persist, we give up. """ def failure(*args, **kwargs): raise JenkinsException("error") self.fakes.jenkins.create_node = failure self.assertRaises(JenkinsException, self.api.add_node, "slave-0", 1) def test_add_spurious(self): """ If adding a node apparently succeeds, but actually didn't then we log an error. """ self.fakes.jenkins.create_node = lambda *args, **kwargs: None self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual("ERROR: Failed to create node 'slave-0'", self.fakes.juju.log[-1]) def test_deleted(self): """ A slave node can be deleted by specifyng its host name. """ self.api.add_node("slave-0", 1, labels=["python"]) self.api.delete_node("slave-0") self.assertEqual([], self.fakes.jenkins.nodes) def test_deleted_no_present(self): """ If a slave node doesn't exists, deleting it is a no-op. """ self.api.delete_node("slave-0") self.assertEqual([], self.fakes.jenkins.nodes) def test_reload(self): """ The reload method POSTs a request to the '/reload' URL, expecting a 503 on the homepage (which happens after redirection). """ error = HTTPError(URL, 503, "Service Unavailable", {}, None) error.url = URL self.fakes.jenkins.responses[urljoin(URL, "/reload")] = error self.api.reload() def test_reload_unexpected_error(self): """ If the error code is not 403, the error is propagated. """ error = HTTPError(URL, 403, "Forbidden", {}, None) self.fakes.jenkins.responses[urljoin(URL, "/reload")] = error self.assertRaises(HTTPError, self.api.reload) def test_reload_unexpected_url(self): """ If the error URL is not the root, the error is propagated. """ error = HTTPError(URL, 503, "Service Unavailable", {}, None) error.url = "/foo" self.fakes.jenkins.responses[urljoin(URL, "/reload")] = error self.assertRaises(HTTPError, self.api.reload) def test_reload_unexpected_success(self): """ If the request unexpectedly succeeds, an error is raised. """ self.fakes.jenkins.responses[urljoin(URL, "/reload")] = "home" self.assertRaises(RuntimeError, self.api.reload)
class ApiTest(JenkinsTest): def setUp(self): super(ApiTest, self).setUp() self.useFixture(JenkinsConfiguredAdmin(self.fakes)) self.fakes.jenkins.scripts[GET_TOKEN_SCRIPT.format("admin")] = "abc\n" self.api = Api() def test_wait_transient_failure(self): """ Wait for Jenkins to be fully up, even in spite of transient failures. """ get_whoami = self.fakes.jenkins.get_whoami tries = [] def transient_failure(): try: if not tries: raise JenkinsException("error") get_whoami() finally: tries.append(True) self.fakes.jenkins.get_whoami = transient_failure self.assertIsNone(self.api.wait()) def test_update_password(self): """ The update_password() method runs a groovy script to update the password for the given user. """ username = "******" password = "******" script = UPDATE_PASSWORD_SCRIPT.format( username=username, password=password) self.fakes.jenkins.scripts[script] = "" self.assertIsNone(self.api.update_password(username, password)) def test_version(self): """The version() method returns the version of the Jenkins server.""" self.assertEqual("2.0.0", self.api.version()) def test_add(self): """ A slave node can be added by specifying executors and labels. """ self.api.add_node("slave-0", 1, labels=["python"]) [node] = self.fakes.jenkins.nodes self.assertEqual("slave-0", node.host) self.assertEqual(1, node.executors) self.assertEqual("slave-0", node.description) self.assertEqual(["python"], node.labels) def test_add_exists(self): """ If a node already exists, nothing is done. """ self.fakes.jenkins.create_node("slave-0", 1, "slave-0") self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual(1, len(self.fakes.jenkins.nodes)) def test_add_transient_failure(self): """ Transient failures get retried. """ create_node = self.fakes.jenkins.create_node tries = [] def transient_failure(*args, **kwargs): try: if not tries: raise JenkinsException("error") create_node(*args, **kwargs) finally: tries.append(True) self.fakes.jenkins.create_node = transient_failure self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual(1, len(self.fakes.jenkins.nodes)) def test_add_retry_give_up(self): """ If errors persist, we give up. """ def failure(*args, **kwargs): raise JenkinsException("error") self.fakes.jenkins.create_node = failure self.assertRaises( JenkinsException, self.api.add_node, "slave-0", 1) def test_add_spurious(self): """ If adding a node apparently succeeds, but actually didn't then we log an error. """ self.fakes.jenkins.create_node = lambda *args, **kwargs: None self.api.add_node("slave-0", 1, labels=["python"]) self.assertEqual( "ERROR: Failed to create node 'slave-0'", self.fakes.juju.log[-1]) def test_deleted(self): """ A slave node can be deleted by specifyng its host name. """ self.api.add_node("slave-0", 1, labels=["python"]) self.api.delete_node("slave-0") self.assertEqual([], self.fakes.jenkins.nodes) def test_deleted_no_present(self): """ If a slave node doesn't exists, deleting it is a no-op. """ self.api.delete_node("slave-0") self.assertEqual([], self.fakes.jenkins.nodes) def test_reload(self): """ The reload method POSTs a request to the '/reload' URL, expecting a 503 on the homepage (which happens after redirection). """ error = HTTPError(URL, 503, "Service Unavailable", {}, None) error.url = URL self.fakes.jenkins.responses[urljoin(URL, "/reload")] = error self.api.reload() def test_reload_unexpected_error(self): """ If the error code is not 403, the error is propagated. """ error = HTTPError(URL, 403, "Forbidden", {}, None) self.fakes.jenkins.responses[urljoin(URL, "/reload")] = error self.assertRaises(HTTPError, self.api.reload) def test_reload_unexpected_url(self): """ If the error URL is not the root, the error is propagated. """ error = HTTPError(URL, 503, "Service Unavailable", {}, None) error.url = "/foo" self.fakes.jenkins.responses[urljoin(URL, "/reload")] = error self.assertRaises(HTTPError, self.api.reload) def test_reload_unexpected_success(self): """ If the request unexpectedly succeeds, an error is raised. """ self.fakes.jenkins.responses[urljoin(URL, "/reload")] = "home" self.assertRaises(RuntimeError, self.api.reload)