aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb39
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb14
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java6
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/system/Migration.java43
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/system/SystemClient.java36
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultMigration.java38
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultSystemClient.java76
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/package-info.java22
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/system/package-info.java22
-rw-r--r--sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java2
-rw-r--r--sonar-ws-client/src/test/java/org/sonar/wsclient/system/internal/DefaultSystemClientTest.java98
11 files changed, 379 insertions, 17 deletions
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb
index c2f2d5a9bb4..24cdfb7edfa 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb
@@ -25,7 +25,6 @@ class Api::ServerController < Api::ApiController
before_filter :set_cache_buster, :only => 'index'
# execute database setup
- verify :method => :post, :only => [:setup, :index_projects]
skip_before_filter :check_database_version, :setup
def key
@@ -65,24 +64,43 @@ class Api::ServerController < Api::ApiController
end
def setup
+ verify_post_request
+ manager=DatabaseMigrationManager.instance
begin
# Ask the DB migration manager to start the migration
# => No need to check for authorizations (actually everybody can run the upgrade)
- # nor concurrent calls (this is handled directly by DatabaseMigrationManager)
- DatabaseMigrationManager.instance.start_migration
-
- current_status = DatabaseMigrationManager.instance.is_sonar_access_allowed? ? "ok" : "ko"
-
- hash={:status => current_status,
- :migration_status => DatabaseMigrationManager.instance.status,
- :message => DatabaseMigrationManager.instance.message}
+ # nor concurrent calls (this is handled directly by DatabaseMigrationManager)
+ manager.start_migration
+
+ operational=manager.is_sonar_access_allowed?
+ current_status = operational ? "ok" : "ko"
+ hash={
+ # deprecated fields
+ :status => current_status,
+ :migration_status => manager.status,
+
+ # correct fields
+ :operational => operational,
+ :state => manager.status
+ }
+ hash[:message]=manager.message if manager.message
+ hash[:startedAt]=manager.migration_start_time if manager.migration_start_time
+
respond_to do |format|
format.json{ render :json => jsonp(hash) }
format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'setup') }
format.text { render :text => hash[:status] }
end
rescue => e
- hash={:status => 'ko', :msg => e.message}
+ hash={
+ # deprecated fields
+ :status => 'ko',
+ :msg => e.message,
+
+ # correct fields
+ :message => e.message,
+ :state => manager.status
+ }
respond_to do |format|
format.json{ render :json => jsonp(hash) }
format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'setup') }
@@ -92,6 +110,7 @@ class Api::ServerController < Api::ApiController
end
def index_projects
+ verify_post_request
access_denied unless has_role?(:admin)
logger.info 'Indexing projects'
Java::OrgSonarServerUi::JRubyFacade.getInstance().indexProjects()
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb
index 4b0e7b4e4ee..82191c86f3a 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb
@@ -93,21 +93,21 @@ class DatabaseMigrationManager
def start_migration
# Use an exclusive block of code to ensure that only 1 thread will be able to proceed with the migration
- can_start_migration = false
+ requires_migration = false
Thread.exclusive do
- if requires_migration?
- @status = MIGRATION_RUNNING
- @message = "Database migration is running"
- can_start_migration = true
- end
+ requires_migration = requires_migration?
end
- if can_start_migration
+ if requires_migration
Thread.new do
begin
+ @status = MIGRATION_RUNNING
+ @message = "Database migration is running"
Thread.current[:name] = "Database Upgrade"
@start_time = Time.now
+
DatabaseVersion.upgrade_and_start
+
@status = MIGRATION_SUCCEEDED
@message = "Migration succeeded."
rescue Exception => e
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java
index 04d4c1b7964..aefd3e382b1 100644
--- a/sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java
@@ -28,6 +28,8 @@ import org.sonar.wsclient.permissions.PermissionClient;
import org.sonar.wsclient.permissions.internal.DefaultPermissionClient;
import org.sonar.wsclient.project.ProjectClient;
import org.sonar.wsclient.project.internal.DefaultProjectClient;
+import org.sonar.wsclient.system.SystemClient;
+import org.sonar.wsclient.system.internal.DefaultSystemClient;
import org.sonar.wsclient.user.UserClient;
import org.sonar.wsclient.user.internal.DefaultUserClient;
@@ -101,6 +103,10 @@ public class SonarClient {
return new DefaultProjectClient(requestFactory);
}
+ public SystemClient systemClient() {
+ return new DefaultSystemClient(requestFactory);
+ }
+
/**
* Create a builder of {@link SonarClient}s.
*/
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/system/Migration.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/Migration.java
new file mode 100644
index 00000000000..8891d96f939
--- /dev/null
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/Migration.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.wsclient.system;
+
+import javax.annotation.Nullable;
+import java.util.Date;
+
+/**
+ * @since 4.0
+ */
+public interface Migration {
+ enum Status {
+ MIGRATION_NEEDED, MIGRATION_RUNNING, MIGRATION_FAILED,
+ MIGRATION_SUCCEEDED, NO_MIGRATION
+ }
+
+ boolean operationalWebapp();
+
+ Status status();
+
+ @Nullable
+ String message();
+
+ @Nullable
+ Date startedAt();
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/system/SystemClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/SystemClient.java
new file mode 100644
index 00000000000..d14377d3f32
--- /dev/null
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/SystemClient.java
@@ -0,0 +1,36 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.wsclient.system;
+
+/**
+ * @since 4.0
+ */
+public interface SystemClient {
+ /**
+ * Asynchronously start a database migration. No effect if executed
+ * several times.
+ */
+ Migration migrate();
+
+ /**
+ * Synchronously start a database migration.
+ */
+ Migration migrate(long timeoutInSeconds, long rateInSeconds);
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultMigration.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultMigration.java
new file mode 100644
index 00000000000..2aa90de9b94
--- /dev/null
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultMigration.java
@@ -0,0 +1,38 @@
+package org.sonar.wsclient.system.internal;
+
+import org.sonar.wsclient.system.Migration;
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+
+import javax.annotation.Nullable;
+import java.util.Date;
+import java.util.Map;
+
+public class DefaultMigration implements Migration {
+
+ private final Map json;
+
+ public DefaultMigration(Map json) {
+ this.json = json;
+ }
+
+ @Override
+ public boolean operationalWebapp() {
+ return JsonUtils.getBoolean(json, "operational");
+ }
+
+ @Override
+ public Status status() {
+ return Status.valueOf(JsonUtils.getString(json, "state"));
+ }
+
+ @Override
+ public String message() {
+ return JsonUtils.getString(json, "message");
+ }
+
+ @Override
+ @Nullable
+ public Date startedAt() {
+ return JsonUtils.getDateTime(json, "startedAt");
+ }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultSystemClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultSystemClient.java
new file mode 100644
index 00000000000..86781a7cdfd
--- /dev/null
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/DefaultSystemClient.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.wsclient.system.internal;
+
+import org.json.simple.JSONValue;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.system.Migration;
+import org.sonar.wsclient.system.SystemClient;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultSystemClient implements SystemClient {
+
+ private final HttpRequestFactory requestFactory;
+
+ public DefaultSystemClient(HttpRequestFactory requestFactory) {
+ this.requestFactory = requestFactory;
+ }
+
+ @Override
+ public Migration migrate() {
+ String json = requestFactory.post("/api/server/setup", new HashMap<String, Object>());
+ return jsonToMigration(json);
+ }
+
+ @Override
+ public Migration migrate(long timeoutInMs, long rateInMs) {
+ if (rateInMs >= timeoutInMs) {
+ throw new IllegalArgumentException("Timeout must be greater than rate");
+ }
+ Migration migration = null;
+ boolean running = true;
+ long endAt = System.currentTimeMillis() + timeoutInMs;
+ while (running && System.currentTimeMillis() < endAt) {
+ migration = migrate();
+ if (migration.status() == Migration.Status.MIGRATION_NEEDED ||
+ migration.status() == Migration.Status.MIGRATION_RUNNING) {
+ sleepQuietly(rateInMs);
+ } else {
+ running = false;
+ }
+ }
+ return migration;
+ }
+
+ private void sleepQuietly(long rateInMs) {
+ try {
+ Thread.sleep(rateInMs);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Fail to sleep!", e);
+ }
+ }
+
+ private Migration jsonToMigration(String json) {
+ Map jsonRoot = (Map) JSONValue.parse(json);
+ return new DefaultMigration(jsonRoot);
+ }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/package-info.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/package-info.java
new file mode 100644
index 00000000000..3cb9011d7f8
--- /dev/null
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/internal/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.wsclient.system.internal;
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/system/package-info.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/package-info.java
new file mode 100644
index 00000000000..433b6d9424b
--- /dev/null
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/system/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.wsclient.system;
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java
index cb1516a9db9..92741d41d26 100644
--- a/sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java
+++ b/sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java
@@ -24,6 +24,7 @@ import org.sonar.wsclient.issue.internal.DefaultActionPlanClient;
import org.sonar.wsclient.issue.internal.DefaultIssueClient;
import org.sonar.wsclient.permissions.internal.DefaultPermissionClient;
import org.sonar.wsclient.project.internal.DefaultProjectClient;
+import org.sonar.wsclient.system.internal.DefaultSystemClient;
import org.sonar.wsclient.user.internal.DefaultUserClient;
import static org.fest.assertions.Assertions.assertThat;
@@ -38,6 +39,7 @@ public class SonarClientTest {
assertThat(client.userClient()).isNotNull().isInstanceOf(DefaultUserClient.class);
assertThat(client.permissionClient()).isNotNull().isInstanceOf(DefaultPermissionClient.class);
assertThat(client.projectClient()).isNotNull().isInstanceOf(DefaultProjectClient.class);
+ assertThat(client.systemClient()).isNotNull().isInstanceOf(DefaultSystemClient.class);
}
@Test
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/system/internal/DefaultSystemClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/system/internal/DefaultSystemClientTest.java
new file mode 100644
index 00000000000..fffc6c83a88
--- /dev/null
+++ b/sonar-ws-client/src/test/java/org/sonar/wsclient/system/internal/DefaultSystemClientTest.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.wsclient.system.internal;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.wsclient.MockHttpServerInterceptor;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.system.Migration;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DefaultSystemClientTest {
+
+ static final String RUNNING_JSON = "{\"status\": \"KO\", \"state\": \"MIGRATION_RUNNING\", " +
+ "\"operational\": false, \"startedAt\": \"2013-12-20T12:34:56+0100\"}";
+
+ static final String DONE_JSON = "{\"status\": \"OK\", \"state\": \"MIGRATION_SUCCEEDED\", " +
+ "\"operational\": true, \"message\": \"done\"}";
+
+ @Rule
+ public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
+
+ @Test
+ public void start_migration_asynchronously() {
+ HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+ httpServer.stubResponseBody(RUNNING_JSON);
+
+ DefaultSystemClient client = new DefaultSystemClient(requestFactory);
+ Migration migration = client.migrate();
+
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/server/setup");
+ assertThat(migration.status()).isEqualTo(Migration.Status.MIGRATION_RUNNING);
+ assertThat(migration.operationalWebapp()).isFalse();
+ assertThat(migration.startedAt().getYear()).isEqualTo(113);//2013 = nb of years since 1900
+ }
+
+ @Test
+ public void fail_if_rate_is_greater_than_timeout() throws Exception {
+ try {
+ DefaultSystemClient client = new DefaultSystemClient(mock(HttpRequestFactory.class));
+ client.migrate(5L, 50L);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).isEqualTo("Timeout must be greater than rate");
+ }
+ }
+
+ @Test
+ public void stop_synchronous_migration_on_timeout() {
+ HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+ httpServer.stubResponseBody(RUNNING_JSON);
+
+ DefaultSystemClient client = new DefaultSystemClient(requestFactory);
+ Migration migration = client.migrate(50L, 5L);
+
+ assertThat(migration.status()).isEqualTo(Migration.Status.MIGRATION_RUNNING);
+ assertThat(migration.operationalWebapp()).isFalse();
+ }
+
+ @Test
+ public void return_result_before_timeout_of_synchronous_migration() {
+ HttpRequestFactory requestFactory = mock(HttpRequestFactory.class);
+ when(requestFactory.post(eq("/api/server/setup"), anyMap())).thenReturn(
+ RUNNING_JSON, DONE_JSON
+ );
+
+ DefaultSystemClient client = new DefaultSystemClient(requestFactory);
+ Migration migration = client.migrate(50L, 5L);
+
+ assertThat(migration.status()).isEqualTo(Migration.Status.MIGRATION_SUCCEEDED);
+ assertThat(migration.operationalWebapp()).isTrue();
+ assertThat(migration.message()).isEqualTo("done");
+ assertThat(migration.startedAt()).isNull();
+ }
+}