Friday 29 July 2011

Load Balancing in MS Dynamics CRM

In its infinite wisdom Microsoft decided to make my life harder by making it MS Dynamics CRM not work out of the box with a load balancer and I'm not talking about Microsoft software based network load balancing although it would apply too, if memory servers right, but a proper hardware based solution, which if again, memory serves me right, it's a Cisco 11506, but that is by the by.

In a normal load balancing situation, you would want a DNS entry for your load balancer's VIP and you would direct your clients to that VIP, so far so good. The problem is that there is a few pesky rows in the DeploymentProperties table in the MSCRM_CONFIG database
ADSdkRootDomain
ADWebApplicationRootDomain
The NvarCharColumn for these rows is, by default, set to the first server in a deployment, which is all well and good if you have a single server in your deployment, but the minute you add another server you are creating a single point of failure, because if the first server goes down, down goes your application. In actual fact it won't come down until the app pool gets recycled if I remember correctly. It will also refuse to start if the first server is down.

In the three+ years since we encountered this problem, it seems that this is all now clearly documented, I should've done a search in google before I wrote all this, oh well.

Note that some people have reported that this setting also needs changing, I guess it depends on whether or not you are using asynchronous plugins, which we don't.
AsyncSdkRootDomain

C# Interview questions -- part 2

Following from my previous post, the last question I had was a MS Dynamics CRM question, which I did not get around to do. Essentially the problem was to create an entity called maet (team backwards) and another one called tluser (result backwards), you can see where this is going, to keep track of match results.
  • Maet had to have a name attribute only, the name of the maet.
  • Tluser had to display two maets and had to have a score attribute for each team and a result attribute (actually, I think it was a tluser attribute, but you get the picture).
The final task was to create a plugin, which I have forgotten what it was meant to do.

The only "tricky" thing here is what relationships are needed. Each tluser needs to show in its form two maets, thus we need to create two N:1 relationships between tluser and maet, in other words each maet can belong to N tlusers, which makes sense if you think about it. Each team is going to have several results, for each match it plays. As a rule of thumb the 1 entity shows up in the form of N entity (this is different for native N:N relationships, but I won't go into them).


Note that these relationships show as 1:N in the maet entity, as below.

At any rate, this is what I would have done, had I had the time to do it in. (Yes, I know it can be prettified, but that takes time).


There are loads of pitfalls with the way this is done, e.g. you could set both maets to be the same, or the result to field to be meaningless, but with a bit of time all of this can be sorted.

Tuesday 26 July 2011

C# Interview questions -- part 1

I had a couple of interviews a few weeks ago and I thought I would share the technical questions that I was asked here.

I was not really impressed at being asked theoretical questions about object oriented programming, perhaps because I struggled with the answers, not because I did not know what the concepts were, or how to use them, but because I couldn't recall what they were called, I'm looking at you encapsulation. You might be reading this and thinking he doesn't have a clue, and you'd be right, but think about it like this. You don't need to know what method overloading is actually called, you just need to know that can have methods with the same name as long as you change something , e.g. number or order of arguments or return type, at any rate. Rant over, let's get on with it.

Can you explain what a class is? Class
Can you explain what encapsulation is?  Encapsulation
Can you explain what inheritance is? Inheritance
Can you explain what a design pattern is? Design Pattern
Can you explain what Database normalization is? Database normalization

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz". This is straight from this post.

Write a program that prints the reverses the words for the following sentence "the quick brown fox jumps over the lazy dog".

Write a program that prints the fibonacci sequence (i.e. 1,1,2,3,5,8 ...):
  1. Any method is available (i.e. use recursion)
  2. Do not use recursion.
I know it is possible to do step 2, I have done it, see below, but in a interview situation, I simply could not do it.

   1         public static long fibiter(long number)
   2         {
   3             long fTerm = 0, sTerm = 1;
   4                         
   5             for (int i = 0; i < number; ++i)
   6             {
   7                 long temp = fTerm;
   8                 fTerm = sTerm;
   9                 sTerm += temp;
  10             }
  11                         
  12             return fTerm;
  13 
  14         }

Friday 22 July 2011

Pure Lunacy or Sheer Genius, You Decide.

I've been doing a code review for a new Microsoft Dynamics CRM 4.0 based application that is coming in and today I found the method below:


   1 
   2     [WebMethod]
   3     public string Engaged(string oId, string strValues)
   4     {
   5         try
   6         {
   7             LogEntry("TRACE", "WebService : Engaged", String.Format("Start: Call oId: {0}, strValues: {1}", oId, strValues));
   8             call outcall = new call();
   9 
  10             CrmBoolean boolean = new CrmBoolean();
  11            
  12             String engagedValue = strValues.Substring((strValues.Length - 3), 2); //Request.QueryString["oId"].Substring((Request.QueryString["oId"].Length - 2), 2).ToString();
  13             if (engagedValue == "No")
  14             {
  15                 boolean.Value = true;
  16             }
  17             else
  18             {
  19                 boolean.Value = false;
  20             }
  21             LogEntry("TRACE", "WebService : Engaged", string.Format("1. Set Call Engaged Status to {0}.",boolean.Value));
  22             outcall.engaged = boolean;
  23 
  24             Key key = new Key();
  25             key.Value = new Guid(oId); 
  26             outcall.callid = key;
  27 
  28             LogEntry("TRACE", "WebService : Engaged", "2. Update Call record");
  29             crmservice.Update(outcall);
  30 
  31              LogEntry("TRACE", "WebService : Engaged", "Return: All Done.");
  32 
  33             return "";
  34         }
  35         
  36         catch (Exception e)
  37         {
  38             LogEntry("ERROR", "WebService : Engaged", string.Format("Error in Engaged Engaged.  Exception {0}.", e.ToString()));
  39 
  40             return e.ToString();
  41         }
  42     }

This method gets invoked with a isv button on the home page. Call here is actually a custom entity.  oId contains the Call guid and strValues, I was not prepared for this, contains a string that is generated like this:

   1 if (document.all.crmGrid.InnerGrid.SelectedRecords.length > 0)
   2 {
   3 var url = "/WebService.asmx/Engaged";
   4 var oXmlHTTP = new ActiveXObject("Msxml2.XMLHTTP");
   5 oXmlHTTP.Open("POST", url, false);
   6 oXmlHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
   7 oXmlHTTP.Send("oId=" + document.all['crmGrid'].InnerGrid.SelectedRecords[0][0] +"&strValues=" + document.all['crmGrid'].InnerGrid.SelectedRecords[0][3].innerText);
   8 document.getElementById('crmGrid').Refresh();
   9 }

Oh, yes we are screen scraping, in our own App, for which we have full control over everything, a perfectly good SDK and is actually all hosted within the same datacentre and inside a secure network, accessed by a single company.

You might be inclined to think that this is a response to a very slow network and that by skipping a call to the database to get the Call instance that we are updating, we might make the user experience better. I would be willing to concede on this point, if it wasn't for the rest of the code that makes calls to the database with careless abandon.

I won't even go into returning an empty string, how the entity has different views (some of which don't contain the engaged attribute) or the fact that if it fails the user is not notified in any way.

