Skip to content

Commit e8f7f77

Browse files
committed
Fixes: U4-581 Automatic publishing not working in load balanced setup - added some more convention and configuration to distributed calls so that servers are aware of the master and how to call into themselves for scheduled tasks, ping and scheduled publishing. Will need to update the docs on LB regarding this too. Cleaned up the code that does the scheduling and separates it into proper segments. Obsoletes the old presentation classes that were doing it.
1 parent b8575ef commit e8f7f77

18 files changed

+711
-172
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using Umbraco.Core.Logging;
3+
using Umbraco.Core.Models;
4+
using Umbraco.Core.Services;
5+
6+
namespace Umbraco.Core.Publishing
7+
{
8+
/// <summary>
9+
/// Used to perform scheduled publishing/unpublishing
10+
/// </summary>
11+
internal class ScheduledPublisher
12+
{
13+
private readonly IContentService _contentService;
14+
15+
public ScheduledPublisher(IContentService contentService)
16+
{
17+
_contentService = contentService;
18+
}
19+
20+
public void CheckPendingAndProcess()
21+
{
22+
foreach (var d in _contentService.GetContentForRelease())
23+
{
24+
try
25+
{
26+
d.ReleaseDate = null;
27+
var result = _contentService.SaveAndPublishWithStatus(d, (int)d.GetWriterProfile().Id);
28+
if (result.Success == false)
29+
{
30+
if (result.Exception != null)
31+
{
32+
LogHelper.Error<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception);
33+
}
34+
else
35+
{
36+
LogHelper.Warn<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType);
37+
}
38+
}
39+
}
40+
catch (Exception ee)
41+
{
42+
LogHelper.Error<ScheduledPublisher>(string.Format("Error publishing node {0}", d.Id), ee);
43+
throw;
44+
}
45+
}
46+
foreach (var d in _contentService.GetContentForExpiration())
47+
{
48+
try
49+
{
50+
d.ExpireDate = null;
51+
_contentService.UnPublish(d, (int)d.GetWriterProfile().Id);
52+
}
53+
catch (Exception ee)
54+
{
55+
LogHelper.Error<ScheduledPublisher>(string.Format("Error unpublishing node {0}", d.Id), ee);
56+
throw;
57+
}
58+
}
59+
}
60+
}
61+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace Umbraco.Core.Sync
2+
{
3+
/// <summary>
4+
/// The current status of the server in the Umbraco environment
5+
/// </summary>
6+
internal enum CurrentServerEnvironmentStatus
7+
{
8+
/// <summary>
9+
/// If the current server is detected as the 'master' server when configured in a load balanced scenario
10+
/// </summary>
11+
Master,
12+
13+
/// <summary>
14+
/// If the current server is detected as a 'slave' server when configured in a load balanced scenario
15+
/// </summary>
16+
Slave,
17+
18+
/// <summary>
19+
/// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario
20+
/// </summary>
21+
Unknown,
22+
23+
/// <summary>
24+
/// If load balancing is not enabled and this is the only server in the umbraco environment
25+
/// </summary>
26+
Single
27+
}
28+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using System;
2+
using System.Linq;
3+
using System.Web;
4+
using System.Xml;
5+
using Umbraco.Core.Configuration;
6+
using Umbraco.Core.IO;
7+
8+
namespace Umbraco.Core.Sync
9+
{
10+
/// <summary>
11+
/// A helper used to determine the current server environment status
12+
/// </summary>
13+
internal static class ServerEnvironmentHelper
14+
{
15+
/// <summary>
16+
/// Returns the current umbraco base url for the current server depending on it's environment
17+
/// status. This will attempt to determine the internal umbraco base url that can be used by the current
18+
/// server to send a request to itself if it is in a load balanced environment.
19+
/// </summary>
20+
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco )</returns>
21+
public static string GetCurrentServerUmbracoBaseUrl()
22+
{
23+
var status = GetStatus();
24+
25+
if (status == CurrentServerEnvironmentStatus.Single)
26+
{
27+
//if it's a single install, then the base url has to be the first url registered
28+
return ApplicationContext.Current.OriginalRequestUrl;
29+
}
30+
31+
var servers = UmbracoSettings.DistributionServers;
32+
33+
var nodes = servers.SelectNodes("./server");
34+
if (nodes == null)
35+
{
36+
//cannot be determined, then the base url has to be the first url registered
37+
return ApplicationContext.Current.OriginalRequestUrl;
38+
}
39+
40+
var xmlNodes = nodes.Cast<XmlNode>().ToList();
41+
42+
foreach (var xmlNode in xmlNodes)
43+
{
44+
var appId = xmlNode.AttributeValue<string>("appId");
45+
var serverName = xmlNode.AttributeValue<string>("serverName");
46+
47+
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
48+
{
49+
continue;
50+
}
51+
52+
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
53+
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
54+
{
55+
//match by appId or computer name! return the url configured
56+
return string.Format("{0}://{1}:{2}/{3}",
57+
xmlNode.AttributeValue<string>("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue<string>("forceProtocol"),
58+
xmlNode.InnerText,
59+
xmlNode.AttributeValue<string>("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue<string>("forcePortnumber"),
60+
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
61+
}
62+
}
63+
64+
//cannot be determined, then the base url has to be the first url registered
65+
return ApplicationContext.Current.OriginalRequestUrl;
66+
}
67+
68+
/// <summary>
69+
/// Returns the current environment status for the current server
70+
/// </summary>
71+
/// <returns></returns>
72+
public static CurrentServerEnvironmentStatus GetStatus()
73+
{
74+
if (UmbracoSettings.UseDistributedCalls == false)
75+
{
76+
return CurrentServerEnvironmentStatus.Single;
77+
}
78+
79+
var servers = UmbracoSettings.DistributionServers;
80+
81+
var nodes = servers.SelectNodes("./server");
82+
if (nodes == null)
83+
{
84+
return CurrentServerEnvironmentStatus.Unknown;
85+
}
86+
87+
var master = nodes.Cast<XmlNode>().FirstOrDefault();
88+
89+
if (master == null)
90+
{
91+
return CurrentServerEnvironmentStatus.Unknown;
92+
}
93+
94+
//we determine master/slave based on the first server registered
95+
//TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that
96+
// but we'd need to change the IServerAddress interfaces which is breaking.
97+
98+
var appId = master.AttributeValue<string>("appId");
99+
var serverName = master.AttributeValue<string>("serverName");
100+
101+
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
102+
{
103+
return CurrentServerEnvironmentStatus.Unknown;
104+
}
105+
106+
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
107+
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
108+
{
109+
//match by appdid or server name!
110+
return CurrentServerEnvironmentStatus.Master;
111+
}
112+
113+
return CurrentServerEnvironmentStatus.Slave;
114+
}
115+
}
116+
}

src/Umbraco.Core/Umbraco.Core.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@
770770
<Compile Include="Publishing\PublishingStrategy.cs" />
771771
<Compile Include="Publishing\PublishStatus.cs" />
772772
<Compile Include="Publishing\PublishStatusType.cs" />
773+
<Compile Include="Publishing\ScheduledPublisher.cs" />
773774
<Compile Include="RenderingEngine.cs" />
774775
<Compile Include="Security\AuthenticationExtensions.cs" />
775776
<Compile Include="Security\IUsersMembershipProvider.cs" />
@@ -830,7 +831,9 @@
830831
<Compile Include="Standalone\StandaloneCoreBootManager.cs" />
831832
<Compile Include="Strings\ContentBaseExtensions.cs" />
832833
<Compile Include="Strings\Diff.cs" />
834+
<Compile Include="Sync\CurrentServerEnvironmentStatus.cs" />
833835
<Compile Include="Sync\RefreshInstruction.cs" />
836+
<Compile Include="Sync\ServerEnvironmentHelper.cs" />
834837
<Compile Include="TopologicalSorter.cs" />
835838
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
836839
<Compile Include="Strings\IUrlSegmentProvider.cs" />

src/Umbraco.Tests/TypeHelperTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Umbraco.Tests.PartialTrust;
1010
using Umbraco.Web;
1111
using Umbraco.Web.Cache;
12+
using Umbraco.Web.Scheduling;
1213
using UmbracoExamine;
1314
using umbraco;
1415
using umbraco.presentation;
@@ -67,7 +68,7 @@ public void Find_Common_Base_Class()
6768
Assert.AreEqual(typeof(UmbracoEventManager), t5.Result);
6869

6970
var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler),
70-
typeof (LegacyScheduledTasks),
71+
typeof (Scheduler),
7172
typeof(CacheRefresherEventHandler));
7273
Assert.IsTrue(t6.Success);
7374
Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result);

