Creating MinRole Compliant Custom Services for SharePoint 2016

Home » SharePoint Development » Creating MinRole Compliant Custom Services for SharePoint 2016

Creating MinRole Compliant Custom Services for SharePoint 2016

Posted on

One of the most significant new improvements in SharePoint 2016 is the MinRole funcionality. Any SharePoint administrator who is responsible for maintaining a farm consisting of more than three or four servers can attest to the difficulty of maintaining the proper allocation of services on each server. In a properly distributed farm architecture, each server should serve a specific role, such as search, distributed cache, web, or web front end, and services assigned to that role should be the only ones running on each server. This makes the overall administration of the farm much simpler, as there is never any question as to which machine is doing what and how pulling one out or adding another one into the environment will have an impact.

Although this is a desirable state for any multi-server farm, in the past it has been difficult to achieve as farm roles were ill-defined and there were no management tools to ensure that servers which fell out of compliance by running services not allocated to their role could be reset to a proper state automatically. The introduction of MinRole changed all that by forcing a decision during the installation process on which role a server should be assigned when it is added to the farm. This role assignment is then enforced post-provisioning by automated health check rules which identify servers running services that do not correspond to their defined role and returning the server to the desired state by automatically stopping non-conforming services.  In essence, it allows the SharePoint farm to monitor itself and take corrective action in an effort to improve functional stability.