So what do you think, Pure Lunacy or Sheer Genius?

Monday 18 July 2011

NTP -- Synchronize time using other NTP peers

Let's start by configuring an NTP server. You can install the ntp server with:
yum install ntp -y
Make sure that it starts on system start up:
chkconfig ntp on
Open the firewall and save the changes:
 iptables -I INPUT -p udp --dport ntp -j ACCEPT; service iptables save
Edit the ntp config file /etc/ntp.conf and add the following line:
restrict 10.168.20.0 mask 255.255.255.0 nomodify notrap
This will allow any client in the 10.168.20.0 network to get its time from the ntp server, except that it does not quite do it for me, as I don't have an internet connection. This is because a local server is way down in the pecking order or stratum, so a few extra steps are required:
echo "10.168.20.227" >> /etc/ntp/step-tickers
echo "10.168.20.227" >> /etc/ntp/ntpservers
Assuming that 10.168.20.227 is the ip address of your ntp server. You can now start your ntp server with:
service ntpd start
Interestingly, there are no SELinux settings related to ntp and you can block hosts by using iptables rules.

In order to configure a client to use this ntp server, simply add the following line to the ntp config file of your server:
server 10.168.20.227
Set the ntp daemon to start at boot time and start the service:
chkconfig ntpd on
service ntpd start
You can now use the following command to check the configuration is working:
ntpq -p
which should have a result like this:

          remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
 10.168.20.227   LOCAL(0)        11 u   45   64    1    0.479  263675.   0.000

Sunday 17 July 2011

SSH -- Configure additional options described in documentation

How long is a piece of string?

All I can say about this objective, is that you had better familiarize yourself with the SSH daemon config file (/etc/ssh/sshd_config) and the manual pages (man sshd & man sshd_config) as well as the ssh client config (/etc/ssh/ssh_confg) and manual pages (man ssh & man ssh_config).