src/Umbraco.Web.UI/config/umbracoSettings.Release.config

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,47 @@
223223
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
224224
</scheduledTasks>
225225

226-
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
226+
<!-- distributed calls must be enabled when using Umbraco in a load balanced environment -->
227227
<distributedCall enable="false">
228228
<!-- the id of the user who's making the calls -->
229229
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
230230
<user>0</user>
231+
232+
<!--
233+
When distributed call is enabled, you need to add all of the servers part taking in load balancing
234+
to the server list below.
235+
-->
236+
231237
<servers>
232-
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
233-
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
234-
<!-- <server>127.0.0.1</server>-->
235-
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
238+
239+
<!--
240+
Add ip number or hostname, make sure that it can be reached from all servers
241+
you can also add optional attributes to force a protocol or port number.
242+
243+
Examples:
244+
245+
<server>127.0.0.1</server>
246+
<server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>
247+
248+
Generally when setting up load balancing you will designate a 'master' server,
249+
Umbraco will always assume that the FIRST server listed in this list is the 'master'.
250+
(NOTE: Not all load balancing scenarios have a 'master', depends on how you are setting it up)
251+
252+
In order for scheduled tasks (including scheduled publishing) to work properly when load balancing, each
253+
server in the load balanced environment needs to know if it is the 'master'. In order for servers
254+
to know this or not, they need to compare some values against the servers listed. These values
255+
are either: serverName or appId. You should not enter both values but appId will always supersede serverName.
256+
The serverName is the easiest and will work so long as you are not load balancing your site on the same server.
257+
If you are doing this, then you will need to use appId which is equivalent to the value returned from
258+
HttpRuntime.AppDomainAppId. It is recommended that you set either the serverName or appId for all servers
259+
registered here if possible, not just the first one.
260+
261+
Examples:
262+
263+
<server serverName="MyServer">server1.mysite.com</server>
264+
<server appId="/LM/W3SVC/69/ROOT">server1.mysite.com</server>
265+
-->
266+
236267
</servers>
237268
</distributedCall>
238269

