CoreOS + Etcd + Flannel = Pretty Cool

This is just a brief update on what I’m looking into.

I have a cluster, how do containers on different machines talk?

An easy solution is Flannel + Etcd. Here’s a simple example which will work on two machines. (I suggest using coreos-vagrant to create a two node cluster with flannel. Here’s my user-data and config.rb.)

First Machine:

CID=$(docker run -d alpine /bin/sh -c "while true; do sleep 1; done") && etcdctl set /server $(docker inspect --format '{{.NetworkSettings.IPAddress}}' $CID) && docker exec -it $CID /usr/bin/nc -l -p 9999 && docker stop --time=0 $CID && docker rm $CID

Note: Exit with Ctrl+P Control+Q (^P^Q), doing a Control+C (^C) will not stop and remove the docker container.

Second Machine

docker run --rm -it --add-host server:$(etcdctl get /server) alpine /usr/bin/nc server 9999

Now you should be able to chat between the two machines!

Meh? What back magic is this?

I’m simply using etcd to store the flannel IP of the server in the value of the /server key. On another machine we use that IP to attach netcat. The black magic is in flannel’s overlay network and etcd’s distributed key/value store.

Breaking apart the commands to the server:

CID=$(docker run -d --name server alpine /bin/sh -c "while true; do sleep 1; done")

This command sets up a docker container running the shell with a script that just sleeps. Simply running netcat doesn’t seem to give a good two-way connection in this example. The $CID part stores the docker container’s ID in a variable so I can use it later. I could have used the –name option to give a sensible name, but docker inspect doesn’t seem to accept a name.

etcdctl set /server $(docker inspect --format '{{.NetworkSettings.IPAddress}}' $CID)

Here I am setting a key in etcd so that another machine in fleet (which could be on the other side of the world) can access the flannel IP address of this server. This command makes use of the $CID variable set before (since it is random) and docker inspect’s format command to remove the formatting.

docker exec -it $CID /usr/bin/nc -l -p 9999

Executing netcat here instead of in the docker run phase is just for demonstration, normally this part would be done there. Because we are people who like to see things work, executing this command allows us to interact with the netcat server.

docker stop --time=0 $CID

Stopping the container with a zero time is analogous to killing it.

docker rm $CID

Get rid of that hanging container!

Lesson learned:

  1. Flannel works nicely
  2. Etcd is great
  3. Netcat is amazing
  4. Alpine is a wonderful ~5MiB distro for docker containers
  5. This stuff really needs a web UI! (If you know of a good one, leave a comment and I’ll blog about it.)

Roslyn… Excellent!

RoslynAwesome

 

(Blog post and mini tutorial)

So Microsoft is working on this amazing new compiler for C# and VB.NET code named “Rosyln” which will make you, your dog, your parents, and the world happy. Why? Is it faster or stronger or something? Well, yes but that’s pretty normal for new software. The news here is compiler APIs. Let me say it again…

Compiler APIs.

Okay, let that sink in. What it is useful for? Everything from re-factoring code on the fly to powering the new intellisense in Visual Studio 2013 to whatever.

Show me

Oh, so you want an example, eh? Here’s your example: Suppose you need to re-factor some code and you’re using a regular expression to parse and replace code fragments. But what if you run into some newer code that uses a “var” for example. What will you do? You could come up with a massively complex way of handling that scenario or you could use Roslyn. Because Roslyn exposes the compiler as an API you have access to the semantic tree and all the metadata that tree exposes.

So, ye of little faith, here’s the code already:

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Compilers.CSharp;
 
namespace Roslyn
{
    class Program
    {
        static void Main(string[] args)
        {
            SyntaxTree tree = SyntaxTree.ParseText(
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
 
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            var helloWorld = ""Hello, World!"";
            string xxxx = ""Happy!"";
            Console.WriteLine(helloWorld);
        }
    }
}");
 
            var compilation = Compilation.Create("Program",
                new CompilationOptions(Compilers.OutputKind.ConsoleApplication),
                new SyntaxTree[] { tree });
 
            var root = tree.GetRoot();
            var newRoot = new Rewriter(compilation.GetSemanticModel(tree)).Visit(root);
            var result = newRoot.ToFullString();
        }
    }
 
    class Rewriter : SyntaxRewriter
    {
        private readonly SemanticModel _semanticModel;
 
        public Rewriter(SemanticModel semanticModel)
        {
            _semanticModel = semanticModel;
        }
 
        public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
		{
			if (node.Declaration.Variables.Count > 1) return node;
			if (node.Declaration.Variables[0].Initializer == null) return node;
 
			if (node.Declaration.Type.IsVar == true)
			{
				var info = _semanticModel.GetTypeInfo(node.Declaration.Type);
 
				var result = node.WithDeclaration(Syntax.VariableDeclaration(
						Syntax.ParseTypeName(info.ConvertedType.Name)
							.WithLeadingTrivia(node.Declaration.Type.GetLeadingTrivia())
							.WithTrailingTrivia(node.Declaration.Type.GetTrailingTrivia())
						).WithVariables(node.Declaration.Variables));
 
				return result;
			}
 
			return node;
		}
    }
}

