Implemented Initializing status.

This commit is contained in:
Sipke Schoorstra
2015-08-23 18:27:36 +01:00
parent b560a4c3d0
commit 81adb78818
17 changed files with 161 additions and 116 deletions

View File

@@ -91,6 +91,7 @@
<Compile Include="Providers\Builders\ContentStep.cs" />
<Compile Include="Providers\Builders\RecipeMetadataStep.cs" />
<Compile Include="Providers\Builders\SettingsStep.cs" />
<Compile Include="Providers\Executors\ActivateShellStep.cs" />
<Compile Include="Providers\Executors\ActivateSweepGeneratorStep.cs" />
<Compile Include="Providers\Executors\CommandStep.cs" />
<Compile Include="Providers\Executors\RemoveContentStep.cs" />

View File

@@ -0,0 +1,23 @@
using Orchard.Environment.Configuration;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Providers.Executors {
public class ActivateShellStep : RecipeExecutionStep {
private readonly ShellSettings _shellSettings;
private readonly IShellSettingsManager _shellSettingsManager;
public ActivateShellStep(ShellSettings shellSettings, IShellSettingsManager shellSettingsManager, RecipeExecutionLogger logger)
: base(logger) {
_shellSettings = shellSettings;
_shellSettingsManager = shellSettingsManager;
}
public override string Name { get { return "ActivateShell"; } }
public override void Execute(RecipeExecutionContext context) {
_shellSettings.State = TenantState.Running;
_shellSettingsManager.SaveSettings(_shellSettings);
}
}
}

View File

