├── tools └── NuGet.exe ├── .gitignore ├── WazMonkey ├── packages.config ├── Properties │ └── AssemblyInfo.cs ├── WazMonkey.csproj └── Program.cs ├── WazMonkey.sln └── README.md /tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smarx/WazMonkey/HEAD/tools/NuGet.exe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | *.user 4 | *.suo 5 | *.cache 6 | *.nupkg 7 | packages/ 8 | -------------------------------------------------------------------------------- /WazMonkey/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /WazMonkey.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WazMonkey", "WazMonkey\WazMonkey.csproj", "{64597F68-E7A2-4DE1-A89C-222DB35DC2BF}" 5 | EndProject 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{7C06F84E-6400-4C19-AF5A-541E548ECFC0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {64597F68-E7A2-4DE1-A89C-222DB35DC2BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {64597F68-E7A2-4DE1-A89C-222DB35DC2BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {64597F68-E7A2-4DE1-A89C-222DB35DC2BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {64597F68-E7A2-4DE1-A89C-222DB35DC2BF}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WazMonkey is a simple tool for testing resilience of Windows Azure cloud services. 2 | 3 | It's modeled after [Netflix's Chaos Monkey](http://techblog.netflix.com/2012/07/chaos-monkey-released-into-wild.html). 4 | WazMonkey chooses a role instance from your service at random and reboots it. 5 | 6 | ## Usage: 7 | 8 | WazMonkey v0.2 9 | Copyright (C) 2012 Steve Marx 10 | Usage: WazMonkey -p foo.publishSettings -n myservice -s production 11 | 12 | -p, --publishSettings .publishSettings file - specify either this or a 13 | .pfx file 14 | 15 | --pfx .pfx certificate file - specify either this or a 16 | .publishSettings file 17 | 18 | --subscriptionId subscriptionId to use, defaults to first 19 | subscription found in the .publishSettings file 20 | 21 | -n, --serviceName Required. Name of the cloud service 22 | 23 | -s, --slot Required. The slot ("production" or "staging") 24 | 25 | --reimage Reimage the instance (instead of reboot, the 26 | default) 27 | 28 | --help Display this help screen. 29 | -------------------------------------------------------------------------------- /WazMonkey/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WazMonkey")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WazMonkey")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("03ac350e-8227-472b-bf6b-ca08d451fcc5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WazMonkey/WazMonkey.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {64597F68-E7A2-4DE1-A89C-222DB35DC2BF} 8 | Exe 9 | Properties 10 | WazMonkey 11 | WazMonkey 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\CommandLineParser.1.9.3.19\lib\CommandLine.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(SolutionDir)Tools\nuget install $(ProjectDir)packages.config -o $(SolutionDir)Packages 56 | 57 | 64 | -------------------------------------------------------------------------------- /WazMonkey/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CommandLine; 6 | using CommandLine.Text; 7 | using System.Xml.Linq; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Net; 10 | using System.Globalization; 11 | 12 | namespace WazMonkey 13 | { 14 | class Options : CommandLineOptionsBase 15 | { 16 | [Option("p", "publishSettings", Required = false, HelpText = ".publishSettings file - specify either this or a .pfx file")] 17 | public string PublishSettingsFile { get; set; } 18 | 19 | [Option(null, "pfx", Required = false, HelpText = ".pfx certificate file - specify either this or a .publishSettings file")] 20 | public string PfxFile { get; set; } 21 | 22 | [Option(null, "subscriptionId", Required = false, HelpText = "subscriptionId to use, defaults to first subscription found in the .publishSettings file")] 23 | public string SubscriptionId { get; set; } 24 | 25 | [Option("n", "serviceName", Required = true, HelpText = "Name of the cloud service")] 26 | public string ServiceName { get; set; } 27 | 28 | [Option("s", "slot", Required = true, HelpText = @"The slot (""production"" or ""staging"")")] 29 | public string Slot { get; set; } 30 | 31 | [Option(null, "reimage", Required = false, HelpText = "Reimage the instance (instead of reboot, the default)")] 32 | public bool Reimage { get; set; } 33 | 34 | [HelpOption(HelpText = "Display this help screen.")] 35 | public string GetUsage() 36 | { 37 | var help = new HelpText 38 | { 39 | Heading = new HeadingInfo("WazMonkey", "v0.2"), 40 | Copyright = new CopyrightInfo("Steve Marx", 2012), 41 | AdditionalNewLineAfterOption = true, 42 | AddDashesToOption = true 43 | }; 44 | help.AddPreOptionsLine("Usage: WazMonkey -p foo.publishSettings -n myservice -s production"); 45 | help.AddOptions(this); 46 | return help; 47 | } 48 | } 49 | 50 | class Program 51 | { 52 | static void Main(string[] args) 53 | { 54 | var options = new Options(); 55 | if (CommandLineParser.Default.ParseArguments(args, options)) 56 | { 57 | if (options.PublishSettingsFile == null && options.PfxFile == null) 58 | { 59 | Console.WriteLine("Required: one of --publishSettings or --pfx"); 60 | Console.WriteLine(options.GetUsage()); 61 | Environment.Exit(1); 62 | } 63 | if (options.PublishSettingsFile != null && options.PfxFile != null) 64 | { 65 | Console.WriteLine("Incompatible arguments: --publishSettings and --pfx"); 66 | Console.WriteLine(options.GetUsage()); 67 | Environment.Exit(1); 68 | } 69 | if (options.PublishSettingsFile == null && options.SubscriptionId == null) 70 | { 71 | Console.WriteLine("--subscriptionId must be provided if no .publishSettings file is being used"); 72 | Console.WriteLine(options.GetUsage()); 73 | Environment.Exit(1); 74 | } 75 | if (options.Slot != "production" && options.Slot != "staging") 76 | { 77 | Console.WriteLine(@"Slot must be one of ""production"" or ""staging"""); 78 | Console.WriteLine(options.GetUsage()); 79 | Environment.Exit(1); 80 | } 81 | } 82 | else 83 | { 84 | Environment.Exit(1); 85 | } 86 | 87 | X509Certificate2 cert; 88 | string subscriptionId = options.SubscriptionId; 89 | 90 | if (options.PublishSettingsFile != null) 91 | { 92 | var profile = XDocument.Load(options.PublishSettingsFile); 93 | if (subscriptionId == null) 94 | { 95 | subscriptionId = profile.Descendants("Subscription").First().Attribute("Id").Value; 96 | } 97 | cert = new X509Certificate2(Convert.FromBase64String( 98 | profile.Descendants("PublishProfile").Single() 99 | .Attribute("ManagementCertificate").Value)); 100 | } 101 | else 102 | { 103 | cert = new X509Certificate2(options.PfxFile); 104 | } 105 | 106 | var baseAddress = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}", subscriptionId, options.ServiceName, options.Slot); 107 | 108 | // request details on specified deployment 109 | var req = (HttpWebRequest)WebRequest.Create(baseAddress); 110 | req.Headers["x-ms-version"] = "2012-03-01"; 111 | req.ClientCertificates.Add(cert); 112 | 113 | // find all instances 114 | XNamespace xmlns = "http://schemas.microsoft.com/windowsazure"; 115 | try 116 | { 117 | var responseStream = req.GetResponse().GetResponseStream(); 118 | } 119 | catch (WebException e) 120 | { 121 | if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotFound) 122 | { 123 | var titleCaseSlot = new CultureInfo("en-us", false).TextInfo.ToTitleCase(options.Slot); 124 | Console.WriteLine("Error: {0} deployment for service {1} not found, or the credentials were invalid.", titleCaseSlot, options.ServiceName); 125 | Environment.Exit(1); 126 | } 127 | else 128 | { 129 | throw; 130 | } 131 | } 132 | var instances = XDocument.Load(req.GetResponse().GetResponseStream()).Descendants(xmlns + "InstanceName").Select(n => n.Value).ToArray(); 133 | 134 | // choose one at random 135 | var instance = instances[new Random().Next(instances.Length)]; 136 | Console.WriteLine("{0} {1}.", options.Reimage ? "Reimaging" : "Rebooting", instance); 137 | 138 | // reboot it 139 | req = (HttpWebRequest)WebRequest.Create(string.Format("{0}/roleinstances/{1}?comp={2}", baseAddress, instance, options.Reimage ? "reimage" : "reboot")); 140 | req.Method = "POST"; 141 | req.ContentLength = 0; 142 | req.Headers["x-ms-version"] = "2012-03-01"; 143 | req.ClientCertificates.Add(cert); 144 | 145 | // make sure the response was "accepted" 146 | var response = (HttpWebResponse)req.GetResponse(); 147 | if (response.StatusCode != HttpStatusCode.Accepted) 148 | { 149 | Console.WriteLine("Got unexpected status code: {0}", response.StatusCode); 150 | } 151 | } 152 | } 153 | } 154 | --------------------------------------------------------------------------------