Monday, April 16, 2018

Automate Azure API Management: Backup and Restore

Once you setup the service in Azure and add all your APIs, you should make sure you have a good Backup strategy in place, in case things go wrong.

If you do regularly backup the service, then you can also use those backups for a couple of purposes:

you can for example deploy more than one instance and put a Traffic Manager in front of them, in order to handle geographical distribution.

But you can also restore a specific backup to a different Subscription, and create a separate environment, as per the DTAP street.

Azure API Management provides a REST API that you can use to automate things; unfortunately that works for most features and activities, but not for backing up and restoring the whole service.

For this you need to use the good "old" ARM, which also provides a REST API with specififc API Management operations.

By reading the article above, you might think that this is as easy as getting a token, backing up and restoring the service...

Well, not if you want an idempotent process; in such case, you are on the right post, so just keep reading.

Backing up the service is quite straightforward, as long as you have a good Retry strategy (I suggest Polly); in fact, since some API Management operations can take several minutes (see up to 45 minutes...), once you send a request to backup your service, you get a 202 Accepted HTTP Response, with a Location URL, that you can use to regularly check for the backup operation to be completed.

The same is true for restoring, as well as creating the service in the first place (and keep in mind that you need the service instance to be restored to to exist before you start restoring).

And what if something goes wrong while you wait for completion? Most likely you will lose the Locaion URL, making it harder to know when the process is done (unless manually checking the Azure Portal, which defies the automation).

So in a true DevOps manner, in the diagram here is the fully automated process for backing up and restoring an API Management service.


As you can see in the above picture, there are several steps just to make sure things won't break in different scenarios.

There is also an Azure Storage Table used for auditing (or saving the backup and restore status each time).

So here is the pseudo-code for the Backup operation:

ExecuteRestRequestReturnLocation()
AddBackupEntryToStorageTable()
CheckForOperationCompletion()
UpdateBackupEntryToStorageTable()


Where the most interesting part is the CheckForOperationCompletion(), using Polly and a RetryPolicy, as shown below here:

private async void CheckForOperationCompletion(string locationUrl, string token)
        {
            try
            {
                HttpStatusCode[] httpStatusCodesWorthRetrying = {
                   HttpStatusCode.Accepted, // 202
                };

                var policy = Policy
                  .HandleResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
                  .WaitAndRetryForeverAsync(retryAttempt => TimeSpan.FromMinutes(Math.Pow(_retryAfterMinutes, retryAttempt)));

                HttpResponseMessage result = null;
                result = await RestHelper.ExecuteRestRequestRetryResponseMessage(locationUrl, "Bearer", token, new RetryPolicy<HttpResponseMessage>[] { policy });
                if (result.StatusCode != HttpStatusCode.OK)
                {
                    Log.Error("Exiting CheckForOperationCompletion because of StatusCode: {StatusCode}", result.StatusCode);
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex.Message + " " + ex.StackTrace);
            }
        }

Now, that was quite straightforward..

The Restore part itself is nearly identical to the Backup pseudo-code above, however, when put in the bigger picture of fully restoring an existing API Management backup, there is a bigger logic involved (as you can already see in the diagram above).

private static bool CreateNewApimService(string svcName, string destinationSubscriptionId, string destinationResourceGroupName, string appName)
        {
            //TODO: make async!!
            //1) check if svc already exists
            if (!armClient.VerifyApimServiceExists(svcName).Result)
            {
                //2) if not, check name availability
                if (armClient.CheckApimServiceNameAvailability(svcName, destinationSubscriptionId).Result)
                {
                    //3) then create svc
                    if (!armClient.CreateApimService(svcName, destinationSubscriptionId, destinationResourceGroupName, "West Europe", "Developer").Result)
                    {
                        Log.Error("The Service {0} was not created, something went wrong.", svcName);
                        return false;
                    }
                    
                    //4) Create App Registration (DevPortal) - AzureGraphClient
                    var res = azGraphClient.CreateAppRegistration("new-aad-app-registration", appName, serviceName).Result; 
                    if (String.IsNullOrEmpty(res) || res.StartsWith("Error"))
                    {
                        Log.Error("Cannot create App Registration for {0}", svcName);
                    }
                    else
                    {
                        var appId = res;
                        Log.Information($"API Management service {svcName} was succesfully created along with its AAD App Registration (AppId {appId}).");
                        Log.Information($"The App Registration needs to be authorized before it can be used. An Admin needs to Grant Permissions to it from the Azure portal.");
                    }
                }
                else
                {
                    Log.Error("The name {0} is not available for API Management.", svcName);
                    return false;
                }
            }
            return true;
        }