[MORE INFO: Visit the following link for an overview of the benefits of MinRole, along with guidance for planning and managing a MinRole deployment:  https://msdn.microsoft.com/en-us/library/mt346114(v=office.16).aspx]

In order to support such fine-grained control over service allocation, the core services within SharePoint had to be modified to recognize the redefined server roles and comply with the new health check rules. Customers upgrading an out-of-the-box deployment from 2013 to 2016 automatically gain the benefits of MinRole compliance for all the default services; however, customers running custom services, whether developed internally or provided by a third party vendor, face a more challenging upgrade path. Custom services which ran fine on all servers in a 2013 environment may fail to provision in a 2016 MinRole environment and those that can be provisioned are likely to be disabled the first time the health check rules are executed. To prevent this from happening, developers are encouraged to update their custom service and service instance code to be MinRole compliant.

At first glance, this should be a simple matter of checking for service roles and returning a value indicating whether or not the service instance can run on the specified server. In essence that is, in fact, the end result; however, getting there is not quite so straightforward as just adding a new method to a service instance class, evaluating a role type enumeration and returning a boolean value. There are several steps that have to be done in the right order to get everything working properly so the service can be provisioned initially and pass the health checks once it’s running in the farm.

As of this writing, the documentation for adding MinRole functionality to a custom service consists of a sparse bit of text at the end of a single MSDN article: https://msdn.microsoft.com/en-us/library/mt743705(v=office.16).aspx. There are no examples or sample code to help get developers started down the right path. Even if the simple steps in the article are followed, the resulting modifications will not actually result in a service instance that can be successfully provisioned in a compliant state. To achieve that, a few more steps are required. Below you will find a step-by-step guide to enabling MinRole compliance for any custom service with code samples and explanatory descriptions. At the end of the walkthrough is a link to the source code for a very basic service project that can be used as a template for applying the modifications to your own projects.

 

Step 1: Preparation

In order to test your code modifications, access to a multi-server farm with at least one server in a defined role other than “Custom” or “SingleServerFarm” is necessary. This is the first hurdle that most developers will encounter, as a standard SharePoint development environment consists of a single server running “all up” with most, if not all, of the default services already provisioned. This won’t be sufficient as the checks for MinRole compliance are ignored when the “Custom” role exists. Deployment will always succeed in this scenario which is not a true reflection of what will happen in a production farm. So adding a second server to the development/test farm running one of the defined server roles other than “Custom”, “SingleServerFarm” or “Invalid” is critical.

With a second server (or more) in the farm, service provisioning and health check rule execution for each service can be tested. The next action is to FULLY retract and remove all existing service applications (if your service requires them), service instances AND SERVICES. This is an important distinction as any leftover deployment artifacts will skew the provisioning tests. Retracting the WSP may remove the service instance entries from the Services on Server page but unless there is code in an event receiver to also unprovision and delete the underlying service, it will remain resident in the farm until manually removed with additional code or PowerShell commands. Bear this in mind also when updating the production farm after the custom services have been made MinRole compliant.

 

Step 2: Server Role Verification

In a MinRole deployment, service instances are assigned to roles using the SPServerRole enumeration (https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spserverrole.aspx). This enumeration was available in SharePoint 2013 but it has changed slightly in 2016; there are now seven potential server roles:

  • Invalid (typically used for database servers)
  • WebFrontEnd
  • Application
  • SingleServerFarm
  • DistributedCache
  • Search
  • Custom

(NOTE: The “SingleServer” role from 2013 is now obsolete.)

Each service instance should be assigned to one or more of these roles by overriding the ShouldProvision method of the base SPServiceInstance class. This method takes a server role enumeration value as a parameter and returns a boolean indicating whether or not the service instance should run on the specified server type. For example, a service instance that runs on web and application servers should include code similar to the following:


public override bool ShouldProvision(SPServerRole serverRole)
{
return SPServerRole.Application == serverRole || SPServerRole.WebFrontEnd == serverRole;
}

Bear in mind that the “Custom” role does not have to be specified as all service instances will run on servers of this type. If no value is specified or this method is omitted, the base implementation will return ‘false’. This method does not take the place of optional methods that check for specific service applications or other service instances. If, for example, a custom service should only run on servers that have the “Central Administration” service instance provisioned then an additional check is required in a separate method called during provisioning (and example of such a method named “IsSupportedServer” can be found in the linked sample project).

Step 3: MinRole Integration

The service class itself must also be modified so validation calls for MinRole compliance return the correct result. This is achieved by setting the AutoProvision property of the service class (https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spservice.autoprovision.aspx). When the AutoProvision property on a service returns true, the calling assembly will then pass the farm roles to the ShouldProvision method of the service instance.  For each true value returned from the service instance, the service will be provisioned on each server of that type in the farm. If it returns ‘false’, the service will be provisioned only on servers with the “Custom” role.

There are several important factors relating to the use of the AutoProvision property that developers need to be aware of. First, if a service application is associated with a service then the value of AutoProvision is treated as always returning ‘true’. If there are no service applications then the value is treated the same as the default from the base SPService class which is ‘false’.

Second, health check rules have no effect in cases where the server role is “Custom”. All servers on a “Custom” server will remain provisioned when the rules are executed; likewise, if they are in an unprovisioned state then they will no attempt will be made to provision them. This is contrary to all other role types and can lead to much confusion in a development environment where the primary server almost always has the “Custom” role; it is not unusual to see a service instance being provisioned and unprovisioned on some servers and not others during testing (and, to complicate matters further, it is common for an instance that has been unprovisioned as the result of a health check to get blocked from re-provisioning in Central Administration). In these cases, PowerShell can be used to fully unprovision and remove the offending service instances and services.

Third, the timing of setting the AutoProvision property within the provisioning lifecycle is critical. A common practice for avoiding duplicate service instance provisioning is to perform a check in the feature receiver or other code to evaluate the “Status” property of the service; if it is set to “SPObjectStatus.Online” then provisioning can be skipped, otherwise provisioning proceeds. To make this work correctly, the service class should have its “Status” property set to “SPObjectStatus.Offline” in the default constructor. This is due to the way service classes are instantiated, which requires a new instance to be invoked and subsequently updated before the provisioning methods can be called. If the default state is online then provisioning will never proceed; conversely, if it is never set to online after successful provisioning then duplicates are possible (which cause all sorts of problems with the correct processing of the MinRole health check rules).

Service status plays an important role when it comes to setting the value of the AutoProvision property. If the guidance in the documentation is followed, the AutoProvision property is set in the default constructor. This works fine when only a single service instance exists on any one server but as soon as a duplicate is introduced (by not following the practice mentioned above) the service becomes orphaned and unprovisioning will only succeed by forcibly removing the timer job that the health check rule (or a direct call to the unprovision method of the service) invokes. Furthermore, setting the AutoProvision status in the default constructor of the service is actually out-of-band with respect to the overall provisioning process. It is not required when the service instances are provisioned from the service itself (as seen in the next section) but rather when the MinRole compliance checks are run; if the service instance provisioning fails and the AutoProvision property is already set to ‘True’ then the health checks are going to try and re-provision service instances that cannot actually be activated, resulting in a never ending loop of failed provisioning. Therefore, it is more appropriate to set the AutoProvision property AFTER the initial service instance provisioning when the status is known to be good (or, in the case of the provisioning timer jobs, assumed to be good) and the service switched to an online state.

(NOTE: Some examples found on various sites and blogs show the AutoProvision property being set on the base SPService class instead of the derived service class. Some even indicate it should be overriden from the base class. This is incorrect. The value should be set on the instance the property is scoped to, which is the derived service class.)

Step 4: Provisioning  

It is worth revisiting the provisioning process for custom services as the health check rules for MinRole compliance (referred to as “Server role configuration isn’t correct” in the list of rules in Central Administration) will automatically provision and unprovision service instances without user intervention. Plus, the initial provisioning process during deployment can cause problems during development if the service itself isn’t removed properly or a localized deployment is assumed without testing for multiple instances (a common mistake when everything runs just fine in the “Custom” role).

The “Provision” method of a service class is really just an entry point for the deployment of service instances. Although an underlying service will exist in the farm (easily seen by running the Get-SPService command in PowerShell) it is actually the service instances that do the real work. There are two ways to provision a service instance: 1) locally on the server the code is running on, and 2) a pre-defined timer job. In a multi-server production environment both are actually necessary, whereas in a typical single-server development environment the first method is sufficient. These can be combined in a single method that performs both operations. First, the SPServiceInstance.Provision() method can be called for deployment to the local server, then the instance can be passed to a new SPServiceJobDefinition for scheduling. Optionally, the code can wait for the jobs to complete, which provides an added layer of assurance when setting the AutoProvision property.