Saturday 16 July 2011

SSH -- Configure key-based authentication

This is actually a fairly simple objective. The default configuration is to accept key-based authentication, note this line on the /etc/ssh/sshd_config file:
#PubkeyAuthentication yes
Although the line is commented out, this is actually the default and as such does no need to be explicitly stated, if you wanted to prevent key based authentication, just add this line:
PubkeyAuthentication no
At any rate back to the objective. On the client, issue the following command, and follow the instructions, to generate a key:
ssh-keygen
Note that you don't actually need to add a passphrase, just press enter. This will allow you to login without being prompted for a passprhase.

The last step is to copy the public key that you have just generated to the server you want to login to:
ssh-copy-id  user@<servername>
That's it, if you did not provide a passphrase, you should be able to login with:
ssh user@<servername>
Note, that both ssh-keygen and ssh-copy-id have several options and that you should study them to see what they do.

I guess that in the exam you could be asked to install ssh, even if it does get installed by default. At any rate, just issue the following command:
yum install openssh-server -y
You should then make sure that it is set to run at boot time:
chkconfig sshd on
You can allow ssh traffic through by opening port 22:
iptables -I INPUT -p tcp --dport 22 -j ACCEPT; service iptables save
Depending on your configuration, you might need to change SELinux settings. You can check the SELinux settings like this:
getsebool -a | grep ssh
Finally, you can limit the users that can login by using the DenyUsers directive in the  /etc/ssh/sshd_config file like so:
DenyUsers naughtyuser
Remember to restart the daemon after any changes:
service sshd restart
If you want to prevent hosts from accessing SSH, you can do it by using iptables rules, e.g.:
iptables -I INPUT -p tcp --dport 22 -s 10.168.20.233 -j DROP; service iptables save
I think this pretty much covers this objective.

SMTP -- Configure an MTA to forward (relay) email through a smart host

This is actually quite simple. You need to modify the following line in the postfix config file (/etc/postfix/main.cf):
relayhost = 10.168.20.227
This will relay emails to host 10.168.20.227, which means that this host needs to be configured as Postfix server, see my previous post here.

Note that you still need to change other configuration settings as detailed in my previous post.

Sunday 10 July 2011

Nested Custom Configuration Collections in C#

I've working in a little project to automate the creation of SSH tunnels, so that we don't have to launch a Putty session with all the right tunnels. Lazy? perhaps, but there is nothing like laziness to spur novel solutions, not that this is one of these situations.
At any rate, back to the problem. With a multitude of tunnels and potentially SSH servers on the other side of the NAT divide, I wanted a configuration section like this:

   1 <TunnelSection>
   2     <host SSHServerHostname="tsg" username="user" SSHport="22" password="" privatekey="" privatekeypassphrase="">
   3       <tunnels>
   4         <tunnel name="tfs" localport="8081"  remoteport="8080" destinationserver="tfs.dev.com"  />
   5         <tunnel name="sql" localport="14331"  remoteport="1433" destinationserver="sql2008.dev.com"  />
   6         <tunnel name="crmapp" localport="81"  remoteport="80" destinationserver="crmapp.dev.com"  />
   7       </tunnels>
   8     </host>
   9     <host SSHServerHostname="blade16" username="root" SSHport="22"  password="" privatekey="" privatekeypassphrase="">
  10     <tunnels>
  11       <tunnel name="vnc" localport="5902"  remoteport="5902" destinationserver="blade1.dev.com" />
  12     </tunnels>
  13     </host>
  14 </TunnelSection>

