#region Copyright
/*
* Copyright © 2014-2016 NetApp, Inc. All Rights Reserved.
*
* CONFIDENTIALITY NOTICE: THIS SOFTWARE CONTAINS CONFIDENTIAL INFORMATION OF
* NETAPP, INC. USE, DISCLOSURE OR REPRODUCTION IS PROHIBITED WITHOUT THE PRIOR
* EXPRESS WRITTEN PERMISSION OF NETAPP, INC.
*/
#endregion
#region Using Directives
using Newtonsoft.Json;
using SolidFire.Core.Helpers;
using SolidFire.Core.Objects;
using SolidFire.Element.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using SolidFire.Core.Exceptions;
using System.Reflection;
using static SolidFire.Core.Helpers.SolidFireUtilities;
#endregion
namespace SolidFire.Core
{
public class SFCmdlet : PSCmdlet
{
private string[] _target;
private SFConnection[] _connections;
[Parameter]
public string[] Target
{
get
{
if (_target != null && _target.Count() > 0)
{
return _target;
}
else
{
if (SolidFireUtilities.SFConnection == null)
{
return null;
}
return new string[] { SolidFireUtilities.SFConnection.Name };
}
}
set
{
WriteVerbose("Setting Target to " + value.ToString());
_target = value;
}
}
[Parameter]
public SFConnection[] SFConnection
{
get
{
if (_connections != null && _connections.Count() > 0)
{
return _connections;
}
else
{
return null;
}
}
set
{
WriteVerbose("Setting Connection to " + value.ToString());
_connections = value;
}
}
///
/// This will be called immediately before the request is sent when the target connection is known.
/// Override this method if you need the opportunity to set a parameter in the request object based on the target connection.
/// This is especially helpful if something needs to change based on connection version. See an example in Start-SFVolumeRestore.
///
/// The request object that can be modified and is about to be used in the HttpRequest. This is passed by ref.
/// The API method about to be called in the HttpRequest. This is passed by ref.
/// The connection this request is about to be called on.
public virtual void BeforeRequest(ref RpcBase request, ref string method, SFConnection connection) { }
protected bool NodeOnly { get; set; }
protected bool ClusterOnly { get; set; }
protected bool Both { get; set; }
protected virtual bool CheckConnection(
float minVersionNumber = SolidFireVersionApi.MinVersionApiNumber,
SFEndPoint endPoint = SFEndPoint.Cluster,
float? maxVersionNumber = null,
bool throwTerminatingError = true)
{
ClusterOnly = endPoint == SFEndPoint.Cluster;
NodeOnly = endPoint == SFEndPoint.Node;
Both = endPoint == SFEndPoint.Both;
bool result = true;
if (SFConnection != null)
{
foreach (var connection in SFConnection)
{
result = checkConnection(connection, minVersionNumber, endPoint, maxVersionNumber, throwTerminatingError);
}
}
else if (Target != null)
{
foreach (var target in Target)
{
var connection = GetSFConnection(this, target);
result = checkConnection(connection, minVersionNumber, endPoint, maxVersionNumber, throwTerminatingError);
}
}
return result;
}
protected override void EndProcessing()
{
base.EndProcessing();
if (SFConnection != null)
{
foreach (var connection in SFConnection)
{
clearHttpEventHandlers(connection);
}
}
else if (Target != null)
{
foreach (var target in Target)
{
var connection = GetSFConnection(this, target);
clearHttpEventHandlers(connection);
}
}
}
protected override void StopProcessing()
{
base.StopProcessing();
if (SFConnection != null)
{
foreach (var connection in SFConnection)
{
clearHttpEventHandlers(connection);
}
}
else if (Target != null)
{
foreach (var target in Target)
{
var connection = GetSFConnection(this, target);
clearHttpEventHandlers(connection);
}
}
}
private void clearHttpEventHandlers(SFConnection connection)
{
if (MyInvocation.BoundParameters.ContainsKey("Verbose"))
{
connection.Element.OnRequest -= Element_OnRequest;
connection.Element.OnResponse -= Element_OnResponse;
}
}
private bool checkConnection(SFConnection connection, float minVersionNumber, SFEndPoint endPoint, float? maxVersionNumber, bool throwTerminatingError)
{
bool result = true;
if ((Both) ||
(NodeOnly && connection.Node) ||
(ClusterOnly && !connection.Node) ||
(!NodeOnly && !ClusterOnly))
{
result = result && SolidFireUtilities.CheckConnection(instance: this, minVersionNumber: minVersionNumber, endPoint: endPoint, maxVersionNumber: maxVersionNumber ?? SolidFireVersionApi.MaxVersionApiNumber, throwTerminatingError: throwTerminatingError, connection: connection);
if (MyInvocation.BoundParameters.ContainsKey("Verbose"))
{
connection.Element.OnRequest += Element_OnRequest;
connection.Element.OnResponse += Element_OnResponse;
}
}
return result;
}
private void Element_OnResponse(string content)
{
Console.WriteLine("RESPONSE " + content);
}
private void Element_OnRequest(string content)
{
Console.WriteLine("REQUEST " + content);
}
protected void SetResolver(DynamicRpcContractResolver resolver)
{
if (SFConnection != null)
{
foreach (var conn in SFConnection)
{
conn.Element.SerializerSettings.ContractResolver = resolver;
conn.Element.SerializerSettings.NullValueHandling = NullValueHandling.Include;
}
}
else if (Target != null)
{
foreach (var target in Target)
{
var conn = GetSFConnection(this, target);
conn.Element.SerializerSettings.ContractResolver = resolver;
conn.Element.SerializerSettings.NullValueHandling = NullValueHandling.Include;
}
}
}
protected void ResetResolver()
{
if (SFConnection != null)
{
foreach (var conn in SFConnection)
{
conn.Element.ResetSerializerSettings();
}
}
else if(Target != null)
{
foreach (var target in Target)
{
var conn = GetSFConnection(this, target);
conn.Element.ResetSerializerSettings();
}
}
}
protected void SetTimeout(TimeSpan timeout)
{
if (SFConnection != null)
{
foreach (var conn in SFConnection)
{
conn.Element.SetTimeout(timeout);
}
}
else if (Target != null)
{
foreach (var target in Target)
{
var conn = GetSFConnection(this, target);
conn.Element.SetTimeout(timeout);
}
}
}
protected List> SendRequest(
string method,
RpcBase request = null,
bool throwTerminatingError = true,
bool? force443 = null)
{
var results = new List>();
if (SFConnection != null)
{
foreach (var conn in SFConnection)
{
_sendRequest(conn, method, results, request, throwTerminatingError, force443);
}
}
else
{
foreach (var target in Target)
{
var conn = GetSFConnection(this, target);
_sendRequest(conn, method, results, request, throwTerminatingError, force443);
}
}
return results;
}
private void _sendRequest(
SFConnection conn,
string method,
List> results,
RpcBase request = null,
bool throwTerminatingError = true,
bool? force443 = null)
{
if ((Both) ||
(NodeOnly && conn.Node) ||
(ClusterOnly && !conn.Node))
{
if (WhatIfShouldProcessHandler(string.Format("{0} {1}", conn.Target, request == null ? "" : request.ToString())))
{
if (force443.HasValue) // Some CmdLets are using Node connections but need to force a call to the 443 port. Get-SFBootstrapConfig and New-SFCluster are two of them.
{
WriteVerbose("Altering connection to force use of port 443 due to special needs of " + this.GetType().Name);
conn = (SFConnection)conn.Clone(); // This is a temporary connection that is not persisted beyond the scope of this method.
conn.SetPort(443);
}
WriteVerbose("Running " + this.GetType().Name + " command on connection " + conn.Name + " at " + conn.Uri + " with username " + conn.Credential.UserName);
var returnObj = SingleConnectionRequest(method, conn, request);
if (!HasAPIError(returnObj.Error, this, throwTerminatingError))
{
results.Add(returnObj);
}
}
}
else
{
WriteVerbose("Skipping " + this.GetType().Name + " command on connection " + conn.Name);
var errorRecord =
new ErrorRecord(new ConnectionException("Skipping command on connection " + conn.Name + ". CmdLet requires " + (NodeOnly ? "Node" : "Cluster") + " connection."),
"ConnectionError", ErrorCategory.InvalidOperation, conn);
HandleError(errorRecord, this, false);
}
}
protected SFWrapper SingleConnectionRequest(string method, SFConnection conn, RpcBase request = null)
{
var returnable = new SFWrapper();
returnable.Target = conn;
try
{
double TOLERANCE = .100;
if (conn.Timeout.HasValue && Math.Abs(conn.Timeout.Value - conn.Element.GetTimeout().TotalSeconds) > TOLERANCE)
{
WriteVerbose($"Timeout set to {conn.Timeout.Value} secs on SFConnection {conn.Name}.");
conn.Element.SetTimeout(new TimeSpan(0, 0, conn.Timeout.Value));
}
// Any overridden processing of the request object that needs connection specifics prior to it being sent will be executed here.
BeforeRequest(ref request, ref method, conn);
MethodInfo methodInfo;
if (request != null)
{
var types = new Type[] {request.GetType()};
methodInfo = typeof(SolidFireElement).GetMethod(method, types);
returnable.Result = (T) methodInfo.Invoke(conn.Element, new object[] {request});
}
else
{
methodInfo = typeof(SolidFireElement).GetMethod(method);
returnable.Result = (T) methodInfo.Invoke(conn.Element, new object[] {});
}
}
catch (AggregateException ex)
{
clearHttpEventHandlers(conn);
returnable.Error = new SFError()
{
Message = ex.InnerException.Message,
Name = ex.InnerException.GetType().Name
};
}
catch (ApiServerException ex)
{
clearHttpEventHandlers(conn);
returnable.Error = new SFError() {Message = ex.Message, Name = ((ApiServerException) ex).Name};
}
catch (TargetInvocationException ex)
{
clearHttpEventHandlers(conn);
if (ex.InnerException is AggregateException)
{
var innerEx = (ex.InnerException as AggregateException).Flatten();
returnable.Error = new SFError()
{
Message = innerEx.InnerException.Message,
Name = innerEx.InnerException.GetType().Name
};
}
else if (ex.InnerException is ApiServerException)
{
var innerEx = (ex.InnerException as ApiServerException);
returnable.Error = new SFError() {Message = innerEx.Message, Name = innerEx.Name};
}
else
{
returnable.Error = new SFError()
{
Message = ex.InnerException.Message,
Name = ex.InnerException.GetType().Name
};
}
}
catch (Exception ex)
{
clearHttpEventHandlers(conn);
returnable.Error = new SFError() {Message = ex.Message, Name = ex.GetType().Name};
}
return returnable;
}
protected bool WhatIfShouldProcessHandler(string message)
{
return (ShouldProcess(message, this.GetType().Name));
}
}
}