Wrapper around a user's name and password/key combination.
        public int Invoke(Dictionary<string, string> args)
        {
            var saveAsTemplate = args.Keys.Contains(Arguments.SaveTemplate) &&
                                 Boolean.Parse(args[Arguments.SaveTemplate]);
            var credentials = new Credentials(args[Arguments.Username], args[Arguments.Password]);
            var configName = args[Arguments.ConfigName];
            var logger = LoggerFactory.GetLogger();

            // Deserialize the persisted values from a previous run. The previous run would have saved off
            // a config URL, any IDs for reference, among other things.
            var configState = new ConfigurationState();
            var config = configState.Deserialize(configName);

            // FUTURE: May want to consider cleaning up the interim persistence XML file here after every run,
            // although it can be useful for debugging purposes.

            // FUTURE: Use the log file path in the persisted data to append to an existing log file instead of
            // creating a new one. Note that this will need to be done at the program level as opposed to individual
            // commands, which is more work. Suggest moving the deserialization logic earlier (program level) and
            // setting up the log file once.

            logger.LogInfo(config.ToString());

            // Saving a template will automatically put the configuration into a suspended state. If a template is
            // not being saved, then force the suspended state. Although not strictly necessary, this makes deleting
            // connections and subsequently shutdown more robust.
            if (saveAsTemplate)
            {
                SkytapApi.SaveAsSkytapTemplate(credentials, config.ConfigurationUrl);
            }
            else
            {
                SkytapApi.SetConfigurationState(credentials, config.ConfigurationUrl, ConfigurationStates.Suspended);
            }

            // Wait for Skytap to return the expected configuration state. Do this with a retry block
            // to test for the desired state every second for 5 minutes.
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, config.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                NumRetriesCheckConfigState,
                                                                                _retryIntervalCheckConfigState);

            // Prior to deleting a configuration, query the current connection state and disconnect
            // any existing ICNR or VPN connection. This helps prevent state issues on the Skytap side
            // with not being able to recreate the configuration once again.
            CleanupConnections(credentials, config);

            // HACKHACK: this state check is an attempted workaround at resolving a response not received when deleting a configuration
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, config.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                NumRetriesCheckConfigState,
                                                                                _retryIntervalCheckConfigState);

            // NOTE: It was determined by the engineering team that shutting down the configuration need not
            // be in a finally block. If the configuration does not shut down due to a previous error, we can
            // live with it.
            SkytapApi.ShutDownConfiguration(credentials, config.ConfigurationUrl);

            return CommandResults.Success;
        }
        /// <summary>
        /// Attach an existing VPN to a configuration's network.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configUrl">URL to the Skytap configuration that is associated with the VPN.</param>
        /// <param name="networkId">ID of the network to attach the VPN to.</param>
        /// <param name="vpnId">VPN ID to attach.</param>
        public static void AttachVpnConnection(Credentials credentials, string configUrl, string networkId, string vpnId)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Attaching VPN on Network ID = {0} using VPN ID = {1}", networkId, vpnId);

            var url = string.Format("{0}/networks/{1}/vpns", configUrl, networkId);
            var rBody = "{\"vpn_id\":\"" + vpnId + "\"}";

            logger.LogInfo("Request: URL: {0}\n\tBody: {1}\n", url, rBody);

            var request = (HttpWebRequest) WebRequest.Create(url);

            var usernamekey = string.Format("{0}:{1}", credentials.Username, credentials.Key);

            request.Timeout = _configParams.HttpTimeout;
            request.ReadWriteTimeout = _configParams.HttpTimeout;
            request.ContentType = HttpApiJsonMimeType;
            request.Accept = HttpApiJsonMimeType;
            request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(usernamekey));
            request.Method = WebRequestMethods.Http.Post;

            // ReSharper disable once AccessToStaticMemberViaDerivedType
            byte[] rbPostData = UTF8Encoding.UTF8.GetBytes(rBody);
            request.ContentLength = rbPostData.Length;

            var dataStream = request.GetRequestStream();
            dataStream.Write(rbPostData, 0, rbPostData.Length);
            dataStream.Close();
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
        public int Invoke(Dictionary<string, string> args)
        {
            var logger = LoggerFactory.GetLogger();
            var credentials = new Credentials(args[Arguments.Username], args[Arguments.Password]);

            var responseContent = SkytapApi.GetVpns(credentials);
            Debug.Assert(!string.IsNullOrEmpty(responseContent));

            return CommandResults.Success;
        }
        public void Integration_Api_CreateConfigToShutdownConfig()
        {
            const int numPollsForDesiredState = 20;

            var user = new SkytapUser();
            var timeBetweenPolls = new TimeSpan(0, 0, 0, 5);

            var tfsConfigIdParam = user.ConfigRunningTfs;
            var skytapTargetTemplateIdParam = user.TargetTemplate;
            var configName = "Integration_API_TestConfig_" + DateTime.Now.ToString("yy-MM-dd_hh.mm.ss");
            var credentials = new Credentials(user.Username, user.Key);

            // Get NetworkID of the TFS Configuration which is running in Skytap
            var tfsConfigNetworkId = SkytapApi.GetNetworkIdInConfiguration(credentials, tfsConfigIdParam);
            Assert.IsFalse(string.IsNullOrEmpty(tfsConfigNetworkId));

            // CreateConfiguration (returns ConfigID of the instantiated template)
            var newTargetConfig = SkytapApi.CreateConfiguration(credentials, skytapTargetTemplateIdParam, configName);
            Assert.IsNotNull(newTargetConfig);

            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                                new List<string> {ConfigurationStates.Suspended, ConfigurationStates.Stopped}),
                                                                                numPollsForDesiredState, timeBetweenPolls);

            // CreateIcnrConnection (between the TFSConfigNetwork and the newly instantiated config. Note that it
            // is important that the source network ID be the new configuration and the target network ID be
            // the (existing) TFS configuration. If the two are reversed, some unexplainable 409 (conflict)
            // errors occur. Following up and will update this if a good explanation is received.
            var icnrConnectionId = SkytapApi.CreateIcnrConnection(credentials, newTargetConfig.ConfigurationNetworkId, tfsConfigNetworkId);
            Assert.IsFalse(string.IsNullOrEmpty(icnrConnectionId));
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                numPollsForDesiredState, timeBetweenPolls);

            SkytapApi.SetConfigurationState(credentials, newTargetConfig.ConfigurationUrl, ConfigurationStates.Running);
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                                ConfigurationStates.Running),
                                                                                numPollsForDesiredState, timeBetweenPolls);

            SkytapApi.SaveAsSkytapTemplate(credentials, newTargetConfig.ConfigurationUrl);
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                numPollsForDesiredState, timeBetweenPolls);

            SkytapApi.DeleteIcnrConnection(credentials, icnrConnectionId);
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                numPollsForDesiredState, timeBetweenPolls);

            SkytapApi.ShutDownConfiguration(credentials, newTargetConfig.ConfigurationUrl);
        }
        /// <summary>
        /// Change the name of a Skytap configuration.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configurationUrl">The configuration identifier to change</param>
        /// <param name="newName">New name of the configuration (basic string; not sure of length restrictions)</param>
        public static void UpdateConfigurationName(Credentials credentials, string configurationUrl, string newName)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Updating configuration name to = {0}", newName);

            var url = HttpUtility.HtmlEncode(configurationUrl + "?name=" + newName);
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);

            request.Method = WebRequestMethods.Http.Put;

            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
        /// <summary>
        /// Change the state of a configuration to a specified value, such as Suspended, Stopped, or Running.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configUrl">Configuration whose state is to be modified.</param>
        /// <param name="state">State to transition to, such as Suspended, Stopped, or Running.</param>
        public static void SetConfigurationState(Credentials credentials, string configUrl, string state)
        {
            Debug.Assert(!string.IsNullOrEmpty(credentials.Username));
            Debug.Assert(!string.IsNullOrEmpty(credentials.Key));
            Debug.Assert(!string.IsNullOrEmpty(configUrl));
            Debug.Assert(!string.IsNullOrEmpty(state));

            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Setting configuration state to {0}", state);

            var url = string.Format("{0}?runstate={1}", configUrl, state);
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);

            request.Method = WebRequestMethods.Http.Put;
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
        /// <summary>
        /// Deletes a Skytap configuration, which is useful when a configuration was created for temporary use.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configUrlToDelete">URL of the configuration to be shutdown/deleted.</param>
        public static void ShutDownConfiguration(Credentials credentials, string configUrlToDelete)
        {
            var shutdownRequestDelayTime = new TimeSpan(0, 0, 10 /* sec */);

            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Request URL = {0}", configUrlToDelete);

            var request = CreateSkytapWebRequest(credentials, configUrlToDelete);
            request.Method = HttpDeleteRequest;

            // HACKHACK! There is an issue when running the plug-in in a TFS build where a response is never received
            // during the shutdown of the configuration. A root cause has not yet been identified. In lieu of a
            // proper fix, the following is done:
            //   (a) override the HTTP timeout to be a shorter value so that a frequent check of deletion can be made
            //   (b) if a timeout occurs, check if the configuration still exists in a separate request and if it
            //       still exists, try the whole operation again

            // These overrides are a one-time thing for the hack above. Generally, use the timeouts defined in
            // the method that creates the request.
            request.Timeout = 5000; // ms
            request.ReadWriteTimeout = request.Timeout;

            // NOTE that the delay time between retries is also adjusted as part of the hack. Wait a few seconds
            // instead of minutes.
            var responseContent = Retry.Execute(() =>
                {
                    try
                    {
                        var responseString = SendSkytapHttpRequest(request);

                        return responseString;
                    }
                    catch (WebException)
                    {
                        // In the case of an HTTP error, check if the configuration exists. If it does not, simply continue.
                        // Otherwise, re-throw the error. Note that the call to GetConfiguration avoids the retry operations
                        // since this whole operation will be retried as necessary (think of the shutdown and get as one
                        // transaction).
                        try
                        {
                            logger.LogInfo("Attempting to retrieve the configuration {0} to determine if it was deleted.", configUrlToDelete);
                            GetConfiguration(credentials, configUrlToDelete, false);
                        }
                        catch (WebException e)
                        {
                            // If a 404 was returned for the retrieval of the configuration,
                            // assume it was shut down and deleted and that we are done.
                            // Otherwise, need to rethrow the exception so that another
                            // retry loop is triggered.
                            if (e.Status == WebExceptionStatus.ProtocolError &&
                                ((HttpWebResponse) e.Response).StatusCode == HttpStatusCode.NotFound)
                            {
                                logger.LogInfo("404 status code received from GetConfiguration; assuming configuration is shut down");
                            }
                            else
                            {
                                logger.LogInfo("Non-404 status code received from GetConfiguration; attempting shutdown again");
                                throw;
                            }
                        }
                    }
                    return null;
                },
                _configParams.RetryNumRetries, shutdownRequestDelayTime);

            if (responseContent != null)
            {
                logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
            }
            else
            {
                logger.LogInfo("Shutdown complete, but no response received.");
            }
        }
        /// <summary>
        /// Get a global list of VPNs accessible by the user
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <returns>XML response content for the request.</returns>
        public static string GetVpns(Credentials credentials)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);

            var request = CreateSkytapWebRequest(credentials, _configParams.SkytapHostUrl + "/vpns");
            request.Method = WebRequestMethods.Http.Get;

            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);

            return responseContent;
        }
        /// <summary>
        /// Save the specified configuration as a new Skytap template.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="testTargetConfigUrl">URL of the configuration to turn into a template.</param>
        public static void SaveAsSkytapTemplate(Credentials credentials, string testTargetConfigUrl)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);

            // Grab the configID from the configUrl
            string[] parts = testTargetConfigUrl.Split('/');
            var configId = parts[parts.Length - 1];
            logger.LogInfo("Saving target test configuration as a Skytap template. ConfigurationID = {0}", configId);

            var url = new StringBuilder();
            url.Append(_configParams.SkytapHostUrl + "/templates/");
            logger.LogInfo("Request URL = {0}", url);

            HttpWebRequest request = CreateSkytapWebRequest(credentials, url.ToString());

            request.Method = WebRequestMethods.Http.Post;

            var sbContent = new StringBuilder();
            sbContent.Append("<configuration_id>");
            sbContent.Append(configId);
            sbContent.Append("</configuration_id>");

            string postData = sbContent.ToString();
            byte[] rbPostData = Encoding.UTF8.GetBytes(postData);
            request.ContentLength = rbPostData.Length;

            Stream dataStream = request.GetRequestStream();
            dataStream.Write(rbPostData, 0, rbPostData.Length);
            dataStream.Close();

            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
 /// <summary>
 /// Determine if a configuration is in an expected state, and throw an exception if it is not.
 /// </summary>
 /// <param name="credentials">Username and password for the user making the request.</param>
 /// <param name="testTargetConfigUrl">Configuration to query for the desired state.</param>
 /// <param name="desiredState">The desired state of the configuration, such as Stopped, Suspended, or Running.</param>
 public static void CheckConfigurationForDesiredState(Credentials credentials, string testTargetConfigUrl, string desiredState)
 {
     CheckConfigurationForDesiredState(credentials, testTargetConfigUrl, new List<string> { desiredState });
 }
        /// <summary>
        /// Get the network ID associated with a Skytap configuration, which is useful for other companion calls
        /// that operate on a network.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configId">The configuration identifier to query.</param>
        /// <returns>An ID of the attached network in the configuration.</returns>
        public static string GetNetworkIdInConfiguration(Credentials credentials, string configId)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);

            var url = string.Format("{0}/configurations/{1}", _configParams.SkytapHostUrl, configId);
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);
            request.Method = WebRequestMethods.Http.Get;
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);
            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);

            var xmldoc = new XmlDocument();
            xmldoc.LoadXml(responseContent);

            var configNetIdXmlNode = xmldoc.SelectSingleNode("//networks/network/id");
            Debug.Assert(configNetIdXmlNode != null);

            var configNetId = configNetIdXmlNode.InnerText;
            logger.LogInfo("DEBUG: Network ID got " + configNetId + "\n");

            return configNetId;
        }
        public void SkytapApi_CreateSkytapWebRequest_BadUrl()
        {
            const string url = "BadlyFormattedUrl";
            var credentials = new Credentials("John", "123456ABC");

            SkytapApi.CreateSkytapWebRequest(credentials, url);
        }
        private static void CleanupConnections(Credentials credentials, SkytapConfiguration config)
        {
            // FUTURE: may need to account for more than one connection, but that change should be
            // relatively straightforward. Add an array to the persisted values and simply loop
            // over all of them to clean-up.
            if (!string.IsNullOrEmpty(config.IcnrId))
            {
                SkytapApi.DeleteIcnrConnection(credentials, config.IcnrId);
            }

            if (!string.IsNullOrEmpty(config.VpnId))
            {
                SkytapApi.DisconnectVpn(credentials, config.ConfigurationUrl, config.ConfigurationNetworkId, config.VpnId);
                SkytapApi.DetachVpn(credentials, config.ConfigurationUrl, config.ConfigurationNetworkId, config.VpnId);
            }
        }
        /// <summary>
        /// Creates an ICNR connection between a source and target network.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="sourceNetId">Source Skytap network ID that the connection starts from.</param>
        /// <param name="targetNetId">Target Skytap network ID that the connection goes to.</param>
        /// <returns>String ID of the new connection if successful.</returns>
        public static string CreateIcnrConnection(Credentials credentials, string sourceNetId, string targetNetId)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("source NetId: " + sourceNetId + "target Net ID: " + targetNetId + "\n");

            var url = string.Format("{0}/tunnels?source_network_id={1}&target_network_id={2}",
                                    _configParams.SkytapHostUrl, sourceNetId, targetNetId);
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);
            request.Method = WebRequestMethods.Http.Post;
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);

            // Extract the ICNR ID from the response XML
            var xmldoc = new XmlDocument();
            xmldoc.LoadXml(responseContent);

            var icnrIdXmlNode = xmldoc.SelectSingleNode("//tunnel/id");
            Debug.Assert(icnrIdXmlNode != null);

            return icnrIdXmlNode.InnerText;
        }
        /// <summary>
        /// Deletes a specified ICNR connection.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="icnrId">ID of the ICNR connection to remove.</param>
        public static void DeleteIcnrConnection(Credentials credentials, string icnrId)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Deleting ICNR ID = {0}", icnrId);

            var url = string.Format("{0}/tunnels/{1}", _configParams.SkytapHostUrl, icnrId);
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);
            request.Method = HttpDeleteRequest;
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
        /// <summary>
        /// Create a new Skytap configuration based on the specified template and provide an optional name.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="templateId">ID of the template to base the new configuration on.</param>
        /// <param name="configName">Optional name of the new configuration.</param>
        /// <returns></returns>
        public static SkytapConfiguration CreateConfiguration(Credentials credentials, string templateId, string configName = null)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Creating configuration based on template ID = {0}", templateId);

            var newSkytapConfiguration = new SkytapConfiguration();

            var url = _configParams.SkytapHostUrl + "/configurations/";
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);
            request.Method = WebRequestMethods.Http.Post;

            // FUTURE: This likely can be simplified to include "?template_id=<id>" on the URL instead of this.
            // Do this once good automated tests are in place to validate.
            var webRequestContent = string.Format("<template_id>{0}</template_id>", templateId);
            byte[] rbPostData = Encoding.UTF8.GetBytes(webRequestContent);
            request.ContentLength = rbPostData.Length;

            var dataStream = request.GetRequestStream();
            dataStream.Write(rbPostData, 0, rbPostData.Length);
            dataStream.Close();

            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);
            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);

            var xmldoc = new XmlDocument();
            xmldoc.LoadXml(responseContent);

            var configUrlXmlNode = xmldoc.SelectSingleNode("/configuration/url");
            Debug.Assert(configUrlXmlNode != null);
            newSkytapConfiguration.ConfigurationUrl = configUrlXmlNode.InnerText;

            var configNetworkIdNode = xmldoc.SelectSingleNode("//networks/network/id");
            Debug.Assert(configNetworkIdNode != null);
            newSkytapConfiguration.ConfigurationNetworkId = configNetworkIdNode.InnerText;

            // Change the name of Configuration if a name has been provided
            if (configName != null)
            {
                newSkytapConfiguration.Name = configName;
                UpdateConfigurationName(credentials, newSkytapConfiguration.ConfigurationUrl, configName);
            }

            return newSkytapConfiguration;
        }
        /// <summary>
        /// After attaching, connect the VPN to the specified configuration and network.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configUrl">URL to the Skytap configuration that is associated with the VPN.</param>
        /// <param name="networkId">ID of the network to attach the VPN to.</param>
        /// <param name="vpnId">VPN ID to attach.</param>
        public static void ConnectVpn(Credentials credentials, string configUrl, string networkId, string vpnId)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Connecting VPN on Network ID = {0} using VPN ID = {1}", networkId, vpnId);

            var url = string.Format("{0}/networks/{1}/vpns/{2}.json", configUrl, networkId, vpnId);
            logger.LogInfo("Request URL = {0}", url);

            HttpWebRequest request = CreateSkytapWebRequest(credentials, url);
            request.Method = WebRequestMethods.Http.Put;
            request.ContentType = HttpApiJsonMimeType;
            request.Accept = HttpApiJsonMimeType;

            // ReSharper disable once AccessToStaticMemberViaDerivedType
            byte[] rbPostData = UTF8Encoding.UTF8.GetBytes("{ \"connected\": true }");
            request.ContentLength = rbPostData.Length;

            Stream dataStream = request.GetRequestStream();
            dataStream.Write(rbPostData, 0, rbPostData.Length);
            dataStream.Close();
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
        /// <summary>
        /// Determine if a configuration is in an expected state, and throw an exception if it is not.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="testTargetConfigUrl">Configuration to query for the desired state.</param>
        /// <param name="desiredStates">A list of states that are desired, such as a combination of
        /// Stopped, Suspended, or Running.</param>
        public static void CheckConfigurationForDesiredState(Credentials credentials, string testTargetConfigUrl, List<string> desiredStates)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);

            Debug.Assert(!string.IsNullOrEmpty(testTargetConfigUrl));
            Debug.Assert(desiredStates != null && desiredStates.Count > 0);

            var actualState = GetConfigurationState(credentials, testTargetConfigUrl);

            if (desiredStates.Contains(actualState))
            {
                logger.LogInfo("Configuration entered one of the desired states: {0}", actualState);
            }
            else
            {
                var desiredStatesString = desiredStates.Aggregate(string.Empty, (current, s) => current + (s + ", "));
                logger.LogInfo("Configuration NOT in one of the desired states: Desired = {0}; Actual = {1}", desiredStatesString, actualState);

                throw new ApplicationException(string.Format(Resources.SkytapApi_CheckConfigurationForDesiredState_ERROR_UnexpectedState, testTargetConfigUrl, desiredStatesString));
            }
        }
        internal static HttpWebRequest CreateSkytapWebRequest(Credentials credentials, string url)
        {
            Debug.Assert(!string.IsNullOrEmpty(credentials.Username));
            Debug.Assert(!string.IsNullOrEmpty(credentials.Key));
            Debug.Assert(!string.IsNullOrEmpty(url));

            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = _configParams.HttpTimeout;
            request.ReadWriteTimeout = _configParams.HttpTimeout;
            request.ContentType = HttpApiXmlMimeType;
            request.Accept = HttpApiXmlMimeType;

            // DEBUG CODE ONLY - Setting KeepAlive should be kept true for request/response efficiency
            //
            // Set KeepAlive to false explicitly for reliability reasons. This will open a new connection
            // to the Skytap servers for every request and is less efficient, but may prevent connection
            // issues with timeouts and responses not being received.
            // request.KeepAlive = false;

            var usernameKey = string.Format("{0}:{1}", credentials.Username, credentials.Key);
            request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(usernameKey));

            return request;
        }
        /// <summary>
        /// Gets the current state of the configuration, such as Running, Suspended, or Stopped.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configurationUrl">URL of the configuration to query.</param>
        /// <returns>A string representing the state of the configuration, such as running, suspended, or stopped.</returns>
        /// <remarks>This is a helper method that extracts the runstate out of the XML response, but you
        /// could just as easily use <seealso cref="GetConfiguration"/> to get the full response XML
        /// containing all properties.</remarks>
        public static string GetConfigurationState(Credentials credentials, string configurationUrl)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);

            var responseContent = GetConfiguration(credentials, configurationUrl);

            // Parse out the response XML to get just the runstate for return to the caller.
            var xmldoc = new XmlDocument();
            xmldoc.LoadXml(responseContent);

            var configStateXmlNode = xmldoc.SelectSingleNode("/configuration/runstate");
            Debug.Assert(configStateXmlNode != null);

            return configStateXmlNode.InnerText;
        }
        /// <summary>
        /// Disconnect a VPN connection. This should be done prior to detaching a VPN.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configUrl">URL to the Skytap configuration that is associated with the VPN.</param>
        /// <param name="networkId">ID of the network containing the VPN to disconnect.</param>
        /// <param name="vpnId">VPN ID to disconnect.</param>
        public static void DisconnectVpn(Credentials credentials, string configUrl, string networkId, string vpnId)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Disconnecting VPN on Network ID = {0} using VPN ID = {1}", networkId, vpnId);

            var url = string.Format("{0}/networks/{1}/vpns/{2}?connected=false", configUrl, networkId, vpnId);
            logger.LogInfo("Request URL = {0}", url);

            var request = CreateSkytapWebRequest(credentials, url);
            request.Method = WebRequestMethods.Http.Put;
            var responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);

            logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
        }
        public void CreateSkytapWebRequest_Basic()
        {
            const string url = "http://someserver.somewhere.com";
            var credentials = new Credentials("John", "123456ABC");

            var httpRequest = SkytapApi.CreateSkytapWebRequest(credentials, url);

            Assert.IsNotNull(httpRequest);
            Assert.AreEqual(ApplicationParameters.DefaultHttpTimeout, httpRequest.Timeout);
            Assert.AreEqual("application/xml", httpRequest.ContentType);
            Assert.IsTrue(httpRequest.Headers["Authorization"].Contains("Basic"));
        }
        /// <summary>
        /// Retrieves the XML that describes the specified Skytap configuration.
        /// </summary>
        /// <param name="credentials">Username and password for the user making the request.</param>
        /// <param name="configurationUrl">URL of the configuration to query.</param>
        /// <param name="doRetries">Whether or not to retry the call if it fails.</param>
        /// <returns>An XML string with all the configuration information.</returns>
        public static string GetConfiguration(Credentials credentials, string configurationUrl, bool doRetries = true)
        {
            var logger = LoggerFactory.GetLogger();
            logger.LogInfo("Enter {0}... \n", MethodBase.GetCurrentMethod().Name);
            logger.LogInfo("Request URL = {0}", configurationUrl);

            var request = CreateSkytapWebRequest(credentials, configurationUrl);
            request.Method = WebRequestMethods.Http.Get;

            string responseContent;
            if (doRetries)
            {
                responseContent = Retry.Execute(() => SendSkytapHttpRequest(request), _configParams.RetryNumRetries, _configParams.RetryWaitTime);
            }
            else
            {
                responseContent = SendSkytapHttpRequest(request);
            }

            if (!string.IsNullOrEmpty(responseContent))
            {
                logger.LogInfo("{0}: RESPONSE = {1}\n", MethodBase.GetCurrentMethod().Name, responseContent);
            }

            return responseContent;
        }
        public int Invoke(Dictionary<string, string> args)
        {
            var credentials = new Credentials(args[Arguments.Username], args[Arguments.Password]);
            var configId = args.ContainsKey(Arguments.ConfigId) ? args[Arguments.ConfigId] : null;
            var vpnId = args.ContainsKey(Arguments.VpnId) ? args[Arguments.VpnId] : null;
            var templateId = args[Arguments.TemplateId];
            var configName = args[Arguments.ConfigName];
            var configState = new ConfigurationState();

            var logger = LoggerFactory.GetLogger();

            // CreateConfiguration (returns ConfigID of the instantiated template)
            SkytapConfiguration newTargetConfig = SkytapApi.CreateConfiguration(credentials, templateId, configName);

            // Wait for Skytap to return the expected configuration state. Do this with a retry block
            // to test for the desired state every second for 5 minutes.
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                             new List<string> {ConfigurationStates.Suspended, ConfigurationStates.Stopped}),
                                                                             NumRetriesCheckConfigState,
                                                                             _retryIntervalCheckConfigState);

            // If VPNID supplied, make a VPN connection, if a configid, create ICNR Connection
            if (String.IsNullOrEmpty(vpnId))
            {
                // CreateIcnrConnection (between the TFSConfigNetwork and the newly instantiated config. Note that it
                // is important that the source network ID be the new configuration and the target network ID be
                // the (existing) TFS configuration. If the two are reversed, a 409 (conflict) error may occur.
                var tfsConfigNetworkId = SkytapApi.GetNetworkIdInConfiguration(credentials, configId);
                var icnrId = SkytapApi.CreateIcnrConnection(credentials, newTargetConfig.ConfigurationNetworkId, tfsConfigNetworkId );
                newTargetConfig.IcnrId = icnrId;
            }
            else
            {
                SkytapApi.AttachVpnConnection(credentials, newTargetConfig.ConfigurationUrl, newTargetConfig.ConfigurationNetworkId, vpnId);
                SkytapApi.ConnectVpn(credentials, newTargetConfig.ConfigurationUrl, newTargetConfig.ConfigurationNetworkId, vpnId);
                newTargetConfig.VpnId = vpnId;
            }

            // Need to wait again for ICNR or VPN to complete.
            //
            // Before starting the configuration, ensure that it is suspended. If it is not suspended (and perhaps stopped)
            // wait until it is in the desired state.
            var configurationState = SkytapApi.GetConfigurationState(credentials, newTargetConfig.ConfigurationUrl);

            if (configurationState != ConfigurationStates.Running)
            {
                // Attempt to start up the configuration using retry semantics just in case the first request doesn't work. This
                // could happen if the configuration tries to restart but the service is busy so the state is returned to
                // suspended and we need to retry the start-up.
                Retry.Execute(() =>
                    {
                        // Start up the configuration by changing its state to running. It is assumed there is a change to the
                        // runstate at this point, either to "busy" or "running". If the configuration ends up as "running",
                        // nothing else to do - just continue. If the state goes back to "suspended", need to retry the start
                        // logic a few more times until the number of retries is exhausted. If it never comes back from "busy" or
                        // enters some other unknown state, just exit once the retry threshold is reached.
                        SkytapApi.SetConfigurationState(credentials, newTargetConfig.ConfigurationUrl, ConfigurationStates.Running);

                        Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                            new List<string> { ConfigurationStates.Running, ConfigurationStates.Suspended }),
                                                                            NumRetriesCheckConfigState, _retryIntervalCheckConfigState);

                        // Re-get the configuration state so we can determine whether to give up attempting to start the
                        // configuration or try again. The exception will trigger a retry.
                        var currentConfigState = SkytapApi.GetConfigurationState(credentials, newTargetConfig.ConfigurationUrl);
                        if (currentConfigState == ConfigurationStates.Suspended)
                        {
                            throw new ApplicationException(Resources.TfsStartup_UnexpectedReversionToSuspended);
                        }
                    },
                    NumRetriesStartConfig, _retryIntervalStartConfig);
            }

            // Persist the log file path so that successive invocations of the EXE can use the
            // same log file.
            newTargetConfig.LogFilePath = ((TraceLogger) logger).LogFilePath;

            // Store Config Url so that we can run shutdown on it later since TFS isn't smart enough to do this for us
            var configStatePath = configState.Serialize(newTargetConfig);
            logger.LogInfo("Persisted configuration path: " + configStatePath);
            logger.LogInfo(newTargetConfig.ToString());

            return CommandResults.Success;
        }