As you can see there is quite some logic involved when creating a new API Management service programmatically, such as:

1) Check if the service exists already,
2) Check service name availability,
3) Create APIM service,
4) Create App Registration for the APIM Developer Portal...

In particular, here I use Polly to retry after the API Management service creation, which unlike the Backup and Restore operations (where you get a 202 - Accepted until a 200 - OK), it always returns 200 - OK, and in the Response Content Body it contains a JSON field called provisioningState, which will be initially "Created", and only after several minutes (up to 45...) it will become "Succeeded".


Inside the CreateApimService method there is the Polly Retry Policy:
public async Task<bool> CheckForCreationCompletion(string url)
        {
            var response = await WaitForCreationCompletion(() => RestHelper.ExecuteRestRequest(url, "Bearer", _accessToken, HttpMethod.Get, ""));
            if (!response)
            {
                Log.Error("CheckForCreationCompletion failed");
            }
            return response;
        }

        private Task<bool> WaitForCreationCompletion(Func<Task<HttpResponseMessage>> requester)
        {
            var policy = Policy
                    .HandleResult<bool>(false)
                    .WaitAndRetryForeverAsync(retryAttempt => TimeSpan.FromMinutes(Math.Pow(_retryAfterMinutes, retryAttempt)));

            //var retryPolicy = new RetryPolicy<HttpResponseMessage>[] { policy };
            //you can subscribe to the RetryPolicy.Retrying event here to be notified 
            //of retry attempts (e.g. for logging purposes)
            return policy.ExecuteAsync(async () =>
            {
                HttpResponseMessage response;
                try
                {
                    response = await requester().ConfigureAwait(false);
                }
                catch (TaskCanceledException e) //HttpClient throws this on timeout
                {
                    //we need to convert it to a different exception
                    //otherwise ExecuteAsync will think we requested cancellation
                    throw new HttpRequestException("Request timed out", e);
                }
                //assuming you treat an unsuccessful status code as an error
                //otherwise just return the respone here
                return response.CheckForSucceededState();
            });
        }

And to check for the custom message returned, I use a C# Extension:
public static bool CheckForSucceededState(this HttpResponseMessage r)
        {
            if (r == null || !r.IsSuccessStatusCode) return false;
            var readResult = r.Content.ReadAsStringAsync().Result;
            JObject o = JObject.Parse(readResult);
            var provisioningState = (string)o["properties"]["provisioningState"]; //Created, Succeeded
            var targetProvisioningState = (string)o["properties"]["targetProvisioningState"]; //Activating
            return provisioningState == "Succeeded";
        }

And this is the ResilientHttpClient used for the regular Polly Retry in the CheckForOperationCompletion:
public class ResilientHttpClient<T> : HttpClient where T : class
    {
        private HttpClient _client;
        private RetryPolicy<T> _policy;

        public ResilientHttpClient(RetryPolicy<T>[] policies) 
        {
            _policy = policies[0];
            _client = new HttpClient();            
        }

        private Task<T> HttpInvoker(Func<Task<T>> action)
        {
            return _policy.ExecuteAsync(() => action());
        }

        public Task<T> SendAsync(string uri,
            HttpMethod method,
            string authorizationToken = null,
            string authorizationMethod = "Bearer")
        {
            return HttpInvoker(async () =>
            {
                var requestMessage = new HttpRequestMessage(method, uri);
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken);
                var response = await _client.SendAsync(requestMessage);
                return response as T;
            });
        }
        
    }

