From patchwork Mon Dec 12 22:20:51 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Yin Lin X-Patchwork-Id: 705162 X-Patchwork-Delegate: guru@ovn.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3tcy3K5CpJz9t2T for ; Tue, 13 Dec 2016 09:21:01 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=onevmw.onmicrosoft.com header.i=@onevmw.onmicrosoft.com header.b="UGqXuSDi"; dkim-atps=neutral Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id CFBF2BFB; Mon, 12 Dec 2016 22:20:59 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 15CE0BDE for ; Mon, 12 Dec 2016 22:20:59 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from NAM01-BN3-obe.outbound.protection.outlook.com (mail-bn3nam01on0067.outbound.protection.outlook.com [104.47.33.67]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 16E82135 for ; Mon, 12 Dec 2016 22:20:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=onevmw.onmicrosoft.com; s=selector1-vmware-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=Bb35lNbu1DyJUoavRxRIohAAPVejXIbYN3gtOT8+RsA=; b=UGqXuSDiCCni0Q/0jYilo4i44rOsZlMzC9JVf61jXIU6E6KwduwibnNmtQmSMmPMYhF9/L7ZJwNzppvFuv8c8BRhyuzkONkibss7tPs8gPE2oOF10MwP4N4f5587gOJdUSxoKvAYlxzzInPrB575JWW0jQX2qyLboQBTqXjasf8= Received: from CY1PR0501MB1337.namprd05.prod.outlook.com (10.160.226.142) by CY1PR0501MB1338.namprd05.prod.outlook.com (10.160.226.143) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.1.789.11; Mon, 12 Dec 2016 22:20:52 +0000 Received: from CY1PR0501MB1337.namprd05.prod.outlook.com ([10.160.226.142]) by CY1PR0501MB1337.namprd05.prod.outlook.com ([10.160.226.142]) with mapi id 15.01.0771.014; Mon, 12 Dec 2016 22:20:52 +0000 From: Yin Lin To: "dev@openvswitch.org" Thread-Topic: [PATCH v2] Windows: Implement Hyper-V VIF discovery agent. Thread-Index: AdJUxf3JidYykv8SQS6hgYfuDDt2SA== Date: Mon, 12 Dec 2016 22:20:51 +0000 Message-ID: Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: spf=none (sender IP is ) smtp.mailfrom=linyi@vmware.com; x-originating-ip: [208.91.1.34] x-ms-office365-filtering-correlation-id: f6aa6ca3-aca4-480b-042a-08d422dd2230 x-microsoft-antispam: UriScan:; BCL:0; PCL:0; RULEID:(22001); SRVR:CY1PR0501MB1338; x-microsoft-exchange-diagnostics: 1; CY1PR0501MB1338; 7:8BseHpJpbvpvw81plK0EWQZiTL0UgXp3A4yB6mTqAkASDNOAWsI6vVRuSxMfsafopqbUYR2ZYoCOyr2XQdMKbRK1IUT0WTMLr1ZQ4FcaiAWazuEOY2wk99pCNc8msJv+XqpwzmvqAAVSptaCWXC1WrG6qnA/45JIUr1rQDLzP2ix5DU2uxOxpO71FzRWKgk8bgHWKEOjQW4ErpTjLx62AUP2HqcEKjJvotPZKgbDZHaejmBlGWlu8MXbiJ5KX85Vw3TgkI7y7AJCTn1j7N2b4PY8zIHEA7bmlvM1UhU6VGphjQmrBN+DhTlsDJlJJxorOugvlRSK/FAZiEVWa6i+l0VeM0fB64O+Mxi9DbtM+cLrnGur4WwrlFPm4KHkVXA4Jz3Bl1MWh19bhM5nnLmoNHgRmKOiA0OjZjV3UEYrHOOHaAvvtR/0w1JuYAUaYQocgjEhEHUIsUWbw9DsUJcpLA==; 20:s0ixkdRM+qF6ezmaicH/VsRTvvVPVYGbJ13oSC15GN8SRjU4vTg3i1+NW8YgorGRZuBz4Bf5mhp3oV8Z1qRDr87AK5PBIsp11i5tEslGH5X+p+aOK3TJy9vccly9RhqPI3YAe+vQPw1hKW7TW37bUwffZu8Op80AWqRbNt2I1NU= x-microsoft-antispam-prvs: x-exchange-antispam-report-test: UriScan:(61668805478150)(21748063052155)(21532816269658)(17755550239193); x-exchange-antispam-report-cfa-test: BCL:0; PCL:0; RULEID:(6040375)(601004)(2401047)(8121501046)(5005006)(3002001)(10201501046)(6041248)(20161123555025)(20161123562025)(20161123564025)(20161123560025)(20161123558021)(6072148); SRVR:CY1PR0501MB1338; BCL:0; PCL:0; RULEID:; SRVR:CY1PR0501MB1338; x-forefront-prvs: 0154C61618 x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(6009001)(7916002)(39450400003)(189002)(199003)(86362001)(6436002)(97736004)(2501003)(6506006)(66066001)(5640700002)(575784001)(122556002)(110136003)(33656002)(7696004)(8936002)(3846002)(81156014)(305945005)(6116002)(3660700001)(102836003)(2351001)(76576001)(8676002)(31430400001)(9686002)(3280700002)(74316002)(1730700003)(81166006)(7736002)(106356001)(77096006)(68736007)(5660300001)(54356999)(99286002)(101416001)(450100001)(189998001)(38730400001)(50986999)(2900100001)(105586002)(2906002)(92566002)(107886002)(6916009)(579004)(569005); DIR:OUT; SFP:1101; SCL:1; SRVR:CY1PR0501MB1338; H:CY1PR0501MB1337.namprd05.prod.outlook.com; FPR:; SPF:None; PTR:InfoNoRecords; MX:1; A:1; LANG:en; received-spf: None (protection.outlook.com: vmware.com does not designate permitted sender hosts) spamdiagnosticoutput: 1:99 spamdiagnosticmetadata: NSPM MIME-Version: 1.0 X-OriginatorOrg: vmware.com X-MS-Exchange-CrossTenant-originalarrivaltime: 12 Dec 2016 22:20:52.0271 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0 X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY1PR0501MB1338 X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org X-Content-Filtered-By: Mailman/MimeDel 2.1.12 Subject: [ovs-dev] [PATCH v2] Windows: Implement Hyper-V VIF discovery agent. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org Signed-off-by: Yin Lin --- windows/OvsDiscoveryAgent/App.config | 18 ++ windows/OvsDiscoveryAgent/OvsDiscoveryAgent.csproj | 83 ++++++++ windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln | 34 +++ windows/OvsDiscoveryAgent/OvsDiscoveryService.cs | 67 ++++++ windows/OvsDiscoveryAgent/OvsSwitchMonitor.cs | 73 +++++++ windows/OvsDiscoveryAgent/OvsVsctl.cs | 82 ++++++++ windows/OvsDiscoveryAgent/Program.cs | 26 +++ .../OvsDiscoveryAgent/Properties/AssemblyInfo.cs | 36 ++++ .../Properties/Settings.Designer.cs | 38 ++++ .../OvsDiscoveryAgent/Properties/Settings.settings | 9 + windows/OvsDiscoveryAgent/VirtualAdapter.cs | 36 ++++ windows/OvsDiscoveryAgent/VirtualAdapterManager.cs | 228 +++++++++++++++++++++ windows/OvsDiscoveryAgent/VirtualAdapterMonitor.cs | 109 ++++++++++ windows/OvsDiscoveryAgent/WmiMonitor.cs | 193 +++++++++++++++++ windows/OvsDiscoveryAgent/WqlCondition.cs | 168 +++++++++++++++ windows/OvsDiscoveryAgent/WqlHelper.cs | 130 ++++++++++++ windows/OvsDiscoveryAgent/WqlObject.cs | 133 ++++++++++++ windows/automake.mk | 21 ++ 18 files changed, 1484 insertions(+) create mode 100644 windows/OvsDiscoveryAgent/App.config create mode 100644 windows/OvsDiscoveryAgent/OvsDiscoveryAgent.csproj create mode 100644 windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln create mode 100644 windows/OvsDiscoveryAgent/OvsDiscoveryService.cs create mode 100644 windows/OvsDiscoveryAgent/OvsSwitchMonitor.cs create mode 100644 windows/OvsDiscoveryAgent/OvsVsctl.cs create mode 100644 windows/OvsDiscoveryAgent/Program.cs create mode 100644 windows/OvsDiscoveryAgent/Properties/AssemblyInfo.cs create mode 100644 windows/OvsDiscoveryAgent/Properties/Settings.Designer.cs create mode 100644 windows/OvsDiscoveryAgent/Properties/Settings.settings create mode 100644 windows/OvsDiscoveryAgent/VirtualAdapter.cs create mode 100644 windows/OvsDiscoveryAgent/VirtualAdapterManager.cs create mode 100644 windows/OvsDiscoveryAgent/VirtualAdapterMonitor.cs create mode 100644 windows/OvsDiscoveryAgent/WmiMonitor.cs create mode 100644 windows/OvsDiscoveryAgent/WqlCondition.cs create mode 100644 windows/OvsDiscoveryAgent/WqlHelper.cs create mode 100644 windows/OvsDiscoveryAgent/WqlObject.cs diff --git a/windows/OvsDiscoveryAgent/App.config b/windows/OvsDiscoveryAgent/App.config new file mode 100644 index 0000000..caf9613 --- /dev/null +++ b/windows/OvsDiscoveryAgent/App.config @@ -0,0 +1,18 @@ + + + + +
+ + + + + + + + + ovs-int + + + + \ No newline at end of file diff --git a/windows/OvsDiscoveryAgent/OvsDiscoveryAgent.csproj b/windows/OvsDiscoveryAgent/OvsDiscoveryAgent.csproj new file mode 100644 index 0000000..24c69bb --- /dev/null +++ b/windows/OvsDiscoveryAgent/OvsDiscoveryAgent.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F} + Exe + Properties + OvsDiscoveryAgent + OvsDiscoveryAgent + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + Component + + + + + + + + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + \ No newline at end of file diff --git a/windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln b/windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln new file mode 100644 index 0000000..b3d6371 --- /dev/null +++ b/windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OvsDiscoveryAgent", "OvsDiscoveryAgent.csproj", "{2563C1BE-B240-4F63-84C5-01D98D015A3F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Debug|x64.ActiveCfg = Debug|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Debug|x64.Build.0 = Debug|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Debug|x86.ActiveCfg = Debug|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Debug|x86.Build.0 = Debug|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Release|Any CPU.Build.0 = Release|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Release|x64.ActiveCfg = Release|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Release|x64.Build.0 = Release|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Release|x86.ActiveCfg = Release|Any CPU + {2563C1BE-B240-4F63-84C5-01D98D015A3F}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/windows/OvsDiscoveryAgent/OvsDiscoveryService.cs b/windows/OvsDiscoveryAgent/OvsDiscoveryService.cs new file mode 100644 index 0000000..b41ecc8 --- /dev/null +++ b/windows/OvsDiscoveryAgent/OvsDiscoveryService.cs @@ -0,0 +1,67 @@ +using System.ServiceProcess; +using System.Threading; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace OvsDiscoveryAgent +{ + public class OvsDiscoveryService: ServiceBase + { +#region Console related functions + [DllImport("Kernel32")] + private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add); + + private delegate bool EventHandler(CtrlType sig); + static EventHandler _handler; + + enum CtrlType + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 + } + + private bool Handler(CtrlType sig) + { + switch (sig) + { + case CtrlType.CTRL_C_EVENT: + case CtrlType.CTRL_LOGOFF_EVENT: + case CtrlType.CTRL_SHUTDOWN_EVENT: + case CtrlType.CTRL_CLOSE_EVENT: + default: + OnStop(); + return false; + } + } + + /// + /// Start the service as a console application. + /// + /// Startup parameters. + public void Start(string[] args) + { + _handler += Handler; + OnStart(args); + (new ManualResetEvent(false)).WaitOne(); + } +#endregion + + protected override void OnStart(string[] args) + { + var worker = new BackgroundWorker(); + worker.DoWork += (s, e) => + { + VirtualAdapterManager.Instance.Start(); + }; + worker.RunWorkerAsync(); + } + + protected override void OnStop() + { + VirtualAdapterManager.Instance.Stop(); + } + } +} diff --git a/windows/OvsDiscoveryAgent/OvsSwitchMonitor.cs b/windows/OvsDiscoveryAgent/OvsSwitchMonitor.cs new file mode 100644 index 0000000..77f1b7f --- /dev/null +++ b/windows/OvsDiscoveryAgent/OvsSwitchMonitor.cs @@ -0,0 +1,73 @@ +using System.Management; + +namespace OvsDiscoveryAgent +{ + public class OvsSwitch + { + public string Id { get; set; } + public override string ToString() + { + return "{" + Id + "}"; + } + } + public class OvsSwitchMonitor : WmiMonitor + { + #region Consts + private static readonly string ovsExtentionName = "Open vSwitch Extension"; + private static WqlCondition isOvsExtensionCondition = new WqlBasicCondition(WqlColumns.ElementName, ovsExtentionName); + private static readonly WqlCondition isEnabledCondition = new WqlBasicCondition(WqlColumns.EnabledState, (int)EnabledState.Enabled); + #endregion + protected OvsSwitchMonitor() { } + private static OvsSwitchMonitor monitorInstance; + public static OvsSwitchMonitor Instance + { + get + { + if (monitorInstance == null) + { + monitorInstance = new OvsSwitchMonitor(); + } + return monitorInstance; + } + } + + protected override ManagementObjectCollection QueryItems() + { + return WqlHelper.QueryAll(WqlTables.EthernetSwitchExtension, + isOvsExtensionCondition & isEnabledCondition, + new string[] { WqlColumns.SystemName, WqlColumns.EnabledState }); + } + protected override string GetItemId(OvsSwitch item) + { + return item.Id; + } + + protected override OvsSwitch ConvertItem(ManagementBaseObject mbo) + { + if (mbo == null) return null; + if ((EnabledState)((ushort)mbo[WqlColumns.EnabledState]) != EnabledState.Enabled) + { + return null; + } + var result = new OvsSwitch(); + result.Id = mbo[WqlColumns.SystemName] as string; + return result; + } + + protected override bool IsSameItem(OvsSwitch oldItem, OvsSwitch newItem) + { + bool oldEnabled = (oldItem != null); + bool newEnabled = (newItem != null); + if (oldEnabled != newEnabled) return false; + if (oldEnabled && oldItem.Id != newItem.Id) return false; + return true; + } + + protected override ManagementEventWatcher CreateEventWatcher() + { + return WqlHelper.GetEventWatcher(WqlEventType.Operation, + WqlTables.EthernetSwitchExtension, + isOvsExtensionCondition); + } + } +} diff --git a/windows/OvsDiscoveryAgent/OvsVsctl.cs b/windows/OvsDiscoveryAgent/OvsVsctl.cs new file mode 100644 index 0000000..3181b3b --- /dev/null +++ b/windows/OvsDiscoveryAgent/OvsVsctl.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OvsDiscoveryAgent +{ + /// + /// Provide basic ovs-vsctl functionality. + /// + /// + /// TODO: This class assumes that ovs-vsctl is in the default PATH and calls + /// it as an external process. In its final format, this class should be a + /// wrapper around ovs-vsctl.c and built as a C++ CLR DLL project. + /// + public static class OvsVsctl + { + private struct CommandResult + { + public int ExitCode; + public string OutputMessage; + public string ErrorMessage; + } + private static Process RunCommandAsync(string command, string args) + { + Trace.TraceInformation("[Console] {0} {1}", command, args); + Process process = new System.Diagnostics.Process(); + ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(); + startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + startInfo.FileName = command; + startInfo.Arguments = args; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + process.StartInfo = startInfo; + process.Start(); + return process; + } + private static CommandResult RunCommand(string command, string args) + { + int maxTimeoutMs = 1000; + var p = RunCommandAsync(command, args); + var result = new CommandResult(); + if (!p.WaitForExit(maxTimeoutMs)) + { + p.Kill(); + string errorMsg = string.Format("Command '{0} {1}' did not return within {2} milliseconds.", + command, args, maxTimeoutMs); + Trace.TraceError(errorMsg); + result.ExitCode = -1; + result.ErrorMessage = errorMsg; + } + else + { + result.ExitCode = p.ExitCode; + result.OutputMessage = p.StandardOutput.ReadToEnd(); + result.ErrorMessage = p.StandardError.ReadToEnd(); + Trace.TraceInformation("Command '{0} {1}' returns {2}, stdout='{3}', stderr='{4}'", + command, args, p.ExitCode, result.OutputMessage, result.ErrorMessage); + } + return result; + } + public static void AddPort(string bridge, string port, string[] settings, bool mayExist = true) + { + // Example: ovs-vsctl add-port -- set interface external_ids:iface-id= + // TODO: There is a bug in ovs kernel that result in "ovs-vsctl add-port" returning an error + // when VM has not been powered on. We are ignoring error for now. + RunCommand("ovs-vsctl.exe", string.Join(" ", mayExist?"--may-exist":string.Empty, + "add-port", bridge, port, string.Join(" ", settings))); + } + public static void DeletePort(string bridge, string port) + { + // Example: ovs-vsctl del-port + // TODO: There is a bug in ovs userspace that result in "ovs-vsctl del-port" always hanging forever + // when OVS extension has been disabled. This results in we always have to kill the process when + // we delete the ports connected to the disabled switch. + RunCommand("ovs-vsctl.exe", string.Format("del-port {0} {1}", bridge, port)); + } + } +} diff --git a/windows/OvsDiscoveryAgent/Program.cs b/windows/OvsDiscoveryAgent/Program.cs new file mode 100644 index 0000000..ec7fa65 --- /dev/null +++ b/windows/OvsDiscoveryAgent/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.ServiceProcess; + +namespace OvsDiscoveryAgent +{ + class Program + { + static void Main(string[] args) + { + if (!Environment.UserInteractive) + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new OvsDiscoveryService() + }; + ServiceBase.Run(ServicesToRun); + } + else + { + OvsDiscoveryService service = new OvsDiscoveryService(); + service.Start(args); + } + } + } +} diff --git a/windows/OvsDiscoveryAgent/Properties/AssemblyInfo.cs b/windows/OvsDiscoveryAgent/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..735e04c --- /dev/null +++ b/windows/OvsDiscoveryAgent/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OvsDiscoveryAgent")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OvsDiscoveryAgent")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2563c1be-b240-4f63-84c5-01d98d015a3f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/windows/OvsDiscoveryAgent/Properties/Settings.Designer.cs b/windows/OvsDiscoveryAgent/Properties/Settings.Designer.cs new file mode 100644 index 0000000..42bbeed --- /dev/null +++ b/windows/OvsDiscoveryAgent/Properties/Settings.Designer.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OvsDiscoveryAgent.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("ovs-int")] + public string OvsBridge { + get { + return ((string)(this["OvsBridge"])); + } + set { + this["OvsBridge"] = value; + } + } + } +} diff --git a/windows/OvsDiscoveryAgent/Properties/Settings.settings b/windows/OvsDiscoveryAgent/Properties/Settings.settings new file mode 100644 index 0000000..73e48a6 --- /dev/null +++ b/windows/OvsDiscoveryAgent/Properties/Settings.settings @@ -0,0 +1,9 @@ + + + + + + ovs-int + + + \ No newline at end of file diff --git a/windows/OvsDiscoveryAgent/VirtualAdapter.cs b/windows/OvsDiscoveryAgent/VirtualAdapter.cs new file mode 100644 index 0000000..6b42e6d --- /dev/null +++ b/windows/OvsDiscoveryAgent/VirtualAdapter.cs @@ -0,0 +1,36 @@ +using System.Management; + +namespace OvsDiscoveryAgent +{ + public class VirtualAdapter + { + public VirtualAdapter() + { + SwitchId = OvsPortName = Id = VmId = string.Empty; + } + public VirtualAdapter(string id, string vmId, string switchId, string ovsPort) + { + Id = id; + SwitchId = switchId; + OvsPortName = ovsPort; + } + public string SwitchId { get; set; } + public string OvsPortName { get; set; } + public string Id { get; set; } + public string VmId { get; set; } + public ManagementBaseObject SourceWmiObject { get; set; } + public override bool Equals(object obj) + { + var otherAdapter = obj as VirtualAdapter; + if (otherAdapter == null) return false; + return SwitchId == otherAdapter.SwitchId && + OvsPortName == otherAdapter.OvsPortName && + Id == otherAdapter.Id && + VmId == otherAdapter.VmId; + } + public override int GetHashCode() + { + return Id.GetHashCode(); + } + } +} diff --git a/windows/OvsDiscoveryAgent/VirtualAdapterManager.cs b/windows/OvsDiscoveryAgent/VirtualAdapterManager.cs new file mode 100644 index 0000000..eac0b00 --- /dev/null +++ b/windows/OvsDiscoveryAgent/VirtualAdapterManager.cs @@ -0,0 +1,228 @@ +using System; +using System.Diagnostics; +using System.Management; + +namespace OvsDiscoveryAgent +{ + public class VirtualAdapterManager + { + protected VirtualAdapterManager() { } + private static VirtualAdapterManager managerInstance; + public static VirtualAdapterManager Instance + { + get + { + if (managerInstance == null) + { + managerInstance = new VirtualAdapterManager(); + } + return managerInstance; + } + } + public void Start() + { + OvsSwitchMonitor.Instance.Subscribe(); + VirtualAdapterMonitor.Instance.Subscribe(); + FullSync(); + OvsSwitchMonitor.Instance.ItemUpdated += OvsSwitchMonitor_ItemUpdated; + VirtualAdapterMonitor.Instance.ItemUpdated += VirtualAdapterMonitor_ItemUpdated; + } + + protected void FullSync() + { + OvsSwitchMonitor.Instance.LoadAllItems(); + VirtualAdapterMonitor.Instance.LoadAllItems(); + OvsSwitchMonitor.Instance.EnterReadLock(); + VirtualAdapterMonitor.Instance.EnterReadLock(); + try + { + // New port is not connected to an OVS switch, ignoring it. + foreach (var port in VirtualAdapterMonitor.Instance.ItemsMap.Values) + { + if (OvsSwitchMonitor.Instance.Contains(port.SwitchId)) + { + if (string.IsNullOrEmpty(port.OvsPortName)) + { + LinkOvsPort(port); + // Once we link OVS port a Modification event will be triggered. + // Ovs port will be added at that time. + } + CreateOvsPort(port); + } + } + } + finally + { + OvsSwitchMonitor.Instance.ExitReadLock(); + VirtualAdapterMonitor.Instance.ExitReadLock(); + } + } + + protected void CreateOvsPort(VirtualAdapter adapter) + { + if (string.IsNullOrEmpty(adapter.OvsPortName)) return; + var settings = new string[] { "--", "set", "interface", adapter.OvsPortName, + "external_ids:vm-id=" + adapter.VmId }; + Trace.TraceInformation("ovs-vsctl: Adding OVS port {0} to bridge {1} with arguments '{2}'", + adapter.OvsPortName, Properties.Settings.Default.OvsBridge, + string.Join(" ", settings)); + OvsVsctl.AddPort(Properties.Settings.Default.OvsBridge, adapter.OvsPortName, settings); + } + + protected void DeleteOvsPort(VirtualAdapter adapter) + { + // Old port is not connected to an OVS switch, ignoring it. + if (string.IsNullOrEmpty(adapter.OvsPortName)) return; + Trace.TraceInformation("ovs-vsctl: Deleting OVS port {0} from bridge {1}", + adapter.OvsPortName, Properties.Settings.Default.OvsBridge); + OvsVsctl.DeletePort(Properties.Settings.Default.OvsBridge, adapter.OvsPortName); + } + + protected void AssignOvsPortName(VirtualAdapter adapter) + { + // TODO: Find a more user friendly ovs port name than its UUID. + adapter.OvsPortName = adapter.Id; + } + + protected void LinkOvsPort(VirtualAdapter adapter) + { + ManagementScope scope; + var vsms = WqlHelper.GetServiceObject(WqlTables.VirtualSystemManagementService, out scope); + var inParams = vsms.GetMethodParameters(WqlMethods.ModifyResourceSettings); + var newObj = (ManagementBaseObject)adapter.SourceWmiObject.Clone(); + Trace.Assert(string.IsNullOrEmpty(adapter.OvsPortName)); + AssignOvsPortName(adapter); + newObj[WqlColumns.ElementName] = adapter.OvsPortName; + string[] resources = new string[1]; + resources[0] = newObj.GetText(TextFormat.CimDtd20); + inParams["ResourceSettings"] = resources; + var outParams = vsms.InvokeMethod(WqlMethods.ModifyResourceSettings, inParams, null); + if (((UInt32)outParams["ReturnValue"] == ReturnCode.Started && WqlHelper.JobCompleted(outParams, scope)) || + (UInt32)outParams["ReturnValue"] == ReturnCode.Completed) + { + Trace.TraceInformation("Set OVS port {0} -> {1} completed successfully.", + adapter.OvsPortName, adapter.Id); + } + else + { + Trace.TraceError("Failed to set OVS port {0} -> {1}.", adapter.OvsPortName, adapter.Id); + } + } + + protected void LinkOrCreateOvsPort(VirtualAdapter newPort) + { + if (string.IsNullOrEmpty(newPort.OvsPortName)) + { + LinkOvsPort(newPort); + // Once we link OVS port a Modification event will be triggered. + // Ovs port will be added at that time. + } + else + { + // This handles the case where user add a Hyper-V VIF with OVS port + // specified. + CreateOvsPort(newPort); + } + } + + protected bool IsOvsConnected(VirtualAdapter adapter) + { + return OvsSwitchMonitor.Instance.Contains(adapter.SwitchId); + } + + private void VirtualAdapterMonitor_ItemUpdated(object sender, WmiMonitorEventArgs e) + { + OvsSwitchMonitor.Instance.EnterReadLock(); + VirtualAdapterMonitor.Instance.EnterReadLock(); + try + { + var newPort = e.NewValue; + var oldPort = e.OldValue; + switch (e.Operation) + { + case ItemOperation.Add: + // New port is not connected to an OVS switch, ignoring it. + if (IsOvsConnected(newPort)) + { + LinkOrCreateOvsPort(newPort); + } + break; + case ItemOperation.Remove: + if (IsOvsConnected(oldPort)) + { + DeleteOvsPort(oldPort); + } + break; + default: + if (oldPort.OvsPortName != newPort.OvsPortName || + oldPort.SwitchId != newPort.SwitchId) + { + if (IsOvsConnected(oldPort)) + { + DeleteOvsPort(e.OldValue); + } + if (IsOvsConnected(newPort)) + { + LinkOrCreateOvsPort(e.NewValue); + } + } + // Currently adapter with same UUID cannot be migrated + // to another VM on Hyper-V. Ignoring the VM ID change case. + break; + } + } + finally + { + VirtualAdapterMonitor.Instance.ExitReadLock(); + OvsSwitchMonitor.Instance.ExitReadLock(); + } + } + + private void OvsSwitchMonitor_ItemUpdated(object sender, WmiMonitorEventArgs e) + { + OvsSwitchMonitor.Instance.EnterReadLock(); + VirtualAdapterMonitor.Instance.EnterReadLock(); + try + { + switch (e.Operation) + { + case ItemOperation.Add: + // New port is not connected to an OVS switch, ignoring it. + foreach (var port in VirtualAdapterMonitor.Instance.ItemsMap.Values) + { + if (port.SwitchId == e.NewValue.Id) + { + LinkOrCreateOvsPort(port); + } + } + break; + case ItemOperation.Remove: + foreach (var port in VirtualAdapterMonitor.Instance.ItemsMap.Values) + { + if (port.SwitchId == e.OldValue.Id) + { + DeleteOvsPort(port); + } + } + break; + default: + Trace.Assert(false, string.Format("Unhandled modification of switch {0} -> {1} detected", + e.OldValue, e.NewValue)); + break; + } + + } + finally + { + VirtualAdapterMonitor.Instance.ExitReadLock(); + OvsSwitchMonitor.Instance.ExitReadLock(); + } + } + + public void Stop() + { + OvsSwitchMonitor.Instance.Unsubscribe(); + VirtualAdapterMonitor.Instance.Unsubscribe(); + } + } +} diff --git a/windows/OvsDiscoveryAgent/VirtualAdapterMonitor.cs b/windows/OvsDiscoveryAgent/VirtualAdapterMonitor.cs new file mode 100644 index 0000000..b725b4e --- /dev/null +++ b/windows/OvsDiscoveryAgent/VirtualAdapterMonitor.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using System.Management; +using System.Threading; +using System.Diagnostics; +using System.Text.RegularExpressions; + + +namespace OvsDiscoveryAgent +{ + public class VirtualAdapterMonitor : WmiMonitor + { + #region Consts + private static readonly string virtualAdapterDefaultElementName = "Dynamic Ethernet Switch Port"; + private static readonly string switchNamePattern = "Msvm_VirtualEthernetSwitch.CreationClassName=\"Msvm_VirtualEthernetSwitch\",Name=\"([A-Za-z0-9\\-]+)\""; + private static readonly string virtualAdapterCaption = "Ethernet Connection Settings"; + public static WqlCondition isVirtualAdapterCondition = new WqlBasicCondition(WqlColumns.Caption, virtualAdapterCaption); + public static readonly WqlCondition isEnabledCondition = new WqlBasicCondition(WqlColumns.EnabledState, (int)EnabledState.Enabled); + #endregion + #region Fields + private static VirtualAdapterMonitor monitorInstance; + private ReaderWriterLockSlim adapterMapLock = new ReaderWriterLockSlim(); + private Dictionary adapterMap = new Dictionary(); + #endregion + protected VirtualAdapterMonitor() { } + public static VirtualAdapterMonitor Instance + { + get + { + if (monitorInstance == null) + { + monitorInstance = new VirtualAdapterMonitor(); + } + return monitorInstance; + } + } + protected override ManagementObjectCollection QueryItems() + { + return WqlHelper.QueryAll(WqlTables.EthernetPortAllocationSettingData, + isVirtualAdapterCondition & isEnabledCondition); + } + + protected override ManagementEventWatcher CreateEventWatcher() + { + return WqlHelper.GetEventWatcher( + WqlEventType.Operation, WqlTables.EthernetPortAllocationSettingData, + isVirtualAdapterCondition); + } + + protected override string GetItemId(VirtualAdapter item) + { + return item.Id; + } + + protected override VirtualAdapter ConvertItem(ManagementBaseObject mbo) + { + if (mbo == null) return null; + var o = mbo[WqlColumns.EnabledState]; + if (((EnabledState)(ushort)o) != EnabledState.Enabled) return null; + VirtualAdapter result = new VirtualAdapter(); + o = mbo[WqlColumns.InstanceID]; + if (o != null) + { + var ids = o.ToString().Split(':', '\\'); + if (ids.Count() < 3) + { + Trace.TraceError("Unable to parse virutal adapter ID: {0}", o.ToString()); + } + else + { + result.VmId = ids[1]; + result.Id = ids[2]; + } + } + o = mbo[WqlColumns.ElementName]; + if (o != null) + { + var name = o.ToString(); + if (name != virtualAdapterDefaultElementName) + { + result.OvsPortName = name; + } + } + var resources = mbo[WqlColumns.HostResource] as string[]; + if (resources != null) + { + foreach (string res in resources) + { + Match match = Regex.Match(res, switchNamePattern, RegexOptions.IgnoreCase); + if (match.Success) + { + result.SwitchId = match.Groups[1].Value; + } + } + } + result.SourceWmiObject = mbo; + return result; + } + + protected override bool IsSameItem(VirtualAdapter oldItem, VirtualAdapter newItem) + { + bool oldEnabled = (oldItem != null); + bool newEnabled = (newItem != null); + if (oldEnabled != newEnabled) return false; + if (oldEnabled && !oldItem.Equals(newItem)) return false; + return true; + } + } +} diff --git a/windows/OvsDiscoveryAgent/WmiMonitor.cs b/windows/OvsDiscoveryAgent/WmiMonitor.cs new file mode 100644 index 0000000..5a59fb9 --- /dev/null +++ b/windows/OvsDiscoveryAgent/WmiMonitor.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Management; +using System.Threading; +using System.Diagnostics; + +namespace OvsDiscoveryAgent +{ + public enum ItemOperation + { + Add, + Remove, + Modify + } + public class WmiMonitorEventArgs : EventArgs + { + public T OldValue { get; set; } + public T NewValue { get; set; } + public ItemOperation Operation { get; set; } + public ManagementBaseObject NewEvent { get; set; } + } + public delegate void WmiMonitorEventHandler(object sender, WmiMonitorEventArgs e); + public abstract class WmiMonitor where T : class + { + protected ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); + public Dictionary ItemsMap { get; private set; } + protected WmiMonitor() + { + ItemsMap = new Dictionary(); + } + public event WmiMonitorEventHandler ItemUpdated; + protected abstract ManagementObjectCollection QueryItems(); + protected abstract ManagementEventWatcher CreateEventWatcher(); + protected abstract string GetItemId(T item); + protected abstract T ConvertItem(ManagementBaseObject mbo); + public void EnterReadLock() + { + cacheLock.EnterReadLock(); + } + public void ExitReadLock() + { + cacheLock.ExitReadLock(); + } + public bool Contains(string id) + { + bool needLock = !cacheLock.IsReadLockHeld; + if (needLock) + { + cacheLock.EnterReadLock(); + } + try + { + return ItemsMap.ContainsKey(id); + } + finally + { + if (needLock) + { + cacheLock.ExitReadLock(); + } + } + } + private bool AddOrDeleteItem(T item, bool isAdd) + { + if (item == null) + { + Trace.TraceError("Item {0} is null", typeof(T).ToString()); + return false; + } + string id = GetItemId(item); + if (string.IsNullOrWhiteSpace(id)) + { + Trace.TraceError("Cannot find ID for {0}", item.ToString()); + return false; + } + if (isAdd) + { + if (ItemsMap.ContainsKey(id)) + { + Trace.TraceError("Duplicate ID {0} found for {1}", id.ToString(), + typeof(T).ToString()); + return false; + } + else + { + ItemsMap.Add(id, item); + return true; + } + } + else + { + if (!ItemsMap.ContainsKey(id)) + { + Trace.TraceError("Unable to find {1} that has ID {0}", id.ToString(), + typeof(T).ToString()); + return false; + } + else + { + ItemsMap.Remove(id); + return true; + } + } + } + public void LoadAllItems() + { + var result = QueryItems(); + cacheLock.EnterWriteLock(); + try + { + ItemsMap.Clear(); + foreach (var mo in result) + { + AddOrDeleteItem(ConvertItem(mo), true); + } + } + finally + { + cacheLock.ExitWriteLock(); + } + } + + public void Subscribe() + { + if (EventWatcher != null) return; + EventWatcher = CreateEventWatcher(); + EventWatcher.EventArrived += EventWatcher_EventArrived; + EventWatcher.Start(); + } + + public void Unsubscribe() + { + if (EventWatcher == null) return; + EventWatcher.EventArrived -= EventWatcher_EventArrived; + EventWatcher.Stop(); + EventWatcher = null; + } + + protected abstract bool IsSameItem(T oldItem, T newItem); + + private void EventWatcher_EventArrived(object sender, EventArrivedEventArgs e) + { + var changed = WqlHelper.ParseEventArrivedEventArgs(e); + T oldItem = default(T), newItem = default(T); + if (changed.Item1 != null) + { + oldItem = ConvertItem(changed.Item1); + } + if (changed.Item2 != null) + { + newItem = ConvertItem(changed.Item2); + } + if (IsSameItem(oldItem, newItem)) + { + //Nothing relevant is changed. + return; + } + var args = new WmiMonitorEventArgs(); + bool updateResult; + args.NewEvent = e.NewEvent; + args.OldValue = oldItem; + args.NewValue = newItem; + cacheLock.EnterWriteLock(); + try + { + if (!IsSameItem(oldItem, null) && IsSameItem(newItem, null)) + { + args.Operation = ItemOperation.Remove; + updateResult = AddOrDeleteItem(oldItem, false); + } + else if (IsSameItem(oldItem, null) && !IsSameItem(newItem, null)) + { + args.Operation = ItemOperation.Add; + updateResult = AddOrDeleteItem(newItem, true); + } + else + { + args.Operation = ItemOperation.Modify; + updateResult = AddOrDeleteItem(oldItem, false) & AddOrDeleteItem(newItem, true); + } + } + finally + { + cacheLock.ExitWriteLock(); + } + if (updateResult) + { + ItemUpdated?.Invoke(this, args); + } + } + protected ManagementEventWatcher EventWatcher { get; set; } + } +} diff --git a/windows/OvsDiscoveryAgent/WqlCondition.cs b/windows/OvsDiscoveryAgent/WqlCondition.cs new file mode 100644 index 0000000..1fac805 --- /dev/null +++ b/windows/OvsDiscoveryAgent/WqlCondition.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OvsDiscoveryAgent +{ + public enum WqlLogicalOperator + { + AND, + OR + } + public enum WqlValueOperator + { + Equal, + NotEqual, + InstanceOf, + Like + } + public abstract class WqlCondition + { + public static WqlCondition operator &(WqlCondition cond1, WqlCondition cond2) + { + return new WqlLogicalCondition(cond1, WqlLogicalOperator.AND, cond2); + } + public static WqlCondition operator |(WqlCondition cond1, WqlCondition cond2) + { + return new WqlLogicalCondition(cond1, WqlLogicalOperator.OR, cond2); + } + public abstract string ToString(string owner); + } + public class WqlAlwaysTrueCondition : WqlCondition + { + public override string ToString() + { + return string.Empty; + } + public override string ToString(string owner) + { + return string.Empty; + } + } + public class UserDefinedCondition : WqlCondition + { + public UserDefinedCondition(string condition) + { + Condition = condition; + } + + public string Condition { get; set; } + public override string ToString() + { + return Condition; + } + public override string ToString(string owner) + { + return Condition; + } + } + public class WqlLogicalCondition : WqlCondition + { + public WqlLogicalCondition(WqlCondition cond1, WqlLogicalOperator oper, WqlCondition cond2) + { + Condition1 = cond1; + Condition2 = cond2; + Operator = oper; + } + public WqlLogicalOperator Operator { get; set; } + public WqlCondition Condition1 { get; set; } + public WqlCondition Condition2 { get; set; } + private string ConvertConditionToString(WqlCondition cond, string owner) + { + if (owner == null) + { + return (cond is WqlLogicalCondition) ? + string.Format("({0})", cond.ToString()) : + cond.ToString(); + } + else + { + return (cond is WqlLogicalCondition) ? + string.Format("({0})", cond.ToString(owner)) : + cond.ToString(owner); + } + } + public override string ToString() + { + return ToString(null); + } + public override string ToString(string owner) + { + var condStrs = new List(); + var condStr1 = ConvertConditionToString(Condition1, owner); + var condStr2 = ConvertConditionToString(Condition2, owner); + if (!string.IsNullOrWhiteSpace(condStr1)) condStrs.Add(condStr1); + if (!string.IsNullOrWhiteSpace(condStr2)) condStrs.Add(condStr2); + return string.Join(Operator == WqlLogicalOperator.AND ? " AND " : " OR ", + condStrs); + } + } + public class WqlBasicCondition : WqlCondition + { + public WqlBasicCondition() { } + public WqlBasicCondition(string key, T value) + { + Key = key; + Value = value; + Operator = WqlValueOperator.Equal; + } + public WqlBasicCondition(string key, WqlValueOperator oper, T value) + { + Key = key; + Value = value; + Operator = oper; + } + public WqlValueOperator Operator { get; set; } + public string Key { get; set; } + public T Value { get; set; } + public static string ToString(WqlValueOperator oper) + { + switch (oper) + { + case WqlValueOperator.Equal: + return "="; + case WqlValueOperator.InstanceOf: + return "ISA"; + case WqlValueOperator.Like: + return "LIKE"; + case WqlValueOperator.NotEqual: + return "<>"; + default: + throw new NotImplementedException("Unsupported operator: " + oper); + } + } + + public override string ToString() + { + return ToString(null); + } + public override string ToString(string owner) + { + string key = (string.IsNullOrWhiteSpace(owner) ? string.Empty : owner + ".") + Key; + if (Value is string || Value.ToString().Contains(' ')) + { + return string.Format("{0} {1} '{2}'", key, ToString(Operator), Value); + } + else + { + return string.Join(" ", key, ToString(Operator), Value); + } + } + } + public class WqlBasicCondition : WqlBasicCondition + { + public WqlBasicCondition() { } + public WqlBasicCondition(string key, string value) + { + Key = key; + Value = value; + Operator = WqlValueOperator.Equal; + } + public WqlBasicCondition(string key, WqlValueOperator oper, string value) + { + Key = key; + Value = value; + Operator = oper; + } + } +} diff --git a/windows/OvsDiscoveryAgent/WqlHelper.cs b/windows/OvsDiscoveryAgent/WqlHelper.cs new file mode 100644 index 0000000..c959c76 --- /dev/null +++ b/windows/OvsDiscoveryAgent/WqlHelper.cs @@ -0,0 +1,130 @@ +using System; +using System.Management; +using System.Diagnostics; + +namespace OvsDiscoveryAgent +{ + public class WqlHelper + { + private static readonly string selectFromQuery = "SELECT {0} FROM {1}"; + private static readonly string selectFromWhereQuery = "SELECT {0} FROM {1} WHERE {2}"; + public static ManagementEventWatcher GetEventWatcher(WqlEventType eventType, WqlTable tableName) + { + return GetEventWatcher(eventType, tableName, new WqlAlwaysTrueCondition()); + } + public static ManagementEventWatcher GetEventWatcher(WqlEventType eventType, WqlTable tableName, + string additionalConditions) + { + return GetEventWatcher(eventType, tableName, new UserDefinedCondition(additionalConditions)); + } + public static ManagementEventWatcher GetEventWatcher(WqlEventType eventType, WqlTable tableName, + WqlCondition additionalCondition) + { + var scope = new ManagementScope(tableName.ScopeName); + scope.Connect(); + var condTargetIsTable = new WqlBasicCondition(WqlColumns.TargetInstance, WqlValueOperator.InstanceOf, + tableName.Name); + var query = new WqlEventQuery(eventType.Name, + TimeSpan.FromSeconds(1), + (condTargetIsTable & new UserDefinedCondition( + additionalCondition.ToString(WqlColumns.TargetInstance))).ToString()); + return new ManagementEventWatcher(scope, query); + } + public static ManagementObjectCollection QueryAll(WqlTable tableName, WqlCondition condition = null) + { + if (condition == null) { condition = new WqlAlwaysTrueCondition(); } + return QueryAll(tableName, condition.ToString(), null); + } + public static ManagementObjectCollection QueryAll(WqlTable tableName, string condition) + { + return QueryAll(tableName, condition, null); + } + public static ManagementObjectCollection QueryAll(WqlTable tableName, WqlCondition condition, string[] fields) + { + return QueryAll(tableName, condition.ToString(), fields); + } + public static ManagementObjectCollection QueryAll(WqlTable tableName, string condition, string[] fields) + { + string fieldStr = (fields == null || fields.Length == 0) ? "*" : string.Join(",", fields); + string queryStr; + if (string.IsNullOrWhiteSpace(condition)) + { + queryStr = string.Format(selectFromQuery, fieldStr, tableName.Name); + } + else + { + queryStr = string.Format(selectFromWhereQuery, fieldStr, tableName.Name, condition); + } + var searcher = new ManagementObjectSearcher(tableName.ScopeName, queryStr); + return searcher.Get(); + } + /// + /// Common utility function to get a service object + /// + /// Serivce object name + /// Service object with the given name. + public static ManagementObject GetServiceObject(WqlTable tableName, out ManagementScope scope) + { + scope = new ManagementScope(tableName.ScopeName); + scope.Connect(); + ManagementPath wmiPath = new ManagementPath(tableName.Name); + ManagementClass serviceClass = new ManagementClass(scope, wmiPath, null); + ManagementObjectCollection services = serviceClass.GetInstances(); + + ManagementObject serviceObject = null; + + foreach (ManagementObject service in services) + { + serviceObject = service; + } + return serviceObject; + } + public static Tuple ParseEventArrivedEventArgs(EventArrivedEventArgs args) + { + ManagementBaseObject oldMbo = null; + ManagementBaseObject newMbo = null; + if (WqlEventType.Modification == args) + { + oldMbo = (ManagementBaseObject)args.NewEvent[WqlColumns.PreviousInstance]; + newMbo = (ManagementBaseObject)args.NewEvent[WqlColumns.TargetInstance]; + } + else if (WqlEventType.Creation == args) + { + newMbo = (ManagementBaseObject)args.NewEvent[WqlColumns.TargetInstance]; + } + else if (WqlEventType.Deletion == args) + { + oldMbo = (ManagementBaseObject)args.NewEvent[WqlColumns.TargetInstance]; + } + return new Tuple(oldMbo, newMbo); + } + public static bool JobCompleted(ManagementBaseObject outParams, ManagementScope scope) + { + bool jobCompleted = true; + + //Retrieve msvc_StorageJob path. This is a full wmi path + string JobPath = (string)outParams["Job"]; + ManagementObject Job = new ManagementObject(scope, new ManagementPath(JobPath), null); + //Try to get storage job information + Job.Get(); + while ((UInt16)Job["JobState"] == JobState.Starting + || (UInt16)Job["JobState"] == JobState.Running) + { + Trace.TraceInformation("In progress... {0}% completed.", Job["PercentComplete"]); + System.Threading.Thread.Sleep(1000); + Job.Get(); + } + + //Figure out if job failed + UInt16 jobState = (UInt16)Job["JobState"]; + if (jobState != JobState.Completed) + { + UInt16 jobErrorCode = (UInt16)Job["ErrorCode"]; + Trace.TraceInformation("Error Code:{0}", jobErrorCode); + Trace.TraceInformation("ErrorDescription: {0}", (string)Job["ErrorDescription"]); + jobCompleted = false; + } + return jobCompleted; + } + } +} diff --git a/windows/OvsDiscoveryAgent/WqlObject.cs b/windows/OvsDiscoveryAgent/WqlObject.cs new file mode 100644 index 0000000..148245f --- /dev/null +++ b/windows/OvsDiscoveryAgent/WqlObject.cs @@ -0,0 +1,133 @@ +using System; +using System.Management; + +namespace OvsDiscoveryAgent +{ + public static class WqlColumns + { + public static readonly string SystemName = "SystemName"; + public static readonly string EnabledState = "EnabledState"; + public static readonly string ElementName = "ElementName"; + public static readonly string HostResource = "HostResource"; + public static readonly string InstanceID = "InstanceID"; + public static readonly string Caption = "Caption"; + public static readonly string TargetInstance = "TargetInstance"; + public static readonly string PreviousInstance = "PreviousInstance"; + } + public static class WqlMethods + { + public static readonly string ModifyResourceSettings = "ModifyResourceSettings"; + } + public class WqlObject + { + public string Name { get; private set; } + public WqlObject() { } + public WqlObject(string type) + { + Name = type; + } + public override string ToString() + { + return Name; + } + } + public class WqlEventType : WqlObject + { + public WqlEventType() { } + public WqlEventType(string type) : base(type) { } + public static bool operator ==(WqlEventType type, ManagementBaseObject mbo) + { + return type.Name == mbo.ClassPath.ClassName; + } + public static bool operator !=(WqlEventType type, ManagementBaseObject mbo) + { + return !(type == mbo); + } + public static bool operator == (WqlEventType type, EventArrivedEventArgs args) + { + return type == args.NewEvent; + } + public static bool operator !=(WqlEventType type, EventArrivedEventArgs args) + { + return !(type == args); + } + public override int GetHashCode() + { + return Name.GetHashCode(); + } + public override bool Equals(object obj) + { + return Name == obj.ToString(); + } + public static readonly WqlEventType Operation = new WqlEventType("__InstanceOperationEvent"); + public static readonly WqlEventType Modification = new WqlEventType("__InstanceModificationEvent"); + public static readonly WqlEventType Creation = new WqlEventType("__InstanceCreationEvent"); + public static readonly WqlEventType Deletion = new WqlEventType("__InstanceDeletionEvent"); + } + public class WqlTable : WqlObject + { + public WqlTable() { } + public WqlTable(string scopeName, string tableName) : base(tableName) + { + ScopeName = scopeName; + } + public override string ToString() + { + return string.Format("{0}:{1}", ScopeName, Name); + } + public string ScopeName { get; private set; } + } + + public static class WqlTables + { + private static readonly string virtScope = @"\root\virtualization\v2"; + public static readonly WqlTable SyntheticEthernetPort = new WqlTable(virtScope, "Msvm_SyntheticEthernetPort"); + public static readonly WqlTable EthernetSwitchExtension = new WqlTable(virtScope, "Msvm_EthernetSwitchExtension"); + public static readonly WqlTable EthernetPortAllocationSettingData = new WqlTable(virtScope, "Msvm_EthernetPortAllocationSettingData"); + public static readonly WqlTable VirtualSystemManagementService = new WqlTable(virtScope, "Msvm_VirtualSystemManagementService"); + } + + public enum EnabledState + { + Unknown = 0, + Enabled = 2, + Disabled = 3, + Paused = 32768, + Suspended = 32769, + Starting = 32770, + Snapshotting = 32771, + Saving = 32773, + Stopping = 32774, + Pausing = 32776, + Resuming = 32777, + } + public static class ReturnCode + { + public const UInt32 Completed = 0; + public const UInt32 Started = 4096; + public const UInt32 Failed = 32768; + public const UInt32 AccessDenied = 32769; + public const UInt32 NotSupported = 32770; + public const UInt32 Unknown = 32771; + public const UInt32 Timeout = 32772; + public const UInt32 InvalidParameter = 32773; + public const UInt32 SystemInUse = 32774; + public const UInt32 InvalidState = 32775; + public const UInt32 IncorrectDataType = 32776; + public const UInt32 SystemNotAvailable = 32777; + public const UInt32 OutofMemory = 32778; + } + public static class JobState + { + public const UInt16 New = 2; + public const UInt16 Starting = 3; + public const UInt16 Running = 4; + public const UInt16 Suspended = 5; + public const UInt16 ShuttingDown = 6; + public const UInt16 Completed = 7; + public const UInt16 Terminated = 8; + public const UInt16 Killed = 9; + public const UInt16 Exception = 10; + public const UInt16 Service = 11; + } +} diff --git a/windows/automake.mk b/windows/automake.mk index fa610ec..a003c30 100644 --- a/windows/automake.mk +++ b/windows/automake.mk @@ -55,3 +55,24 @@ EXTRA_DIST += \ windows/ovs-windows-installer/images/dlgbmp.bmp \ windows/ovs-windows-installer/ovs-windows-installer.wixproj +ovs_discovery_agent: all + MSBuild.exe windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln /target:Build /property:Configuration="Release" + +EXTRA_DIST += \ + windows/OvsDiscoveryAgent/App.config \ + windows/OvsDiscoveryAgent/OvsDiscoveryAgent.csproj \ + windows/OvsDiscoveryAgent/OvsDiscoveryAgent.sln \ + windows/OvsDiscoveryAgent/OvsDiscoveryService.cs \ + windows/OvsDiscoveryAgent/OvsSwitchMonitor.cs \ + windows/OvsDiscoveryAgent/OvsVsctl.cs \ + windows/OvsDiscoveryAgent/Program.cs \ + windows/OvsDiscoveryAgent/Properties/AssemblyInfo.cs \ + windows/OvsDiscoveryAgent/Properties/Settings.Designer.cs \ + windows/OvsDiscoveryAgent/Properties/Settings.settings \ + windows/OvsDiscoveryAgent/VirtualAdapter.cs \ + windows/OvsDiscoveryAgent/VirtualAdapterManager.cs \ + windows/OvsDiscoveryAgent/VirtualAdapterMonitor.cs \ + windows/OvsDiscoveryAgent/WmiMonitor.cs \ + windows/OvsDiscoveryAgent/WqlCondition.cs \ + windows/OvsDiscoveryAgent/WqlHelper.cs \ + windows/OvsDiscoveryAgent/WqlObject.cs