src/Umbraco.Web.UI/config/umbracoSettings.config

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,51 @@
223223
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
224224
</scheduledTasks>
225225

226-
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
227-
<distributedCall enable="false">
226+
<!-- distributed calls must be enabled when using Umbraco in a load balanced environment -->
227+
<distributedCall enable="true">
228228
<!-- the id of the user who's making the calls -->
229229
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
230230
<user>0</user>
231+
232+
<!--
233+
When distributed call is enabled, you need to add all of the servers part taking in load balancing
234+
to the server list below.
235+
-->
236+
231237
<servers>
232-
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
233-
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
234-
<!-- <server>127.0.0.1</server>-->
235-
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
238+
239+
<!--
240+
Add ip number or hostname, make sure that it can be reached from all servers
241+
you can also add optional attributes to force a protocol or port number.
242+
243+
Examples:
244+
245+
<server>127.0.0.1</server>
246+
<server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>
247+
248+
Generally when setting up load balancing you will designate a 'master' server,
249+
Umbraco will always assume that the FIRST server listed in this list is the 'master'.
250+
(NOTE: Not all load balancing scenarios have a 'master', depends on how you are setting it up)
251+
252+
In order for scheduled tasks (including scheduled publishing) to work properly when load balancing, each
253+
server in the load balanced environment needs to know if it is the 'master'. In order for servers
254+
to know this or not, they need to compare some values against the servers listed. These values
255+
are either: serverName or appId. You should not enter both values but appId will always supersede serverName.
256+
The serverName is the easiest and will work so long as you are not load balancing your site on the same server.
257+
If you are doing this, then you will need to use appId which is equivalent to the value returned from
258+
HttpRuntime.AppDomainAppId. It is recommended that you set either the serverName or appId for all servers
259+
registered here if possible, not just the first one.
260+
261+
Examples:
262+
263+
<server serverName="MyServer">server1.mysite.com</server>
264+
<server appId="/LM/W3SVC/69/ROOT">server1.mysite.com</server>
265+
-->
266+
267+
<server forcePortnumber="6210" appId="/LM/W3SVC/69/ROOT">localhost</server>
268+
<server>umb1.dev</server>
269+
<server>umb2.dev</server>
270+
236271
</servers>
237272
</distributedCall>
238273

0 commit comments

Comments
 (0)