The example in the microsoft website does not quite do what I wanted to do, although, I suspect that I might have been able to twist it to get it working. At any rate, here is the code:

   1 using System;
   2 using System.Configuration;
   3 
   4 
   5 namespace SSHTunnelWF
   6 {
   7 
   8 
   9   public class TunnelSection : ConfigurationSection
  10     {
  11 
  12         [ConfigurationProperty("", IsDefaultCollection = true)]  
  13         public HostCollection Tunnels
  14         {
  15             get
  16             {
  17                 HostCollection hostCollection = (HostCollection)base[""];
  18 
  19                 return hostCollection;                
  20 
  21             }
  22         }
  23 
  24     }
  25 
  26 
  27     public class HostCollection : ConfigurationElementCollection
  28     {
  29         public HostCollection()
  30         {
  31             HostConfigElement details = (HostConfigElement)CreateNewElement();
  32             if (details.SSHServerHostname != "")
  33             {
  34                 Add(details);
  35             }
  36         }
  37 
  38         public override ConfigurationElementCollectionType CollectionType
  39         {
  40             get
  41             {
  42                 return ConfigurationElementCollectionType.BasicMap;
  43             }
  44         }
  45 
  46         protected override ConfigurationElement CreateNewElement()
  47         {
  48             return new HostConfigElement();
  49         }
  50 
  51         protected override Object GetElementKey(ConfigurationElement element)
  52         {
  53             return ((HostConfigElement)element).SSHServerHostname;
  54         }
  55 
  56         public HostConfigElement this[int index]
  57         {
  58             get
  59             {
  60                 return (HostConfigElement)BaseGet(index);
  61             }
  62             set
  63             {
  64                 if (BaseGet(index) != null)
  65                 {
  66                     BaseRemoveAt(index);
  67                 }
  68                 BaseAdd(index, value);
  69             }
  70         }
  71 
  72         new public HostConfigElement this[string name]
  73         {
  74             get
  75             {
  76                 return (HostConfigElement)BaseGet(name);
  77             }
  78         }
  79 
  80         public int IndexOf(HostConfigElement details)
  81         {
  82             return BaseIndexOf(details);
  83         }
  84 
  85         public void Add(HostConfigElement details)
  86         {
  87             BaseAdd(details);
  88         }
  89         protected override void BaseAdd(ConfigurationElement element)
  90         {
  91             BaseAdd(element, false);
  92         }
  93 
  94         public void Remove(HostConfigElement details)
  95         {
  96             if (BaseIndexOf(details) >= 0)
  97                 BaseRemove(details.SSHServerHostname);
  98         }
  99 
 100         public void RemoveAt(int index)
 101         {
 102             BaseRemoveAt(index);
 103         }
 104 
 105         public void Remove(string name)
 106         {
 107             BaseRemove(name);
 108         }
 109 
 110         public void Clear()
 111         {
 112             BaseClear();
 113         }
 114 
 115         protected override string ElementName
 116         {
 117             get { return "host"; }
 118         }
 119     }
 120 
 121 
 122     public class HostConfigElement:ConfigurationElement
 123     {
 124 
 125         [ConfigurationProperty("SSHServerHostname", IsRequired = true, IsKey = true)]
 126         [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
 127         public string SSHServerHostname
 128         {
 129             get { return (string)this["SSHServerHostname"]; }
 130             set { this["SSHServerHostname"] = value; }
 131         }
 132 
 133         [ConfigurationProperty("username", IsRequired = true)]
 134         [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
 135         public string Username
 136         {
 137             get { return (string)this["username"]; }
 138             set { this["username"] = value; }
 139         }
 140         [ConfigurationProperty("SSHport", IsRequired = true, DefaultValue = 22)]
 141         [IntegerValidator(MinValue = 1, MaxValue = 65536)]
 142         public int SSHPort
 143         {
 144             get { return (int)this["SSHport"]; }
 145             set { this["SSHport"] = value; }
 146         }
 147 
 148        [ConfigurationProperty("password", IsRequired = false)]
 149         public string Password
 150         {
 151             get { return (string)this["password"]; }
 152             set { this["password"] = value; }
 153         }
 154 
 155         [ConfigurationProperty("privatekey", IsRequired = false)]
 156         public string Privatekey
 157         {
 158             get { return (string)this["privatekey"]; }
 159             set { this["privatekey"] = value; }
 160         }
 161 
 162         [ConfigurationProperty("privatekeypassphrase", IsRequired = false)]
 163         public string Privatekeypassphrase
 164         {
 165             get { return (string)this["privatekeypassphrase"]; }
 166             set { this["privatekeypassphrase"] = value; }
 167         }
 168 
 169         [ConfigurationProperty("tunnels", IsDefaultCollection = false)]
 170         public TunnelCollection Tunnels
 171         {
 172             get { return (TunnelCollection)base["tunnels"]; }
 173             
 174         }
 175 
 176     }
 177 
 178 
 179     public class TunnelCollection : ConfigurationElementCollection
 180     {
 181 
 182         public new TunnelConfigElement this[string name]
 183         {
 184             get
 185             {
 186                 if (IndexOf(name) < 0) return null;
 187 
 188                 return (TunnelConfigElement)BaseGet(name);
 189             }
 190         }
 191 
 192         public TunnelConfigElement this[int index]
 193         {
 194             get { return (TunnelConfigElement)BaseGet(index); }
 195         }
 196 
 197         public int IndexOf(string name)
 198         {
 199             name = name.ToLower();
 200 
 201             for (int idx = 0; idx < base.Count; idx++)
 202             {
 203                 if (this[idx].Name.ToLower() == name)
 204                     return idx;
 205             }
 206             return -1;
 207         }
 208 
 209         public override ConfigurationElementCollectionType CollectionType
 210         {
 211             get { return ConfigurationElementCollectionType.BasicMap; }
 212         }
 213 
 214         protected override ConfigurationElement CreateNewElement()
 215         {
 216             return new TunnelConfigElement();
 217         }
 218 
 219         protected override object GetElementKey(ConfigurationElement element)
 220         {
 221             return ((TunnelConfigElement)element).Name;
 222         }
 223 
 224         protected override string ElementName
 225         {
 226             get { return "tunnel"; }
 227         }
 228 
 229     }
 230 
 231 
 232     public class TunnelConfigElement : ConfigurationElement
 233     {        
 234 
 235         public TunnelConfigElement()
 236     {
 237     }
 238 
 239         public TunnelConfigElement(string name, int localport, int remoteport, string destinationserver)
 240         {
 241            
 242             this.DestinationServer = destinationserver;
 243             this.RemotePort = remoteport;
 244             this.LocalPort = localport;            
 245             this.Name = name;
 246         }
 247 
 248         [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]       
 249         public string Name
 250         {
 251             get { return (string)this["name"]; }
 252             set { this["name"] = value; }
 253         }        
 254 
 255         [ConfigurationProperty("localport", IsRequired = true, DefaultValue =1)]
 256         [IntegerValidator(MinValue = 1, MaxValue = 65536)]
 257         public int LocalPort
 258         {
 259             get { return (int)this["localport"]; }
 260             set { this["localport"] = value; }
 261         }
 262 
 263         [ConfigurationProperty("remoteport", IsRequired = true, DefaultValue =1)]
 264         [IntegerValidator(MinValue = 1, MaxValue = 65536)]
 265         public int RemotePort
 266         {
 267             get { return (int)this["remoteport"]; }
 268             set { this["remoteport"] = value; }
 269         }
 270 
 271         [ConfigurationProperty("destinationserver", IsRequired = true)]
 272         [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
 273         public string DestinationServer
 274         {
 275             get { return (string)this["destinationserver"]; }
 276             set { this["destinationserver"] = value; }
 277         }
 278 
 279     }
 280 
 281 }

The nesting of the collections is achieved by defining a ConfigurationProperty as a class that inherits from the ConfigurationElementCollection class, TunnelCollection in this case, as part of HostConfigElement, which contains all the configuration properties required by the host. This could be repeated ad infinitum by adding a new ConfigurationProperties to the TunnelConfigElement. Make sure that the ElementName of the collection is overridden and matches your config file, in this case host for the HostCollection and tunnel for the TunnelCollection.

Here is the configuration file. Note that the type needs to reference the ConfigSection class, SSHTunnelWF.TunnelSection in this case, and the assembly name SSHTunnelWF:

   1 <?xml version="1.0"?>
   2 <configuration>
   3   <configSections>
   4     <section name="TunnelSection" type="SSHTunnelWF.TunnelSection,SSHTunnelWF" />
   5   </configSections>
   6   <startup>
   7     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
   8   </startup>
   9   <TunnelSection>
  10     <host SSHServerHostname="tsg.edssdn.net" username="user" SSHport="22" password="pass" privatekey="" privatekeypassphrase="">
  11       <tunnels>
  12         <tunnel name="tfs" localport="8081"  remoteport="8080" destinationserver="tfs2010.dev.com"  />
  13         <tunnel name="sql" localport="14331"  remoteport="1433" destinationserver="sql2008.dev.com"  />
  14         <tunnel name="crm2011app" localport="81"  remoteport="80" destinationserver="crm2011betaapp.dev.com"  />
  15       </tunnels>
  16     </host>
  17     <host SSHServerHostname="blade16" username="root" SSHport="22"  password="pass" privatekey="" privatekeypassphrase="">
  18      <tunnels>
  19        <tunnel name="vnc" localport="5902"  remoteport="5902" destinationserver="blade1.dev.com" />
  20      </tunnels>
  21     </host>
  22   </TunnelSection>
  23 </configuration>


Finally, you can access the contents of this custom configuration section like this:

TunnelSection tunnels = ConfigurationManager.GetSection("TunnelSection") as TunnelSection