Yes it’s a bit complicated but really look at it and you’ll see how amazingly simple it really is. This needs not only .NET 4.5 (Visual Studio 2012 or higher) but also the Roslyn CTP which you can get from NuGet very easily but running this in your NuGet Package Manager Console (you do have that turned on, right?)

NuGetPackageManager NuGetInstallRoslyn

Now compile, run, and examine the contents of result. You will find the var has been replaced by “String” which is what it should be. If you change the value of the “helloWorld” var in the sample code to a 1, and re-run you should see the var replaced by an Int32.

Awesome. Enjoy your trek into the wonderful world of Roslyn.

Find an iPhone with C#

FindMyWife

 

This has happened to me a few times, has it happened to you? My wife asks, nay orders me, to call her at a certain time and invariably out of politeness has switched her phone to vibrate and cannot hear me call no matter how many times I try. So she has the really nice iPhone 4 and I have whatever my place of employment has given me (not iPhone or Android) so my options are limited if I don’t have a computer. Well, you can say goodbye to that!

With the help of Fiddler I was able to track down the API calls that iCloud makes to ping a phone, so now I can make a simple web page that will ping her phone and all I need is my Apple ID, Password, and the name of the Phone / iMac / iPad / iPod / iGiveUp. It’s even in a nice static class for you so you can use it anywhere. Enjoy!

 

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Script.Serialization;
 
namespace ConsoleApplication1
{
  public static class iCloud
  {
    const string iCloudUrl = "https://www.icloud.com";
    const string iCloudLoginUrl = "https://setup.icloud.com/setup/ws/1/login";
    const string iCloudPlaySoundUrl = "https://p03-fmipweb.icloud.com/fmipservice/client/web/playSound";
    const string iCloudInitClientUrl = "https://p03-fmipweb.icloud.com/fmipservice/client/web/initClient";
 
    public static void Ping(string appleId, string password, string deviceName)
    {
      WebClient wc = new WebClient();
      string authCookies = string.Empty;
 
      wc.Headers.Add("Origin", iCloudUrl);
      wc.Headers.Add("Content-Type", "text/plain");
      wc.PostDataToWebsite(iCloudLoginUrl, string.Format(
        "{{\"apple_id\":\"{0}\",\"password\":\"{1}\",\"extended_login\":false}}",
        appleId, password));
 
      if (wc.ResponseHeaders.AllKeys.Any(k => k == "Set-Cookie"))
      {
        wc.Headers.Add("Cookie", wc.ResponseHeaders["Set-Cookie"]);
      }
      else
      {
        throw new System.Security.SecurityException("Invalid username / password");
      }
 
      var jsonString = wc.PostDataToWebsite(iCloudInitClientUrl,
        "{\"clientContext\":{\"appName\":\"iCloud Find (Web)\",\"appVersion\":\"2.0\"," +
        "\"timezone\":\"US/Eastern\",\"inactiveTime\":2255,\"apiVersion\":\"3.0\",\"webStats\":\"0:15\"}}");
 
      if (jsonString.StartsWith("{\"statusCode\":\"200\""))
      {
        var js = new JavaScriptSerializer();
        var response = js.Deserialize(jsonString, typeof(object)) as dynamic;
        var content = response["content"];
        foreach (Dictionary<string,object> o in content)
        {
          if (o.Values.Contains(deviceName))
          {
            var psResult = wc.PostDataToWebsite(iCloudPlaySoundUrl, string.Format(
              "{{\"device\":\"{0}\",\"subject\":\"Find My iPhone Alert\"}}", o["id"]));
 
            return;
          }
        }
      }
    }
  }
}

You will also need my extension method for WebClient.PostDataToWebsite here:

public static class Extensions
{
  public static string PostDataToWebsite(this WebClient wc, string url, string postData)
  {
    var result = string.Empty;
 
    wc.Encoding = System.Text.Encoding.UTF8;
    wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
 
    result = wc.UploadString(url, "POST", postData);
 
    return result;
  }
}

And the brave among you will be able to try it out via this secure page: iCloud Pinger (SSL Encrypted & I don’t log your credentials).