#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)); } } }