diff options
author | David Pursehouse <dpursehouse@collab.net> | 2020-01-31 11:01:01 +0900 |
---|---|---|
committer | David Pursehouse <dpursehouse@collab.net> | 2020-01-31 11:01:01 +0900 |
commit | f3ad6218fb36a89c1ef33858fc7b5b75c90d535d (patch) | |
tree | 1111879134914e1b1908b97d05c5ec5570d47335 | |
parent | da1fff77c40d42c8613058eadba4743f9ea54eaf (diff) | |
parent | ebb7a1a844a63cd97ab2fbcb45858a68d570c809 (diff) |
Merge branch 'stable-2.16' into stable-3.0
* stable-2.16:
Expose jetty threadpool metrics via dropwizard
Fix editing name & email for service user
Fix accountBelongsToRealm implementation
Fix support for python3 in download_bower.py
Change-Id: I4e2d984af74eac279ad22d11f3e606c3ef361c94
13 files changed, 329 insertions, 15 deletions
diff --git a/java/com/google/gerrit/metrics/dropwizard/BUILD b/java/com/google/gerrit/metrics/dropwizard/BUILD index 4b3859f7a8..307980939f 100644 --- a/java/com/google/gerrit/metrics/dropwizard/BUILD +++ b/java/com/google/gerrit/metrics/dropwizard/BUILD @@ -8,6 +8,7 @@ java_library( "//java/com/google/gerrit/common:annotations", "//java/com/google/gerrit/extensions:api", "//java/com/google/gerrit/metrics", + "//java/com/google/gerrit/pgm/http/jetty", "//java/com/google/gerrit/server", "//lib:args4j", "//lib:guava", diff --git a/java/com/google/gerrit/pgm/http/jetty/BUILD b/java/com/google/gerrit/pgm/http/jetty/BUILD index ea3afe118c..43a8ed92b8 100644 --- a/java/com/google/gerrit/pgm/http/jetty/BUILD +++ b/java/com/google/gerrit/pgm/http/jetty/BUILD @@ -10,6 +10,7 @@ java_library( "//java/com/google/gerrit/httpd", "//java/com/google/gerrit/launcher", "//java/com/google/gerrit/lifecycle", + "//java/com/google/gerrit/metrics", "//java/com/google/gerrit/server", "//java/com/google/gerrit/server/util/time", "//java/com/google/gerrit/sshd", diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java b/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java new file mode 100644 index 0000000000..92edf403d1 --- /dev/null +++ b/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java @@ -0,0 +1,91 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.gerrit.pgm.http.jetty; + +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.metrics.CallbackMetric; +import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class JettyMetrics { + + @Inject + JettyMetrics(JettyServer jetty, MetricMaker metrics) { + CallbackMetric0<Integer> minPoolSize = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/min_pool_size", + Integer.class, + new Description("Minimum thread pool size").setGauge()); + CallbackMetric0<Integer> maxPoolSize = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/max_pool_size", + Integer.class, + new Description("Maximum thread pool size").setGauge()); + CallbackMetric0<Integer> poolSize = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/pool_size", + Integer.class, + new Description("Current thread pool size").setGauge()); + CallbackMetric0<Integer> idleThreads = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/idle_threads", + Integer.class, + new Description("Idle httpd threads").setGauge().setUnit("threads")); + CallbackMetric0<Integer> busyThreads = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/active_threads", + Integer.class, + new Description("Active httpd threads").setGauge().setUnit("threads")); + CallbackMetric0<Integer> reservedThreads = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/reserved_threads", + Integer.class, + new Description("Reserved httpd threads").setGauge().setUnit("threads")); + CallbackMetric0<Integer> queueSize = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/queue_size", + Integer.class, + new Description("Thread pool queue size").setGauge().setUnit("requests")); + CallbackMetric0<Boolean> lowOnThreads = + metrics.newCallbackMetric( + "httpd/jetty/threadpool/is_low_on_threads", + Boolean.class, + new Description("Whether thread pool is low on threads").setGauge()); + JettyServer.Metrics jettyMetrics = jetty.getMetrics(); + metrics.newTrigger( + ImmutableSet.<CallbackMetric<?>>of( + idleThreads, + busyThreads, + reservedThreads, + minPoolSize, + maxPoolSize, + poolSize, + queueSize, + lowOnThreads), + () -> { + minPoolSize.set(jettyMetrics.getMinThreads()); + maxPoolSize.set(jettyMetrics.getMaxThreads()); + poolSize.set(jettyMetrics.getThreads()); + idleThreads.set(jettyMetrics.getIdleThreads()); + busyThreads.set(jettyMetrics.getBusyThreads()); + reservedThreads.set(jettyMetrics.getReservedThreads()); + queueSize.set(jettyMetrics.getQueueSize()); + lowOnThreads.set(jettyMetrics.isLowOnThreads()); + }); + } +} diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyModule.java b/java/com/google/gerrit/pgm/http/jetty/JettyModule.java index c818276a5d..32a8b6d0f2 100644 --- a/java/com/google/gerrit/pgm/http/jetty/JettyModule.java +++ b/java/com/google/gerrit/pgm/http/jetty/JettyModule.java @@ -31,5 +31,6 @@ public class JettyModule extends LifecycleModule { bind(JettyServer.class); listener().to(JettyServer.Lifecycle.class); install(new FactoryModuleBuilder().build(HttpLogFactory.class)); + bind(JettyMetrics.class); } } diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java index 7745ad4523..8ce83de800 100644 --- a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java +++ b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java @@ -66,7 +66,6 @@ import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; import org.eclipse.jgit.lib.Config; @Singleton @@ -115,9 +114,49 @@ public class JettyServer { } } + static class Metrics { + private final QueuedThreadPool threadPool; + + Metrics(QueuedThreadPool threadPool) { + this.threadPool = threadPool; + } + + public int getIdleThreads() { + return threadPool.getIdleThreads(); + } + + public int getBusyThreads() { + return threadPool.getBusyThreads(); + } + + public int getReservedThreads() { + return threadPool.getReservedThreads(); + } + + public int getMinThreads() { + return threadPool.getMinThreads(); + } + + public int getMaxThreads() { + return threadPool.getMaxThreads(); + } + + public int getThreads() { + return threadPool.getThreads(); + } + + public int getQueueSize() { + return threadPool.getQueueSize(); + } + + public boolean isLowOnThreads() { + return threadPool.isLowOnThreads(); + } + } + private final SitePaths site; private final Server httpd; - + private final Metrics metrics; private boolean reverseProxy; @Inject @@ -129,8 +168,10 @@ public class JettyServer { HttpLogFactory httpLogFactory) { this.site = site; - httpd = new Server(threadPool(cfg, threadSettingsConfig)); + QueuedThreadPool pool = threadPool(cfg, threadSettingsConfig); + httpd = new Server(pool); httpd.setConnectors(listen(httpd, cfg)); + metrics = new Metrics(pool); Handler app = makeContext(env, cfg); if (cfg.getBoolean("httpd", "requestLog", !reverseProxy)) { @@ -159,6 +200,10 @@ public class JettyServer { httpd.setStopAtShutdown(false); } + Metrics getMetrics() { + return metrics; + } + private Connector[] listen(Server server, Config cfg) { // OpenID and certain web-based single-sign-on products can cause // some very long headers, especially in the Referer header. We @@ -334,7 +379,7 @@ public class JettyServer { return site.resolve(path); } - private ThreadPool threadPool(Config cfg, ThreadSettingsConfig threadSettingsConfig) { + private QueuedThreadPool threadPool(Config cfg, ThreadSettingsConfig threadSettingsConfig) { int maxThreads = threadSettingsConfig.getHttpdMaxThreads(); int minThreads = cfg.getInt("httpd", null, "minthreads", 5); int maxQueued = cfg.getInt("httpd", null, "maxqueued", 200); diff --git a/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/java/com/google/gerrit/server/auth/ldap/LdapRealm.java index ed446f218a..b047488097 100644 --- a/java/com/google/gerrit/server/auth/ldap/LdapRealm.java +++ b/java/com/google/gerrit/server/auth/ldap/LdapRealm.java @@ -336,7 +336,7 @@ class LdapRealm extends AbstractRealm { @Override public boolean accountBelongsToRealm(Collection<ExternalId> externalIds) { for (ExternalId id : externalIds) { - if (id.toString().contains(SCHEME_GERRIT)) { + if (id.isScheme(SCHEME_GERRIT)) { return true; } } diff --git a/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java b/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java index 54d50f08f4..2cef62d723 100644 --- a/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java +++ b/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java @@ -122,7 +122,7 @@ public class OAuthRealm extends AbstractRealm { @Override public boolean accountBelongsToRealm(Collection<ExternalId> externalIds) { for (ExternalId id : externalIds) { - if (id.toString().contains(SCHEME_EXTERNAL)) { + if (id.isScheme(SCHEME_EXTERNAL)) { return true; } } diff --git a/java/com/google/gerrit/server/restapi/account/DeleteEmail.java b/java/com/google/gerrit/server/restapi/account/DeleteEmail.java index 7a0300513e..c2076f6855 100644 --- a/java/com/google/gerrit/server/restapi/account/DeleteEmail.java +++ b/java/com/google/gerrit/server/restapi/account/DeleteEmail.java @@ -24,6 +24,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountException; @@ -79,12 +80,14 @@ public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> public Response<?> apply(IdentifiedUser user, String email) throws ResourceNotFoundException, ResourceConflictException, MethodNotAllowedException, IOException, ConfigInvalidException { - if (!realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) { + Account.Id accountId = user.getAccountId(); + if (realm.accountBelongsToRealm(externalIds.byAccount(accountId)) + && !realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) { throw new MethodNotAllowedException("realm does not allow deleting emails"); } Set<ExternalId> extIds = - externalIds.byAccount(user.getAccountId()).stream() + externalIds.byAccount(accountId).stream() .filter(e -> email.equals(e.email())) .collect(toSet()); if (extIds.isEmpty()) { diff --git a/java/com/google/gerrit/server/restapi/account/PutName.java b/java/com/google/gerrit/server/restapi/account/PutName.java index 30dfc66ee8..9f9dd073ec 100644 --- a/java/com/google/gerrit/server/restapi/account/PutName.java +++ b/java/com/google/gerrit/server/restapi/account/PutName.java @@ -22,6 +22,7 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.ServerInitiated; @@ -29,6 +30,7 @@ import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.AccountsUpdate; import com.google.gerrit.server.account.Realm; +import com.google.gerrit.server.account.externalids.ExternalIds; import com.google.gerrit.server.permissions.GlobalPermission; import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackendException; @@ -43,6 +45,7 @@ public class PutName implements RestModifyView<AccountResource, NameInput> { private final Provider<CurrentUser> self; private final Realm realm; private final PermissionBackend permissionBackend; + private final ExternalIds externalIds; private final Provider<AccountsUpdate> accountsUpdateProvider; @Inject @@ -50,10 +53,12 @@ public class PutName implements RestModifyView<AccountResource, NameInput> { Provider<CurrentUser> self, Realm realm, PermissionBackend permissionBackend, + ExternalIds externalIds, @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider) { this.self = self; this.realm = realm; this.permissionBackend = permissionBackend; + this.externalIds = externalIds; this.accountsUpdateProvider = accountsUpdateProvider; } @@ -74,7 +79,9 @@ public class PutName implements RestModifyView<AccountResource, NameInput> { input = new NameInput(); } - if (!realm.allowsEdit(AccountFieldName.FULL_NAME)) { + Account.Id accountId = user.getAccountId(); + if (realm.accountBelongsToRealm(externalIds.byAccount(accountId)) + && !realm.allowsEdit(AccountFieldName.FULL_NAME)) { throw new MethodNotAllowedException("realm does not allow editing name"); } @@ -82,7 +89,7 @@ public class PutName implements RestModifyView<AccountResource, NameInput> { AccountState accountState = accountsUpdateProvider .get() - .update("Set Full Name via API", user.getAccountId(), u -> u.setFullName(newName)) + .update("Set Full Name via API", accountId, u -> u.setFullName(newName)) .orElseThrow(() -> new ResourceNotFoundException("account not found")); return Strings.isNullOrEmpty(accountState.getAccount().getFullName()) ? Response.none() diff --git a/java/com/google/gerrit/server/restapi/account/PutUsername.java b/java/com/google/gerrit/server/restapi/account/PutUsername.java index 76aece7488..8d949b8dad 100644 --- a/java/com/google/gerrit/server/restapi/account/PutUsername.java +++ b/java/com/google/gerrit/server/restapi/account/PutUsername.java @@ -78,10 +78,6 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER); } - if (!realm.allowsEdit(AccountFieldName.USER_NAME)) { - throw new MethodNotAllowedException("realm does not allow editing username"); - } - if (input == null) { input = new UsernameInput(); } @@ -91,6 +87,11 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu throw new MethodNotAllowedException("Username cannot be changed."); } + if (realm.accountBelongsToRealm(externalIds.byAccount(accountId)) + && !realm.allowsEdit(AccountFieldName.USER_NAME)) { + throw new MethodNotAllowedException("realm does not allow editing username"); + } + if (Strings.isNullOrEmpty(input.username)) { return input.username; } diff --git a/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java b/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java new file mode 100644 index 0000000000..13de3e7ad0 --- /dev/null +++ b/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java @@ -0,0 +1,94 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.auth.ldap; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; +import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_CACHE; +import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_EXIST_CACHE; +import static com.google.gerrit.server.auth.ldap.LdapModule.PARENT_GROUPS_CACHE; +import static com.google.gerrit.server.auth.ldap.LdapModule.USERNAME_CACHE; + +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.server.account.externalids.ExternalId; +import com.google.gerrit.server.cache.CacheModule; +import com.google.gerrit.testing.InMemoryModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.TypeLiteral; +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; + +public final class LdapRealmTest { + @Inject private LdapRealm ldapRealm = null; + + @Before + public void setUpInjector() throws Exception { + Injector injector = + Guice.createInjector( + new InMemoryModule(), + new CacheModule() { + @Override + protected void configure() { + cache(GROUP_CACHE, String.class, new TypeLiteral<Set<AccountGroup.UUID>>() {}) + .loader(LdapRealm.MemberLoader.class); + cache(USERNAME_CACHE, String.class, new TypeLiteral<Optional<Account.Id>>() {}) + .loader(LdapRealm.UserLoader.class); + cache(GROUP_EXIST_CACHE, String.class, new TypeLiteral<Boolean>() {}) + .loader(LdapRealm.ExistenceLoader.class); + cache( + PARENT_GROUPS_CACHE, String.class, new TypeLiteral<ImmutableSet<String>>() {}); + } + }); + injector.injectMembers(this); + } + + private ExternalId id(String scheme, String id) { + return ExternalId.create(scheme, id, new Account.Id(1000)); + } + + private boolean accountBelongsToRealm(ExternalId... ids) { + return ldapRealm.accountBelongsToRealm(Arrays.asList(ids)); + } + + private boolean accountBelongsToRealm(String scheme, String id) { + return accountBelongsToRealm(id(scheme, id)); + } + + @Test + public void accountBelongsToRealm() throws Exception { + assertThat(accountBelongsToRealm(SCHEME_GERRIT, "test")).isTrue(); + assertThat(accountBelongsToRealm(id(SCHEME_USERNAME, "test"), id(SCHEME_GERRIT, "test"))) + .isTrue(); + assertThat(accountBelongsToRealm(id(SCHEME_GERRIT, "test"), id(SCHEME_USERNAME, "test"))) + .isTrue(); + + assertThat(accountBelongsToRealm(SCHEME_USERNAME, "test")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_MAILTO, "foo@bar.com")).isFalse(); + + assertThat(accountBelongsToRealm(SCHEME_USERNAME, "gerrit")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_USERNAME, "xxgerritxx")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_MAILTO, "gerrit.foo@bar.com")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_MAILTO, "bar.gerrit@bar.com")).isFalse(); + } +} diff --git a/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java b/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java new file mode 100644 index 0000000000..7a0661a59b --- /dev/null +++ b/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java @@ -0,0 +1,69 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.auth.oauth; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_EXTERNAL; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; + +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.server.account.externalids.ExternalId; +import com.google.gerrit.testing.InMemoryModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; + +public final class OAuthRealmTest { + @Inject private OAuthRealm oauthRealm = null; + + @Before + public void setUpInjector() throws Exception { + Injector injector = Guice.createInjector(new InMemoryModule()); + injector.injectMembers(this); + } + + private ExternalId id(String scheme, String id) { + return ExternalId.create(scheme, id, new Account.Id(1000)); + } + + private boolean accountBelongsToRealm(ExternalId... ids) { + return oauthRealm.accountBelongsToRealm(Arrays.asList(ids)); + } + + private boolean accountBelongsToRealm(String scheme, String id) { + return accountBelongsToRealm(id(scheme, id)); + } + + @Test + public void accountBelongsToRealm() throws Exception { + assertThat(accountBelongsToRealm(SCHEME_EXTERNAL, "test")).isTrue(); + assertThat(accountBelongsToRealm(id(SCHEME_USERNAME, "test"), id(SCHEME_EXTERNAL, "test"))) + .isTrue(); + assertThat(accountBelongsToRealm(id(SCHEME_EXTERNAL, "test"), id(SCHEME_USERNAME, "test"))) + .isTrue(); + + assertThat(accountBelongsToRealm(SCHEME_USERNAME, "test")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_MAILTO, "foo@bar.com")).isFalse(); + + assertThat(accountBelongsToRealm(SCHEME_USERNAME, "external")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_USERNAME, "xxexternalxx")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_MAILTO, "external.foo@bar.com")).isFalse(); + assertThat(accountBelongsToRealm(SCHEME_MAILTO, "bar.external@bar.com")).isFalse(); + } +} diff --git a/tools/js/download_bower.py b/tools/js/download_bower.py index c9a5df69c7..c1d7d00ce2 100755 --- a/tools/js/download_bower.py +++ b/tools/js/download_bower.py @@ -46,7 +46,8 @@ def bower_info(bower, name, package, version): raise out, err = p.communicate() if p.returncode: - sys.stderr.write(err) + # For python3 support we wrap str around err. + sys.stderr.write(str(err)) raise OSError('Command failed: %s' % ' '.join(cmd)) try: |