Sunday, May 16, 2021

Azure/C#: Interview Questions (Restarting a Virtual Machine and Changing the Configuration)

Overview

As part of the interview process for a position, I was asked to write the following code samples 

  1. Write a C# program for restarting a VM
  2. Write a C# program for reading a specific node from XML
  3. Write a C# program for configurating an Azure VM

Write a C# program for Reading a Specific Node from XML

The obvious solution to such a piece of code is XPATH in this case following the theme of managing Azure Virtual Machines.

The boldface code in the following class selects a specific node:

using System.Collections.Generic;
using System.Xml;

namespace AzureVMChallenge
{  
  class VirtualMachineManager
  {
    public const string VirtualMachineElement = 
                          "VirtualMachine";

    public const string ResourceGroupNameElement = 
                          "ResourceGroupName";

    public const string VirtualMachineNameElement = 
                          "VirtualMachineName";


    /* Filename XML format (too simple):
<VirtualMachine>
  <ResourceGroupName>resource group name</ResourceGroupName>
  <VirtualMachineName>virtual machine name</VirtualMachineName>
</VirtualMachine>
    */
    public static VirtualMachineIdentity 
                    GetVirtualMachine(string filename) 
    {
      var document = new XmlDocument();

      document.Load(filename);

      var virtualMachine = document.SelectSingleNode(
                  VirtualMachineElement);

      return new VirtualMachineIdentity(
        virtualMachine.SelectSingleNode(
          ResourceGroupNameElement).InnerText, 
        virtualMachine.SelectSingleNode(
          VirtualMachineNameElement).InnerText);
    }
  }
}

Write a C# program for Reading a Multiple Nodes from XML

A more interesting example is an XPATH query that select multiple nodes based off of specific attribute values

The code in boldface below selects multiple nodes:

using System.Collections.Generic;
using System.Xml;

namespace AzureVMChallenge
{  
  class VirtualMachineManager
  {
    /* Filename XML format (more interesting XPATH):
      <VirtualMachines>
        <VirtualMachine Action="Restart" IsActive="False">
          <ResourceGroupName>AzureChellenge</ResourceGroupName>
          <VirtualMachineName>AzureChellengeVM09</VirtualMachineName>
        </VirtualMachine>
        <VirtualMachine Action="Stop" IsActive="False">
          <ResourceGroupName>AzureChellenge</ResourceGroupName>
          <VirtualMachineName>AzureChellengeVM01</VirtualMachineName>
        </VirtualMachine>
        <VirtualMachine Action="Start" IsActive="True">
          <ResourceGroupName>AzureChellenge</ResourceGroupName>
          <VirtualMachineName>AzureChellengeVM01</VirtualMachineName>
        </VirtualMachine>
        <VirtualMachine Action="Restart" IsActive="False">
          <ResourceGroupName>AzureChellenge</ResourceGroupName>
          <VirtualMachineName>AzureChellengeVM08</VirtualMachineName>
        </VirtualMachine>
        <VirtualMachine Action="Restart" IsActive="True">
          <ResourceGroupName>AzureChellenge</ResourceGroupName>
          <VirtualMachineName>AzureChellengeVM00</VirtualMachineName>
        </VirtualMachine>
      </VirtualMachines>
     */
    public static IEnumerable<VirtualMachineIdentity> 
                    GetVirtualMachines(string filename) 
    {
      var document = new XmlDocument();

      document.Load(filename);

      var virtualMachinesNodes = 
        document.SelectNodes(
          "/VirtualMachines/VirtualMachine[@Action='Restart' and " +            "@IsActive='True']");
      var virtualMachines = new List<VirtualMachineIdentity>();

      foreach (XmlNode virtualMachinesNode in virtualMachinesNodes) {
        virtualMachines.Add(new VirtualMachineIdentity(
          virtualMachinesNode.SelectSingleNode(
            ResourceGroupNameElement).InnerText, 
          virtualMachinesNode.SelectSingleNode(
            VirtualMachineNameElement).InnerText));
      }

      return virtualMachines;
    }
  }
}

Write a C# program for Restarting a VM

A program, driven by XML files, that restarts VMs is as follows with the actual Azure credential setup and virtual machine restart code demarcated in boldface:

using System;

using Microsoft.Azure.Management.Compute.Fluent;
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;

namespace AzureVMChallenge
{  
  class Program
  {
    private const string EnvVarAzureChallengeSubscriptionId = 
                           "AZURECHALLENGE_SUBSCRIPTION_ID";

    private const string EnvVarAzureChallengeTenantId = 
                           "AZURECHALLENGE_TENANT_ID";

    private const string EnvVarAzureChallengeClientId = 
                           "AZURECHALLENGE_CLIENT_ID";

    private const string EnvVarAzureChallengeClientSecret = 
                           "AZURECHALLENGE_CLIENT_SECRET";

    public static void ManageVirtualMachine(
                IAzure azure, 
                VirtualMachineIdentity virtualMachineIdentity)
    {
      var vm = azure.VirtualMachines.GetByResourceGroup(
            virtualMachineIdentity.ResourceGroupName, 
            virtualMachineIdentity.VirtualMachineName);

      if  (PowerState.Running == vm.PowerState)
      {
        vm.Restart();
      }

      else 
      {
        Console.WriteLine(
          $"VM, {virtualMachineIdentity.VirtualMachineName}, " + 
           "not running so cannot restart.");        
      }
    }