While for the CheckForOperationCompletion a standard HttpClient is used.

Now, as you might have noticed, I added a comment to make this code Async (right now this is a prototype Console App).

Also, ideally an Email should be sent to the user who starts those long running processes (such as creating API Management service or backing it up and restoring it),

And this code should probably reside inside a WebJob (or Service), so it can run on its own, no matter which client calls it.

But all that is out of scope for this prototype and blog post, so that's all for now!

Friday, March 16, 2018

Azure Active Directory Application Components

Here's a diagram to show what are the main components involved in an Azure AppService deployment and configuration, between the actual application and the Azure Active Directory setup.

For those not familiar with Azure, it can get confusing between the concepts of Directory, Tenant, Subscription, and all the sub-components that need to be properly configured when deploying to Azure.

Hopefully this high-level diagram can clarify a bit more.

A few notes:

  • you can create several Tenants under a single Directory.
  • you can use Azure Active Directory in any Tenant (not necessarily the one where the application is deployed).
  • you can use the basic Azure Active Directory authentication and authorization features for free (without a Subscription attached to it), but as soon as you need advanced features (let's say MFA), then you need to buy one of the AAD Premium plans.
  • You do need a Subscription for deployment of apps
  • in this setup, a SSL Certificate is used for Mutual Client Authentication for both the Web App (between an F5 Load Balancer and the AppService), and the Web API (between Azure API Management and the backend API); for this to work you need to upload the certificate to API Management and the F5, as well as the AppServices. Also, remember to set the ClientCertEnabled boolean to True in the Resource Explorer of your AppServices. You most likely will setup also some Custom Host Name for your SSL Certificate in your AppServices.
  • in the AAD App Registration you setup Reply URLs, and OAuth2 Permissions to other Azure resources, as well as your App Roles in the App Manifest.
  • then you can assign the app to Users and Groups with specific Roles (AppRoleAssignment) in the Enterprise Applications section.
  • in API Management you can setup Users, Groups, Products and their User Subscriptions, and of course APIs with their SSL Certificate and Policies. You will need to create and configure an Authorization Server in the API Management Publisher Portal for each API that you intend to use through the Developer Portal.
  • also, you can see at the bottom of the image some APIs, such as ARM, APIM REST, and the two Graph APIs. These are used to automate all the above configuration activities that you can do manually in the Azure Portal.

Have fun!

Thursday, February 8, 2018

PowerHell



So here's another rant about Microsoft inconsistency and UN-usability of their products (sorry but I cannot hold this anymore)..

Since it's been introduced years ago, Microsoft Powershell looked very promising, with its CMDLet approach to reuse scripts, not only for sysadmin tasks, but for almost everything Microsoft.

There is only one problem with it (still nowadays): inconsistency.

The reason why I say this, is because most of the times (I am serious, most of them) I try to use Powershell, I get the famous error message:

The term '[Put your Powershell Module name here]' is not recognized as the name of a cmdlet, function, script file, or operable program.


That seems like a trivial error, so the next thing to do is to install the missing module.

This generally requires a few steps:

  • Figure out which module contains the command you are trying to run (not always straightforward).
  • Figure out which version of the module contains the exact command you try to run (different versions of Powershell (several available by now) might use slightly different syntax or parameters in commands...
  • Install the module (assuming you already installed all its dependencies, otherwise it won't install).


Besides the poor documentation about the different modules for each version, etc., the main issue is that even following the above steps meticolously, won't guarantee that you can finally run your command.

In fact, it turns out that doing a quick search on my machine for "Powershell", I have more than 10 folders containing Powershell modules...

C:\Program Files\WindowsPowerShell
C:\Program Files (x86)\WindowsPowerShell
C:\Program Files\Microsoft Azure Active Directory\Powershell
C:\Program Files\Microsoft Application Insights\Status Monitor\PowerShell
C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell
C:\Program Files (x86)\Microsoft SQL Server\140\Tools\PowerShell
C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\Web\Powershell
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\jgvvk40b.4cw\Starterkit\Extensions\powershell
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\4gsxi3yc.1xk\Starterkit\Extensions\powershell
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\uqch3mzq.qv5\Starterkit\Extensions\powershell
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\5rr1fg2l.ml1\Snippets\PowerShell

And of course each one is not visible to the others..

The obvious questions are: why each single (Microsoft) application installs yet another folder and cannot reuse a main one? And why 2 versions, 32 and 64 bit? And why the new installation can't take care of cleaning up/upgrading what's already installed?

Don't even bother answering, as nothing new here...

And on top of this PowerHell, I decided to skip my machine altogether, and rely on the "official" Microsoft Cloud Shell, which is basically Powershell baked into Azure.

Well, surprise surpise, even there, running the command "New-AzureADPolicy ..." returns the same old error:
The term 'New-AzureADPolicy' is not recognized as the name of a cmdlet, function, script file, or operable program.


You would think that Azure Active Directory CMDLets are installed in Azure.. wrong!

At least not for the AzureADPreview one..

Fair enough, so I ran the command: Install-module AzureADPreview

No luck, same error, even after installing the right module...

Now, eventually after lots of wasted time, I managed to run the command inside Powershell ISE (yet another different way of running Powershell scripts...), and have my policy created in Azure...

But still, go figure, Microsoft...

Tuesday, February 6, 2018

Microsoft Confusion...



I mentioned before about the Azure and Microsoft Graph APIs, and the fact that Microsoft recommends to use the latter one.

In fact, every page of documentation related to the Azure Graph API contains this disclaimer:

We strongly recommend that you use  Microsoft Graph  instead of Azure AD Graph API to access Azure Active Directory resources. Our development efforts are now concentrated on Microsoft Graph and no further enhancements are planned for Azure AD Graph API. There are a very limited number of scenarios for which Azure AD Graph API might still be appropriate; for more information, see the  Microsoft Graph or the Azure AD Graph

I can see this disclaimer in articles dated back as much as June 2017.

Still, as of now (February 2018), the Microsoft Graph API only supports a tiny bunch of operations in its official version 1.0, and a few more in its beta version, while most of the operations that are supported on the Azure Graph API (soo many), are still not supported at all in the Microsoft Graph API (beta or not).

And here is another annoying detail: even though the documentation states that
Microsoft Graph currently supports two versions: v1.0 and beta.
v1.0 includes generally available APIs. Use the v1.0 version for all production apps.
beta includes APIs that are currently in preview. Because we might introduce breaking changes to our beta APIs,
we recommend that you use the beta version only to test apps that are in development; do not use beta APIs in your production apps.
In reality the v1.0 is really so limited that it cannot be used, except for a few operations.

One simple example is listing your AAD App Registrations (you can easily reproduce this from the Graph Explorer); if you run this query:

GET https://graph.microsoft.com/beta/applications

You will see the list of AAD App Registrations correctly returned as JSON

However, if you switch to the v1.0:

GET https://graph.microsoft.com/v1.0/applications

This is what you get:
{
    "error": {
        "code": "BadRequest",
        "message": "Resource not found for the segment 'applications'.",
        "innerError": {
            "request-id": "6bf2f498-fe90-464c-9b83-084d4cc316f9",
            "date": "2018-02-06T13:20:25"
        }
    }
}
So, yeah, after almost a year of using the beta and "prodcution ready" v1.0, the Microsoft Graph API is still very unusable, and the Azure Graph API remain the only reliable API to query AAD programmatically...

And unfortunately, I have to admit, this is not surprising from Microsoft...
:(

Step-by-Step Guide to Fine-Tune an AI Model

Estimated reading time: ~ 8 minutes. Key Takeaways Fine-tuning enhances the performance of pre-trained AI models for specific tasks. Both Te...