In the sample project, this logic is contained within the ProvisionServiceInstance method of the ProvisioningUtility class (which is only abstracted for maintainability - it can be included directly in the service class code if necessary):


public static void ProvisionServiceInstance(SPServiceInstance instance, bool wait)
{
if (instance.Status == SPObjectStatus.Offline || instance.Status == SPObjectStatus.Disabled)
{
if (instance.Server.Id == SPServer.Local.Id)
{
instance.Provision();
}
else
{
SPServiceInstanceJobDefinition definition = new SPServiceInstanceJobDefinition(instance, true);
definition.Schedule = new SPOneTimeSchedule(DateTime.Now);
definition.Update();
}
if (wait)
{
WaitForServiceInstanceStatus(instance, SPObjectStatus.Online, new TimeSpan(0, 1, 0));
}
}
}

 

Failure to provision a service instance in a multi-server environment via the provided timer job will most likely result in an error and cause the service instance to fail the MinRole compliancy check. As such, it is a good idea to add some additional code in the service class to ensure the timer jobs are properly initiated. This can be done in the “Provision” method of the service, which also creates the service instances on each farm and initiates provisioning (which are actually two separate processes). For example:


public override void Provision()
{
EnsureServiceInstances();
ProvisioningUtility.ProvisionServiceInstances(Instances, false);
ProvisioningUtility.EnableTimerJobs(JobDefinitions);
Status = SPObjectStatus.Online;
AutoProvision = true;
this.Update();
}