    private static IAzure GetAzure() {
      var subscriptionId = Environment.GetEnvironmentVariable(
                  EnvVarAzureChallengeSubscriptionId, 
                  EnvironmentVariableTarget.User);
      var tenantId = Environment.GetEnvironmentVariable(
              EnvVarAzureChallengeTenantId, 
              EnvironmentVariableTarget.User);
      var clientId = Environment.GetEnvironmentVariable(
                EnvVarAzureChallengeClientId, 
                EnvironmentVariableTarget.User);
      var clientSecret = Environment.GetEnvironmentVariable(
                  EnvVarAzureChallengeClientSecret, 
                  EnvironmentVariableTarget.User);
      var credentials = new AzureCredentialsFactory().
                FromServicePrincipal(
                  clientId, 
                  clientSecret, 
                  tenantId, 
                  AzureEnvironment.AzureGlobalCloud);

      return Azure.Authenticate(credentials).
                  WithSubscription(subscriptionId);
    }

    private static void SimpleVMRestart()
    {
      var azure = GetAzure();
      var virtualMachineIdentity = 
          VirtualMachineManager.GetVirtualMachine(
              "TargetVirualMachine.xml");

      ManageVirtualMachine(azure, virtualMachineIdentity);
    }

    private static void MoreInterestiongRestart()
    {
      var azure = GetAzure();

      foreach (var virtualMachineIdentity in 
                 VirtualMachineManager.GetVirtualMachines(
                   "TargetVirualMachines.xml"))
      {
        ManageVirtualMachine(azure, virtualMachineIdentity);
      }
    }

    static void Main(string[] args)
    {
      try
      {
        SimpleVMRestart();
        MoreInterestiongRestart();
      }

      catch (Exception ex)
      {
        Console.WriteLine($"{ex.Message}");
        Console.WriteLine($"{ex.StackTrace}");
      }
    }
  }
}

Write a C# Program for Configuring an Azure VM

The requirement is ambiguous. It may mean modifying the configuration of the VM in Azure and it may be mean modifying the guest OS.

C# Program to Change the VM Size

An example of configuring an Azure VM by changing its size is as follows where the code to change the VM's size is identified by boldface:

    private static void ResizeVMUser(
              IAzure azure, 
              VirtualMachineIdentity virtualMachineIdentity) 
    {
      var vm = azure.VirtualMachines.GetByResourceGroup(
            virtualMachineIdentity.ResourceGroupName, 
            virtualMachineIdentity.VirtualMachineName);

      // Toggle size to show changing VM configuration
      var size =
         (VirtualMachineSizeTypes.StandardD2sV3 == 
                  vm.Inner.HardwareProfile.VmSize) ?
            VirtualMachineSizeTypes.StandardD4sV3 : 
            VirtualMachineSizeTypes.StandardD2sV3;

      vm.Inner.HardwareProfile.VmSize = size;
      vm.Inner.Validate();
      vm.Update()
        .Apply();
    }

    private static void ResizeVMExample()
    {
      var azure = GetAzure();
      var virtualMachineIdentity = 
          VirtualMachineManager.GetVirtualMachine(
            "TargetVirualMachine.xml");

      ResizeVMUser(azure, virtualMachineIdentity);      
    }

C# Program to Change the Guest OS

The following snippet adds a user to Azure VM running Linux where the code in boldface actually accesses Azure and adds the user:

    private const string LinuxVmAccessExtensionName = 
                           "VMAccessForLinux";

    private const string LinuxVmAccessExtensionPublisherName = 
                           "Microsoft.OSTCExtensions";

    private const string LinuxVmAccessExtensionTypeName = 
                           "VMAccessForLinux";

    private const string LinuxVmAccessExtensionVersionName = "1.4";

    private const string SettingUsernameName = "username";

    private const string SettingPasswordName = "password";

    private const string SettingExpirationName = "expiration";

    private static string ExpiratioNDateFormat(DateTime expiration)
    {
      return expiration.ToString("yyyy-MM-dd");
    }

    private static void AddVMUser(
                IAzure azure, 
                VirtualMachineIdentity virtualMachineIdentity,
                string newUsername,
                string newPassword,
                DateTime userExpiration) 
    {
      var vm = azure.VirtualMachines.GetByResourceGroup(
            virtualMachineIdentity.ResourceGroupName, 
            virtualMachineIdentity.VirtualMachineName);

      if (vm.ListExtensions().ContainsKey(
                          LinuxVmAccessExtensionName))
      {
        vm.Update().UpdateExtension(LinuxVmAccessExtensionName
              .WithProtectedSetting(SettingUsernameName, newUsername)
              .WithProtectedSetting(SettingPasswordName, newPassword)
              .WithProtectedSetting(
                  SettingExpirationName, 
                  ExpiratioNDateFormat(userExpiration))
            .Parent()
            .Apply();
      }

      else
      {                        
        vm.Update()
          .DefineNewExtension(LinuxVmAccessExtensionName)
              .WithPublisher(LinuxVmAccessExtensionPublisherName)
              .WithType(LinuxVmAccessExtensionTypeName)
              .WithVersion(LinuxVmAccessExtensionVersionName)
              .WithProtectedSetting(SettingUsernameName, newUsername)
              .WithProtectedSetting(SettingPasswordName, newPassword)
              .WithProtectedSetting(
                 SettingExpirationName, 
                 ExpiratioNDateFormat(userExpiration))
              .Attach()
            .Apply();
      }
    }

    private static void ConfigureVMExample()
    {
      var azure = GetAzure();
      var virtualMachineIdentity = 
          VirtualMachineManager
             .GetVirtualMachine("TargetVirualMachine.xml");
      var userExpiration = DateTime.Now.AddMonths(3);

      AddVMUser(
        azure, 
        virtualMachineIdentity, 
        "anyuser02", 
        "anypassword", 
        userExpiration);      
    }