@@ -74,7 +74,13 @@ namespace Orchard.Recipes.Services {
if (!String.IsNullOrWhiteSpace(recipeName))
query = from record in query where record.RecipeName == recipeName select record;
var stepResultRecord = query.Single();
var stepResultRecord = query.SingleOrDefault();
if (stepResultRecord == null)
// No step result record was created when scheduling the step, so simply ignore.
// The only reason where one would not create such a record would be Setup,
// when no database exists to store the record but still wants to schedule a recipe step (such as the "StopViewsBackgroundCompilationStep").
return;
stepResultRecord.IsCompleted = true;
stepResultRecord.IsSuccessful = isSuccessful;

View File

@@ -24,6 +24,7 @@ namespace Orchard.Setup.Controllers {
ISetupService setupService,
IViewsBackgroundCompilation viewsBackgroundCompilation,
ShellSettings shellSettings) {
_viewsBackgroundCompilation = viewsBackgroundCompilation;
_shellSettings = shellSettings;
_notifier = notifier;
@@ -42,6 +43,10 @@ namespace Orchard.Setup.Controllers {
public ActionResult Index() {
var initialSettings = _setupService.Prime();
if(initialSettings.State == TenantState.Initializing)
return View("Initializing");
var recipes = _setupService.Recipes().ToList();
string recipeDescription = null;
if (recipes.Count > 0) {
@@ -59,7 +64,7 @@ namespace Orchard.Setup.Controllers {
return IndexViewResult(new SetupViewModel {
AdminUsername = "admin",
DatabaseIsPreconfigured = !string.IsNullOrEmpty(initialSettings.DataProvider),
DatabaseIsPreconfigured = !String.IsNullOrEmpty(initialSettings.DataProvider),
Recipes = recipes,
RecipeDescription = recipeDescription
});
@@ -76,13 +81,13 @@ namespace Orchard.Setup.Controllers {
if (model.DatabaseProvider != SetupDatabaseType.Builtin && string.IsNullOrEmpty(model.DatabaseConnectionString))
ModelState.AddModelError("DatabaseConnectionString", T("A connection string is required.").Text);
if (!string.IsNullOrWhiteSpace(model.ConfirmPassword) && model.AdminPassword != model.ConfirmPassword ) {
if (!String.IsNullOrWhiteSpace(model.ConfirmPassword) && model.AdminPassword != model.ConfirmPassword) {
ModelState.AddModelError("ConfirmPassword", T("Password confirmation must match.").Text);
}
if (model.DatabaseProvider != SetupDatabaseType.Builtin && !string.IsNullOrWhiteSpace(model.DatabaseTablePrefix)) {
model.DatabaseTablePrefix = model.DatabaseTablePrefix.Trim();
if(!char.IsLetter(model.DatabaseTablePrefix[0])) {
if (!Char.IsLetter(model.DatabaseTablePrefix[0])) {
ModelState.AddModelError("DatabaseTablePrefix", T("The table prefix must begin with a letter.").Text);
}
@@ -103,7 +108,7 @@ namespace Orchard.Setup.Controllers {
foreach (var recipe in recipes.Where(recipe => recipe.Name == model.Recipe)) {
model.RecipeDescription = recipe.Description;
}
model.DatabaseIsPreconfigured = !string.IsNullOrEmpty(_setupService.Prime().DataProvider);
model.DatabaseIsPreconfigured = !String.IsNullOrEmpty(_setupService.Prime().DataProvider);
return IndexViewResult(model);
}
@@ -111,8 +116,7 @@ namespace Orchard.Setup.Controllers {
try {
string providerName = null;
switch (model.DatabaseProvider)
{
switch (model.DatabaseProvider) {
case SetupDatabaseType.Builtin:
providerName = "SqlCe";
break;
@@ -140,11 +144,11 @@ namespace Orchard.Setup.Controllers {
DatabaseProvider = providerName,
DatabaseConnectionString = model.DatabaseConnectionString,
DatabaseTablePrefix = model.DatabaseTablePrefix,
EnabledFeatures = null, // default list
EnabledFeatures = null, // Default list
Recipe = model.Recipe
};
string executionId = _setupService.Setup(setupContext);
var executionId = _setupService.Setup(setupContext);
// First time installation if finally done. Tell the background views compilation
// process to stop, so that it doesn't interfere with the user (asp.net compilation
@@ -153,7 +157,8 @@ namespace Orchard.Setup.Controllers {
// Redirect to the welcome page.
return Redirect("~/" + _shellSettings.RequestUrlPrefix);
} catch (Exception ex) {
}
catch (Exception ex) {
Logger.Error(ex, "Setup failed");
_notifier.Error(T("Setup failed: {0}", ex.Message));
@@ -161,7 +166,7 @@ namespace Orchard.Setup.Controllers {
foreach (var recipe in recipes.Where(recipe => recipe.Name == model.Recipe)) {
model.RecipeDescription = recipe.Description;
}
model.DatabaseIsPreconfigured = !string.IsNullOrEmpty(_setupService.Prime().DataProvider);
model.DatabaseIsPreconfigured = !String.IsNullOrEmpty(_setupService.Prime().DataProvider);
return IndexViewResult(model);
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Orchard.Setup.Controllers
{
public enum SetupDatabaseType
{
Builtin,
SqlServer,
MySql,
PostgreSql,
}
}

View File

@@ -25,6 +25,7 @@
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -78,7 +79,7 @@
<Compile Include="Annotations\StringLengthMin.cs" />
<Compile Include="Commands\SetupCommand.cs" />
<Compile Include="Controllers\SetupController.cs" />
<Compile Include="Controllers\SetupDatabaseType.cs" />
<Compile Include="ViewModels\SetupDatabaseType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Routes.cs" />
<Compile Include="Services\ISetupService.cs" />
@@ -136,6 +137,9 @@
<ItemGroup>
<Content Include="Views\Document.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Setup\Initializing.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -4,8 +4,7 @@ using System.Web.Routing;
using Orchard.Mvc.Routes;
namespace Orchard.Setup {
public class Routes : IRouteProvider
{
public class Routes : IRouteProvider {
public void GetRoutes(ICollection<RouteDescriptor> routes) {
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Web;
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.Core.Settings.Models;
using Orchard.Data;
@@ -16,7 +17,6 @@ using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.ShellBuilders;
using Orchard.Environment.State;
using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.Logging;
using Orchard.Recipes.Models;
@@ -26,7 +26,7 @@ using Orchard.Settings;
using Orchard.Utility.Extensions;
namespace Orchard.Setup.Services {
public class SetupService : ISetupService {
public class SetupService : Component, ISetupService {
private readonly ShellSettings _shellSettings;
private readonly IOrchardHost _orchardHost;
private readonly IShellSettingsManager _shellSettingsManager;
@@ -46,6 +46,7 @@ namespace Orchard.Setup.Services {
IProcessingEngine processingEngine,
IExtensionManager extensionManager,
IRecipeHarvester recipeHarvester) {
_shellSettings = shellSettings;
_orchardHost = orchardHost;
_shellSettingsManager = shellSettingsManager;
@@ -54,13 +55,8 @@ namespace Orchard.Setup.Services {
_processingEngine = processingEngine;
_extensionManager = extensionManager;
_recipeHarvester = recipeHarvester;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public Localizer T { get; set; }
public ILogger Logger { get; set; }
public ShellSettings Prime() {
return _shellSettings;
}
@@ -99,23 +95,20 @@ namespace Orchard.Setup.Services {
var shellSettings = new ShellSettings(_shellSettings);
if (string.IsNullOrEmpty(shellSettings.DataProvider)) {
if (String.IsNullOrEmpty(shellSettings.DataProvider)) {
shellSettings.DataProvider = context.DatabaseProvider;
shellSettings.DataConnectionString = context.DatabaseConnectionString;
shellSettings.DataTablePrefix = context.DatabaseTablePrefix;
}
#region Encryption Settings
shellSettings.EncryptionAlgorithm = "AES";
// randomly generated key
// Randomly generated key.
shellSettings.EncryptionKey = SymmetricAlgorithm.Create(shellSettings.EncryptionAlgorithm).Key.ToHexString();
shellSettings.HashAlgorithm = "HMACSHA256";
// randomly generated key
shellSettings.HashKey = HMAC.Create(shellSettings.HashAlgorithm).Key.ToHexString();
#endregion
// Randomly generated key.
shellSettings.HashKey = HMAC.Create(shellSettings.HashAlgorithm).Key.ToHexString();
var shellDescriptor = new ShellDescriptor {
Features = context.EnabledFeatures.Select(name => new ShellFeature { Name = name })
@@ -123,12 +116,12 @@ namespace Orchard.Setup.Services {
var shellBlueprint = _compositionStrategy.Compose(shellSettings, shellDescriptor);
// initialize database explicitly, and store shell descriptor
// Initialize database explicitly, and store shell descriptor.
using (var bootstrapLifetimeScope = _shellContainerFactory.CreateContainer(shellSettings, shellBlueprint)) {
using (var environment = bootstrapLifetimeScope.CreateWorkContextScope()) {
// check if the database is already created (in case an exception occured in the second phase)
// Check if the database is already created (in case an exception occured in the second phase).
var schemaBuilder = new SchemaBuilder(environment.Resolve<IDataMigrationInterpreter>());
var installationPresent = true;
try {
@@ -143,7 +136,7 @@ namespace Orchard.Setup.Services {
throw new OrchardException(T("A previous Orchard installation was detected in this database with this table prefix."));
}
// Make a workaround to avoid the Transaction issue for PostgreSQL
// Workaround to avoid some Transaction issue for PostgreSQL.
environment.Resolve<ITransactionManager>().RequireNew();
schemaBuilder.CreateTable("Orchard_Framework_DataMigrationRecord", table => table
@@ -168,15 +161,15 @@ namespace Orchard.Setup.Services {
}
}
// in effect "pump messages" see PostMessage circa 1980
// In effect "pump messages" see PostMessage circa 1980.
while (_processingEngine.AreTasksPending())
_processingEngine.ExecuteNextTask();
// creating a standalone environment.
// Creating a standalone environment.
// in theory this environment can be used to resolve any normal components by interface, and those
// components will exist entirely in isolation - no crossover between the safemode container currently in effect
// components will exist entirely in isolation - no crossover between the safemode container currently in effect.
// must mark state as Running - otherwise standalone enviro is created "for setup"
// Set shell state to "Running" so that the proper shell context is created.
shellSettings.State = TenantState.Running;
using (var environment = _orchardHost.CreateStandaloneEnvironment(shellSettings)) {
try {
@@ -188,24 +181,25 @@ namespace Orchard.Setup.Services {
}
}
// Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
shellSettings.State = _shellSettings.State = TenantState.Initializing;
_shellSettingsManager.SaveSettings(shellSettings);
return executionId;
}
private string CreateTenantData(SetupContext context, IWorkContextScope environment) {
// create superuser
// Create site owner.
var membershipService = environment.Resolve<IMembershipService>();
var user =
membershipService.CreateUser(new CreateUserParams(context.AdminUsername, context.AdminPassword,
String.Empty, String.Empty, String.Empty,
true));
var user = membershipService.CreateUser(
new CreateUserParams(context.AdminUsername, context.AdminPassword,
String.Empty, String.Empty, String.Empty, true));
// set superuser as current user for request (it will be set as the owner of all content items)
// Set site owner as current user for request (it will be set as the owner of all content items).
var authenticationService = environment.Resolve<IAuthenticationService>();
authenticationService.SetAuthenticatedUserForRequest(user);
// set site name and settings
// Set site name and settings.
var siteService = environment.Resolve<ISiteService>();
var siteSettings = siteService.GetSiteSettings().As<SiteSettingsPart>();
siteSettings.SiteSalt = Guid.NewGuid().ToString("N");
@@ -213,7 +207,7 @@ namespace Orchard.Setup.Services {
siteSettings.SuperUser = context.AdminUsername;
siteSettings.SiteCulture = "en-US";
// add default culture
// Add default culture.
var cultureManager = environment.Resolve<ICultureManager>();
cultureManager.AddCulture("en-US");
@@ -225,7 +219,19 @@ namespace Orchard.Setup.Services {
var executionId = recipeManager.Execute(recipe);
// null check: temporary fix for running setup in command line
// Once the recipe has finished executing, we need to update the shell state to "Running", so add a recipe step that does exactly that.
var recipeStepQueue = environment.Resolve<IRecipeStepQueue>();
var recipeStepResultRecordRepository = environment.Resolve<IRepository<RecipeStepResultRecord>>();
var activateShellStep = new RecipeStep(Guid.NewGuid().ToString("N"), recipe.Name, "ActivateShell", new XElement("ActivateShell"));
recipeStepQueue.Enqueue(executionId, activateShellStep);
recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
ExecutionId = executionId,
RecipeName = recipe.Name,
StepId = activateShellStep.Id,
StepName = activateShellStep.Name
});
// Null check: temporary fix for running setup in command line.
if (HttpContext.Current != null) {
authenticationService.SignIn(user, true);
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Web.Routing;
using Autofac;
using Orchard.Caching;

View File

@@ -0,0 +1,10 @@
namespace Orchard.Setup.ViewModels
{
public enum SetupDatabaseType
{
Builtin,
SqlServer,
MySql,
PostgreSql,
}
}

View File

@@ -1,5 +1,6 @@
@model Orchard.Setup.ViewModels.SetupViewModel
@using Orchard.Recipes.Models;
@using Orchard.Setup.ViewModels
@{
Script.Require("jQuery");
Script.Require("ShapesBase");
@@ -47,19 +48,19 @@ if (!Model.DatabaseIsPreconfigured) {
<legend>@T("How would you like to store your data?")</legend>
@Html.ValidationMessage("DatabaseOptions", "Unable to setup data storage.")
<div>
@Html.RadioButtonFor(svm => svm.DatabaseProvider, Orchard.Setup.Controllers.SetupDatabaseType.Builtin.ToString(), new { id = "builtin" })
@Html.RadioButtonFor(svm => svm.DatabaseProvider, SetupDatabaseType.Builtin.ToString(), new { id = "builtin" })
<label for="builtin" class="forcheckbox">@T("Use built-in data storage (SQL Server Compact)")</label>
</div>
<div>
@Html.RadioButtonFor(svm => svm.DatabaseProvider, Orchard.Setup.Controllers.SetupDatabaseType.SqlServer.ToString(), new { id = "sqlserver" })
@Html.RadioButtonFor(svm => svm.DatabaseProvider, SetupDatabaseType.SqlServer.ToString(), new { id = "sqlserver" })
<label for="sqlserver" class="forcheckbox">@T("Use an existing SQL Server, SQL Express database")</label>
</div>
<div>
@Html.RadioButtonFor(svm => svm.DatabaseProvider, Orchard.Setup.Controllers.SetupDatabaseType.MySql.ToString(), new { id = "mysql" })
@Html.RadioButtonFor(svm => svm.DatabaseProvider, SetupDatabaseType.MySql.ToString(), new { id = "mysql" })
<label for="mysql" class="forcheckbox">@T("Use an existing MySql database")</label>
</div>
<div>
@Html.RadioButtonFor(svm => svm.DatabaseProvider, Orchard.Setup.Controllers.SetupDatabaseType.PostgreSql.ToString(), new { id = "postgresql" })
@Html.RadioButtonFor(svm => svm.DatabaseProvider, SetupDatabaseType.PostgreSql.ToString(), new { id = "postgresql" })
<label for="postgresql" class="forcheckbox">@T("Use an existing PostgreSQL database")</label>
</div>
<div data-controllerid="builtin" data-defaultstate="hidden">

View File

@@ -0,0 +1 @@
@T("Orchard is initializing. Please check back in a few minutes.")

View File

@@ -9,7 +9,7 @@ namespace Orchard.Environment.Configuration {
public class ShellSettingsManager : Component, IShellSettingsManager {
private const string _settingsFileName = "Settings.txt";
private const string SettingsFileName = "Settings.txt";
private readonly IAppDataFolder _appDataFolder;
private readonly IShellSettingsManagerEventHandler _events;
@@ -37,7 +37,7 @@ namespace Orchard.Environment.Configuration {
throw new ArgumentException("The Name property of the supplied ShellSettings object is null or empty; the settings cannot be saved.", "settings");
Logger.Information("Saving ShellSettings for tenant '{0}'...", settings.Name);
var filePath = Path.Combine(Path.Combine("Sites", settings.Name), _settingsFileName);
var filePath = Path.Combine(Path.Combine("Sites", settings.Name), SettingsFileName);
_appDataFolder.CreateFile(filePath, ShellSettingsSerializer.ComposeSettings(settings));
Logger.Information("ShellSettings saved successfully; flagging tenant '{0}' for restart.", settings.Name);
@@ -48,7 +48,7 @@ namespace Orchard.Environment.Configuration {
var filePaths = _appDataFolder
.ListDirectories("Sites")
.SelectMany(path => _appDataFolder.ListFiles(path))
.Where(path => String.Equals(Path.GetFileName(path), _settingsFileName, StringComparison.OrdinalIgnoreCase));
.Where(path => String.Equals(Path.GetFileName(path), SettingsFileName, StringComparison.OrdinalIgnoreCase));
foreach (var filePath in filePaths) {
yield return ShellSettingsSerializer.ParseSettings(_appDataFolder.ReadFile(filePath));

View File

@@ -1,6 +1,7 @@
namespace Orchard.Environment.Configuration {
public enum TenantState {
Uninitialized,
Initializing,
Running,
Disabled,
Invalid

View File

@@ -128,12 +128,12 @@ namespace Orchard.Environment {
void CreateAndActivateShells() {
Logger.Information("Start creation of shells");
// is there any tenant right now ?
// Is there any tenant right now?
var allSettings = _shellSettingsManager.LoadSettings()
.Where(settings => settings.State == TenantState.Running || settings.State == TenantState.Uninitialized)
.Where(settings => settings.State == TenantState.Running || settings.State == TenantState.Uninitialized || settings.State == TenantState.Initializing)
.ToArray();
// load all tenants, and activate their shell
// Load all tenants, and activate their shell.
if (allSettings.Any()) {
Parallel.ForEach(allSettings, settings => {
try {
@@ -145,7 +145,7 @@ namespace Orchard.Environment {
}
});
}
// no settings, run the Setup
// No settings, run the Setup.
else {
var setupContext = CreateSetupContext();
ActivateShell(setupContext);
@@ -172,25 +172,27 @@ namespace Orchard.Environment {
}
/// <summary>
/// Creates a transient shell for the default tenant's setup
/// Creates a transient shell for the default tenant's setup.
/// </summary>
private ShellContext CreateSetupContext() {
Logger.Debug("Creating shell context for root setup");
Logger.Debug("Creating shell context for root setup.");
return _shellContextFactory.CreateSetupContext(new ShellSettings { Name = ShellSettings.DefaultName });
}
/// <summary>
/// Creates a shell context based on shell settings
/// Creates a shell context based on shell settings.
/// </summary>
private ShellContext CreateShellContext(ShellSettings settings) {
if (settings.State == TenantState.Uninitialized) {
Logger.Debug("Creating shell context for tenant {0} setup", settings.Name);
switch (settings.State) {
case TenantState.Uninitialized:
case TenantState.Initializing:
Logger.Debug("Creating shell context for tenant {0} setup.", settings.Name);
return _shellContextFactory.CreateSetupContext(settings);
}
Logger.Debug("Creating shell context for tenant {0}", settings.Name);
default:
Logger.Debug("Creating shell context for tenant {0}.", settings.Name);
return _shellContextFactory.CreateShellContext(settings);
}
}
private void SetupExtensions() {
_extensionLoaderCoordinator.SetupExtensions();

View File

@@ -37,6 +37,7 @@ namespace Orchard.Environment {
ISweepGenerator sweepGenerator,
IEnumerable<IOwinMiddlewareProvider> owinMiddlewareProviders,
ShellSettings shellSettings) {
_eventsFactory = eventsFactory;
_routeProviders = routeProviders;
_httpRouteProviders = httpRouteProviders;

View File

@@ -103,7 +103,8 @@ namespace Orchard.Environment.ShellBuilders {
Features = new[] {
new ShellFeature { Name = "Orchard.Setup" },
new ShellFeature { Name = "Shapes" },
new ShellFeature { Name = "Orchard.jQuery" },
new ShellFeature { Name = "Orchard.Recipes" },
new ShellFeature { Name = "Orchard.jQuery" }
},
};