private void EnsureServiceInstances()
{
SPFarm farm = SPFarm.Local;
if (farm == null)
{
throw new Exception("This server is not part of a farm.");
}
foreach (SPServer server in farm.Servers)
{
if (DemoServiceInstance.IsSupportedServer(server))
{
DemoServiceInstance serviceInstance = DemoServiceInstance.GetServiceInstance(server);
if (serviceInstance == null)
{
serviceInstance = new DemoServiceInstance(Guid.NewGuid().ToString(), server, this);
serviceInstance.Update();
}
}
}
}

Note that each service instance, on each server, is first instantiated and then subsequently updated (which commits the instance), followed by the actual provisioning on the local server and via the timer job as seen in the previous code sample. It may be tempting to invoke provisioning directly on the service instance after instantiating it in the for…each block using the current server reference; however, provisioning will fail with a conflict error if this is attempted. The correct way to provision service instances across multiple servers is by creating a new timer job instance using the SPServiceInstanceJobDefinition object.  Also note that the AutoProvision property is set after provisioning of the service instances is complete and the service status is set to online (both of which are applied simultaneously in the service “Update” method, so the order could be swapped without impacting the final result).

Step 5: Unprovisioning

During development, it is likely that multiple attempts will be made to provision the service while working out bugs in the code. Doing so requires deleting the service itself in addition to removing the service instances. A retraction of the solution package will not achieve this unless code is added to the FeatureUninstalling event in the feature receiver (alternatively, this can be done via PowerShell if a feature receiver is not being used to provision the service to begin with). Failure to remove the underlying service will result in service instance provisioning failures, further complicated by the automatic unprovisioning attempts made by the MinRole health check rule. The following example demonstrates automatic removal of the service in a feature receiver:


public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
DemoService demoService = DemoService.GetService();
if (demoService != null)
{
demoService.Delete();
}
}

 

Step 6: Other Considerations

The remainder of the code in the sample involves standard service-related methods for unprovisioning, deletion, creating a link to a service configuration application page, and so on. Notably absent from the sample is implementation of the “IsReadyForRoleConversion” method as described in the documentation on TechNet (https://technet.microsoft.com/en-us/library/mt743705(v=office.16).aspx​). The text relating to that method was only recently added and the method does not exist in the base SPServiceInstance class in builds prior to November 2016. It can only be assumed that an update will include this functionality, so code modifications will be necessary when the appropriate update is installed in farms that contain custom services. The method itself is simple enough, producing a custom error message that provides an error message in Central Administration containing instructions regarding management of a service instance when a server role changes to one that the service does not support.

In conclusion, converting existing custom services to be MinRole compliant is not a complicated task but there are some important points that need to be considered prior to making code modifications. To begin with, developers must have a properly configured environment for testing, which is not likely to be the case in most organizations. Once this hurdle has been cleared, completely removing any existing service artifacts is essential to establishing a clean baseline for MinRole validation. Assuming that provisioning is already being handled in the proper manner for multiple server deployment, the code changes come down to one additional method in the service instance class and a new property setting in the service class. Naturally, if provisioning isn’t being handled properly in the existing code, further modifications will be necessary to change the provisioning behavior and sequence; however, overall time to complete the task should be minimal if the steps outlined herein are followed in the correct order. Developers should take note of the fact that these steps differ slightly from those described in the TechNet documentation with regards to the point in the service instantiation and provisioning lifecycle at which the AutoProvision property should be set for the most consistent results.

 

The sample project can be downloaded from the following GitHub repo: https://github.com/eshupps/SPDemo.Services.MinRole​​

 

Eric Shupps Eric Alan Shupps eshupps @eshupps SharePoint Cowboy BinaryWave SmartTrack
Take the trouble out of troubleshooting.
Improve service levels and avoid downtime with

SmartTrack Operational Analytics for SharePoint​​