/// <summary> /// Starts the controller. /// </summary> /// <param name="k8s">The <see cref="IKubernetes"/> client to use.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public static async Task StartAsync(IKubernetes k8s) { Covenant.Requires <ArgumentNullException>(k8s != null, nameof(k8s)); // Load the configuration settings. var leaderConfig = new LeaderElectionConfig( k8s, @namespace: KubeNamespace.NeonSystem, leaseName: $"{Program.Service.Name}.nodetask", identity: Pod.Name, promotionCounter: Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_promoted", "Leader promotions"), demotionCounter: Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_demoted", "Leader demotions"), newLeaderCounter: Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_newLeader", "Leadership changes")); var options = new ResourceManagerOptions() { IdleInterval = Program.Service.Environment.Get("NODETASK_IDLE_INTERVAL", TimeSpan.FromSeconds(1)), ErrorMinRequeueInterval = Program.Service.Environment.Get("NODETASK_ERROR_MIN_REQUEUE_INTERVAL", TimeSpan.FromSeconds(15)), ErrorMaxRetryInterval = Program.Service.Environment.Get("NODETASK_ERROR_MAX_REQUEUE_INTERVAL", TimeSpan.FromSeconds(60)), IdleCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle", "IDLE events processed."), ReconcileCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle", "RECONCILE events processed."), DeleteCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle", "DELETED events processed."), StatusModifyCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle", "STATUS-MODIFY events processed."), IdleErrorCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle_error", "Failed NodeTask IDLE event processing."), ReconcileErrorCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_reconcile_error", "Failed NodeTask RECONCILE event processing."), DeleteErrorCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_delete_error", "Failed NodeTask DELETE event processing."), StatusModifyErrorCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_statusmodify_error", "Failed NodeTask STATUS-MODIFY events processing.") }; resourceManager = new ResourceManager <V1NeonNodeTask, NodeTaskController>( k8s, options: options, leaderConfig: leaderConfig); await resourceManager.StartAsync(); }
/// <summary> /// Constructor. Note that you should dispose the instance when you're finished with it. /// </summary> /// <param name="addressOrFQDN">The target XenServer IP address or FQDN.</param> /// <param name="username">The user name.</param> /// <param name="password">The password.</param> /// <param name="name">Optionally specifies the XenServer name.</param> /// <param name="logFolder"> /// The folder where log files are to be written, otherwise or <c>null</c> or /// empty if logging is disabled. /// </param> public XenClient(string addressOrFQDN, string username, string password, string name = null, string logFolder = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(username)); if (!IPAddress.TryParse(addressOrFQDN, out var address)) { var hostEntry = Dns.GetHostEntry(addressOrFQDN); if (hostEntry.AddressList.Length == 0) { throw new XenException($"[{addressOrFQDN}] is not a valid IP address or fully qualified domain name of a XenServer host."); } address = hostEntry.AddressList.First(); } var logWriter = (TextWriter)null; if (!string.IsNullOrEmpty(logFolder)) { Directory.CreateDirectory(logFolder); logWriter = new StreamWriter(Path.Combine(logFolder, $"XENSERVER-{addressOrFQDN}.log")); } Address = addressOrFQDN; Name = name; SshProxy = new SshProxy <XenClient>(addressOrFQDN, null, address, SshCredentials.FromUserPassword(username, password), logWriter); SshProxy.Metadata = this; runOptions = RunOptions.IgnoreRemotePath; // Initialize the operation classes. Repository = new RepositoryOperations(this); Template = new TemplateOperations(this); Machine = new MachineOperations(this); }
/// <summary> /// <para> /// Plays a playbook within a specific working directory using <b>neon ansible play -- [args] playbook</b>. /// </para> /// <note> /// This method will have Ansible gather facts by default which can be quite slow. /// Consider using <see cref="PlayInFolderNoGather(string, string, string[])"/> instead /// for unit tests that don't required the facts. /// </note> /// </summary> /// <param name="workDir">The playbook working directory (or <c>null</c> to use a temporary folder).</param> /// <param name="playbook">The playbook text.</param> /// <param name="args">Optional command line arguments to be included in the command.</param> /// <returns>An <see cref="AnsiblePlayResults"/> describing what happened.</returns> /// <remarks> /// <note> /// Use this method for playbooks that need to read or write files. /// </note> /// </remarks> public static AnsiblePlayResults PlayInFolder(string workDir, string playbook, params string[] args) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(playbook)); if (!string.IsNullOrEmpty(workDir)) { Directory.CreateDirectory(workDir); Environment.CurrentDirectory = workDir; File.WriteAllText(Path.Combine(workDir, "play.yaml"), playbook); var response = NeonHelper.ExecuteCapture("neon", new object[] { "ansible", "play", "--noterminal", "--", args, "-vvvv", "play.yaml" }); return(new AnsiblePlayResults(response)); } else { using (var folder = new TempFolder()) { var orgDirectory = Environment.CurrentDirectory; try { Environment.CurrentDirectory = folder.Path; File.WriteAllText(Path.Combine(folder.Path, "play.yaml"), playbook); var response = NeonHelper.ExecuteCapture("neon", new object[] { "ansible", "play", "--noterminal", "--", args, "-vvvv", "play.yaml" }); return(new AnsiblePlayResults(response)); } finally { Environment.CurrentDirectory = orgDirectory; } } } }
/// <summary> /// Constructor. /// </summary> /// <param name="controller">The setup controller.</param> internal SetupClusterStatus(ISetupController controller) { Covenant.Requires <ArgumentNullException>(controller != null, nameof(controller)); this.isClone = false; this.controller = controller; this.cluster = controller.Get <ClusterProxy>(KubeSetupProperty.ClusterProxy); this.GlobalStatus = controller.GlobalStatus; this.globalStatus = this.GlobalStatus; // Initialize the cluster node/host status instances. this.Nodes = new List <SetupNodeStatus>(); foreach (var node in cluster.Nodes) { Nodes.Add(new SetupNodeStatus(node, node.NodeDefinition)); } this.Hosts = new List <SetupNodeStatus>(); foreach (var host in cluster.Hosts) { Hosts.Add(new SetupNodeStatus(host, new object())); } // Initialize the setup steps. this.Steps = new List <SetupStepStatus>(); foreach (var step in controller.GetStepStatus().Where(step => !step.IsQuiet)) { Steps.Add(step); } this.CurrentStep = Steps.SingleOrDefault(step => step.Number == controller.CurrentStepNumber); }
/// <summary> /// Returns the Cadence activity type name to be used for a activity interface or /// implementation class. /// </summary> /// <param name="activityType">The activity interface or implementation type.</param> /// <param name="activityAttribute">Specifies the <see cref="ActivityAttribute"/>.</param> /// <returns>The type name.</returns> /// <remarks> /// <para> /// If <paramref name="activityAttribute"/> is passed and <see cref="ActivityAttribute.Name"/> /// is not <c>null</c> or empty, then the name specified in the attribute is returned. /// </para> /// <para> /// Otherwise, we'll return the fully qualified name of the activity interface /// with the leadting "I" removed. /// </para> /// </remarks> internal static string GetActivityTypeName(Type activityType, ActivityAttribute activityAttribute) { Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType)); if (activityAttribute != null && !string.IsNullOrEmpty(activityAttribute.Name)) { return(activityAttribute.Name); } if (activityType.IsClass) { CadenceHelper.ValidateActivityImplementation(activityType); activityType = CadenceHelper.GetActivityInterface(activityType); } else { CadenceHelper.ValidateActivityInterface(activityType); } var fullName = activityType.FullName; var name = activityType.Name; if (name.StartsWith("I") && name != "I") { // We're going to strip the leading "I" from the unqualified // type name (unless that's the only character). fullName = fullName.Substring(0, fullName.Length - name.Length); fullName += name.Substring(1); } // We need to replace the "+" characters .NET uses for nested types into // "." so the result will be a valid C# type identifier. return(fullName.Replace('+', '.')); }
/// <summary> /// Create the node folders required by neoneKUBE. /// </summary> /// <param name="controller">The setup controller.</param> public void BaseCreateKubeFolders(ISetupController controller) { Covenant.Requires <ArgumentException>(controller != null, nameof(controller)); InvokeIdempotent("base/folders", () => { controller.LogProgress(this, verb: "create", message: "node folders"); var folderScript = $@" set -euo pipefail mkdir -p {KubeNodeFolder.Bin} chmod 750 {KubeNodeFolder.Bin} mkdir -p {KubeNodeFolder.Config} chmod 750 {KubeNodeFolder.Config} mkdir -p {KubeNodeFolder.Setup} chmod 750 {KubeNodeFolder.Setup} mkdir -p {KubeNodeFolder.Helm} chmod 750 {KubeNodeFolder.Helm} mkdir -p {KubeNodeFolder.State} chmod 750 {KubeNodeFolder.State} mkdir -p {KubeNodeFolder.State}/setup chmod 750 {KubeNodeFolder.State}/setup mkdir -p {KubeNodeFolder.NeonRun} chmod 740 {KubeNodeFolder.NeonRun} "; SudoCommand(CommandBundle.FromScript(folderScript), RunOptions.Defaults | RunOptions.FaultOnError); }); }
/// <summary> /// Performs an HTTP <b>PATCH</b> ensuring that a success code was returned. /// </summary> /// <param name="uri">The URI</param> /// <param name="document">The optional object to be uploaded as the request payload.</param> /// <param name="args">The optional query arguments.</param> /// <param name="headers">The Optional HTTP headers.</param> /// <param name="cancellationToken">The optional <see cref="CancellationToken"/>.</param> /// <param name="logActivity">The optional <see cref="LogActivity"/> whose ID is to be included in the request.</param> /// <returns>The <see cref="JsonResponse"/>.</returns> /// <exception cref="SocketException">Thrown for network connectivity issues.</exception> /// <exception cref="HttpException">Thrown when the server responds with an HTTP error status code.</exception> public async Task <JsonResponse> PatchAsync( string uri, object document = null, ArgDictionary args = null, ArgDictionary headers = null, CancellationToken cancellationToken = default, LogActivity logActivity = default) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(uri)); return(await safeRetryPolicy.InvokeAsync( async() => { var requestUri = FormatUri(uri, args); try { var client = this.HttpClient; if (client == null) { throw new ObjectDisposedException(nameof(JsonClient)); } var httpResponse = await client.PatchAsync(requestUri, CreateContent(document), cancellationToken: cancellationToken, headers: headers, activity: logActivity); var jsonResponse = new JsonResponse(requestUri, httpResponse, await httpResponse.Content.ReadAsStringAsync()); jsonResponse.EnsureSuccess(); return jsonResponse; } catch (HttpRequestException e) { throw new HttpException(e, requestUri); } })); }
/// <summary> /// Disables DHCP. /// </summary> /// <param name="controller">The setup controller.</param> public void BaseDisableDhcp(ISetupController controller) { Covenant.Requires <ArgumentException>(controller != null, nameof(controller)); var hostingEnvironment = controller.Get <HostingEnvironment>(KubeSetupProperty.HostingEnvironment); InvokeIdempotent("base/dhcp", () => { controller.LogProgress(this, verb: "disable", message: "dhcp"); var initNetPlanScript = $@" set -euo pipefail rm -rf /etc/netplan/* cat <<EOF > /etc/netplan/no-dhcp.yaml # This file is used to disable the network when a new VM is created # from a template is booted. The [neon-init] service handles network # provisioning in conjunction with the cluster prepare step. # # Cluster prepare inserts a virtual DVD disc with a script that # handles the network configuration which [neon-init] will # execute. network: version: 2 renderer: networkd ethernets: eth0: dhcp4: no EOF "; SudoCommand(CommandBundle.FromScript(initNetPlanScript), RunOptions.Defaults | RunOptions.FaultOnError); }); }
/// <summary> /// Restarts the service unless it never been started. /// </summary> /// <param name="serviceCreator">Callback that creates and returns the new service instance.</param> /// <param name="runningTimeout"> /// Optionally specifies the maximum time the fixture should wait for the service to transition /// to the <see cref="NeonServiceStatus.Running"/> state. This defaults to <b>30 seconds</b>. /// </param> /// <exception cref="TimeoutException"> /// Thrown if the service didn't transition to the running (or terminated) state /// within <paramref name="runningTimeout"/>. /// </exception> /// <remarks> /// <para> /// This method first calls the <paramref name="serviceCreator"/> callback and expects /// it to return a new service instance that has been initialized by setting its environment /// variables and configuration files as required. The callback should not start thge service. /// </para> /// </remarks> public void Restart(Func <TService> serviceCreator = null, TimeSpan runningTimeout = default) { Covenant.Requires <ArgumentNullException>(serviceCreator != null, nameof(serviceCreator)); if (Service != null && Service.Status == NeonServiceStatus.NotStarted) { return; } if (runningTimeout == default) { runningTimeout = defaultRunningTimeout; } TerminateService(); ClearCaches(); Service = serviceCreator(); Covenant.Assert(Service != null); serviceTask = Service.RunAsync(); // Wait for the service to signal that it's running or has terminated. try { NeonHelper.WaitFor(() => Service.Status == NeonServiceStatus.Running || Service.Status == NeonServiceStatus.Terminated, runningTimeout); } catch (TimeoutException) { // Throw a nicer exception that explains what's happened in more detail. throw new TimeoutException($"Service [{Service.Name}]'s [{typeof(TService).Name}.OnRunAsync()] method did not call [{nameof(NeonService.StartedAsync)}()] within [{runningTimeout}] indicating that the service is ready. Ensure that [{nameof(NeonService.StartedAsync)}()] is being called or increase the timeout."); } IsRunning = Service.Status == NeonServiceStatus.Running; }
/// <summary> /// Transmits a signal to an external workflow, starting the workflow if it's not currently running. /// This low-level method accepts a byte array with the already encoded parameters. /// </summary> /// <param name="workflowTypeName">The target workflow type name.</param> /// <param name="signalName">Identifies the signal.</param> /// <param name="signalArgs">Optionally specifies the signal arguments as a byte array.</param> /// <param name="startArgs">Optionally specifies the workflow arguments.</param> /// <param name="options">Optionally specifies the options to be used for starting the workflow when required.</param> /// <returns>The <see cref="WorkflowExecution"/>.</returns> /// <exception cref="EntityNotExistsException">Thrown if the domain does not exist.</exception> /// <exception cref="BadRequestException">Thrown if the request is invalid.</exception> /// <exception cref="InternalServiceException">Thrown for internal Cadence problems.</exception> internal async Task <WorkflowExecution> SignalWorkflowWithStartAsync(string workflowTypeName, string signalName, byte[] signalArgs, byte[] startArgs, WorkflowOptions options) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName), nameof(workflowTypeName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); EnsureNotDisposed(); options = WorkflowOptions.Normalize(this, options); var reply = (WorkflowSignalWithStartReply) await CallProxyAsync( new WorkflowSignalWithStartRequest() { Workflow = workflowTypeName, WorkflowId = options.WorkflowId, Options = options.ToInternal(), SignalName = signalName, SignalArgs = signalArgs, WorkflowArgs = startArgs, Domain = options.Domain }); reply.ThrowOnError(); return(reply.Execution.ToPublic()); }
/// <summary> /// Scans the assembly passed looking for workflow implementations derived from /// <see cref="WorkflowBase"/> and tagged by <see cref="WorkflowAttribute"/> with /// <see cref="WorkflowAttribute.AutoRegister"/> set to <c>true</c> and registers /// them with Cadence. /// </summary> /// <param name="assembly">The target assembly.</param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="TypeLoadException"> /// Thrown for types tagged by <see cref="WorkflowAttribute"/> that are not /// derived from <see cref="WorkflowBase"/>. /// </exception> /// <exception cref="InvalidOperationException">Thrown if one of the tagged classes conflict with an existing registration.</exception> /// <exception cref="WorkflowWorkerStartedException"> /// Thrown if a workflow worker has already been started for the client. You must /// register workflow implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all of your workflow implementations before starting workers. /// </note> /// </remarks> public async Task RegisterAssemblyWorkflowsAsync(Assembly assembly, string domain = null) { await SyncContext.ClearAsync; Covenant.Requires <ArgumentNullException>(assembly != null, nameof(assembly)); EnsureNotDisposed(); foreach (var type in assembly.GetTypes().Where(t => t.IsClass)) { var workflowAttribute = type.GetCustomAttribute <WorkflowAttribute>(); if (workflowAttribute != null && workflowAttribute.AutoRegister) { var workflowTypeName = CadenceHelper.GetWorkflowTypeName(type, workflowAttribute); await WorkflowBase.RegisterAsync(this, type, workflowTypeName, ResolveDomain(domain)); lock (registeredWorkflowTypes) { registeredWorkflowTypes.Add(CadenceHelper.GetWorkflowInterface(type)); } } } }
/// <summary> /// Constructor. /// </summary> /// <param name="name">The folder name.</param> /// <param name="files">Optional files to be added.</param> /// <param name="folders">Optional folders to be added.</param> public Folder(string name, IEnumerable <File> files = null, IEnumerable <Folder> folders = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(name), nameof(name)); this.Name = name; this.files = new Dictionary <string, File>(); this.folders = new Dictionary <string, Folder>(); if (files != null) { foreach (var file in files) { this.files.Add(file.Name, file); } } if (folders != null) { foreach (var folder in folders) { this.folders.Add(folder.Name, folder); } } }
/// <summary> /// Deletes a hive Docker config. /// </summary> /// <param name="configName">The config name.</param> /// <param name="options">Optional command run options.</param> /// <exception cref="HiveException">Thrown if the operation failed.</exception> public void Remove(string configName, RunOptions options = RunOptions.None) { Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(configName)); var bundle = new CommandBundle("./delete-config.sh"); bundle.AddFile("delete-config.sh", $@"#!/bin/bash docker config inspect {configName} if [ ""$?"" != ""0"" ] ; then echo ""Config doesn't exist."" else docker config rm {configName} fi ", isExecutable: true); var response = hive.GetReachableManager().SudoCommand(bundle, RunOptions.None); if (response.ExitCode != 0) { throw new HiveException(response.ErrorSummary); } }
/// <summary> /// Checks the argument passed for wildcards and expands them into the /// appopriate set of matching file names. /// </summary> /// <param name="path">The file path potentially including wildcards.</param> /// <returns>The set of matching file names.</returns> public static string[] ExpandWildcards(string path) { Covenant.Requires <ArgumentNullException>(path != null); int pos; string dir; string pattern; if (path.IndexOfAny(NeonHelper.FileWildcards) == -1) { return(new string[] { path }); } pos = path.LastIndexOfAny(new char[] { '\\', '/', ':' }); if (pos == -1) { return(Directory.GetFiles(".", path)); } dir = path.Substring(0, pos); pattern = path.Substring(pos + 1); return(Directory.GetFiles(dir, pattern)); }
/// <summary> /// Builds and publishes a project locally to prepare it for being uploaded to the Raspberry. This method /// will display an error message box to the user on failures /// </summary> /// <param name="dte"></param> /// <param name="solution"></param> /// <param name="project"></param> /// <param name="projectProperties"></param> /// <returns></returns> public static async Task <bool> PublishProjectWithUIAsync(DTE2 dte, Solution solution, Project project, ProjectProperties projectProperties) { Covenant.Requires <ArgumentNullException>(dte != null, nameof(dte)); Covenant.Requires <ArgumentNullException>(solution != null, nameof(solution)); Covenant.Requires <ArgumentNullException>(project != null, nameof(project)); Covenant.Requires <ArgumentNullException>(projectProperties != null, nameof(projectProperties)); await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (!await PublishProjectAsync(dte, solution, project, projectProperties)) { MessageBox.Show( "[dotnet publish] failed for the project.\r\n\r\nLook at the Output/Debug panel for more details.", "Publish Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } else { return(true); } }
/// <summary> /// Performs an HTTP <b>GET</b> using a specific <see cref="IRetryPolicy"/> and /// without ensuring that a success code was returned. /// </summary> /// <param name="retryPolicy">The retry policy or <c>null</c> to disable retries.</param> /// <param name="uri">The URI</param> /// <param name="args">The optional query arguments.</param> /// <param name="headers">The Optional HTTP headers.</param> /// <param name="cancellationToken">The optional <see cref="CancellationToken"/>.</param> /// <param name="logActivity">The optional <see cref="LogActivity"/> whose ID is to be included in the request.</param> /// <returns>The <see cref="JsonResponse"/>.</returns> /// <exception cref="SocketException">Thrown for network connectivity issues.</exception> public async Task <JsonResponse> GetUnsafeAsync( IRetryPolicy retryPolicy, string uri, ArgDictionary args = null, ArgDictionary headers = null, CancellationToken cancellationToken = default, LogActivity logActivity = default) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(uri)); retryPolicy = retryPolicy ?? NoRetryPolicy.Instance; return(await retryPolicy.InvokeAsync( async() => { var requestUri = FormatUri(uri, args); try { var client = this.HttpClient; if (client == null) { throw new ObjectDisposedException(nameof(JsonClient)); } var httpResponse = await client.GetAsync(requestUri, cancellationToken: cancellationToken, headers: headers, activity: logActivity); return new JsonResponse(requestUri, httpResponse, await httpResponse.Content.ReadAsStringAsync()); } catch (HttpRequestException e) { throw new HttpException(e, requestUri); } })); }
/// <summary> /// Asynchronously publishes a message to an exchange. /// </summary> /// <typeparam name="TMessage">The message type.</typeparam> /// <param name="exchange">The target exchane.</param> /// <param name="message">The message.</param> /// <param name="routingKey">The routing key.</param> /// <param name="headers">Optional message headers.</param> protected async Task PublishAsync <TMessage>(IExchange exchange, TMessage message, string routingKey, params KeyValuePair <string, string>[] headers) where TMessage : class, new() { Covenant.Requires <ArgumentNullException>(exchange != null); Covenant.Requires <ArgumentNullException>(message != null); var body = Serialize(message, Encoding.UTF8); var properties = new MessageProperties() { Type = typeof(TMessage).FullName, ContentType = "application/json", ContentEncoding = "utf-8" }; if (headers != null && headers.Length > 0) { foreach (var header in headers) { properties.Headers.Add(header.Key, header.Value); } } await EasyBus.PublishAsync(exchange, routingKey, mandatory : false, properties, body); }
/// <summary> /// <para> /// Retrieves all of the extensions methods in an assembly targeting a specific type. /// </para> /// <note> /// This doesn't currently support nested or generic target types. /// </note> /// </summary> /// <param name="assembly">The source assembly.</param> /// <param name="targetType">The type being extended.</param> /// <param name="allowPrivate">Optionally specifies that only private methods are to be returned as well as public ones.</param> /// <returns>The extension methods.</returns> public static IEnumerable <MethodInfo> GetExtensionMethodsFor(this Assembly assembly, Type targetType, bool allowPrivate = false) { Covenant.Requires <ArgumentNullException>(assembly != null, nameof(assembly)); Covenant.Requires <ArgumentNullException>(targetType != null, nameof(targetType)); Covenant.Requires <ArgumentException>(!targetType.IsNested, nameof(targetType), "Nested target types are not currently supported."); Covenant.Requires <ArgumentException>(!targetType.IsGenericType, nameof(targetType), "Generic target types are not currently supported."); var bindingFlags = BindingFlags.Static | BindingFlags.Public; if (allowPrivate) { bindingFlags |= BindingFlags.NonPublic; } var query = from type in assembly.GetTypes() where !type.IsGenericType && !type.IsNested from method in type.GetMethods(bindingFlags) where method.IsDefined(typeof(ExtensionAttribute), false) where method.GetParameters()[0].ParameterType == targetType select method; return(query); }
/// <summary> /// Removes a named virtual machine. /// </summary> /// <param name="machineName">The machine name.</param> public void RemoveVM(string machineName) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(machineName)); CheckDisposed(); var machine = GetVM(machineName); var drives = GetVMDrives(machineName); // Remove the machine along with any of of its virtual hard drive files. try { powershell.Execute($"Remove-VM -Name \"{machineName}\" -Force"); } catch (Exception e) { throw new HyperVException(e.Message, e); } foreach (var drivePath in drives) { File.Delete(drivePath); } }
/// <summary> /// Signals the workflow. /// </summary> /// <param name="signalName">The signal name.</param> /// <param name="args">The signal arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if the child workflow has not been started.</exception> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed /// are compatible with the target workflow arguments. /// </note> /// </remarks> public async Task SignalAsync(string signalName, params object[] args) { await SyncContext.ClearAsync; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (Execution == null) { throw new InvalidOperationException("The stub must be started first."); } var reply = (WorkflowSignalReply)await client.CallProxyAsync( new WorkflowSignalRequest() { WorkflowId = execution.WorkflowId, RunId = execution.RunId, Domain = options.Domain, SignalName = signalName, SignalArgs = client.DataConverter.ToData(args) }); reply.ThrowOnError(); }
/// <summary> /// Lists the Cadence domains. /// </summary> /// <param name="pageSize"> /// The maximum number of domains to be returned. This must be /// greater than or equal to one. /// </param> /// <param name="nextPageToken"> /// Optionally specifies an opaque token that can be used to retrieve subsequent /// pages of domains. /// </param> /// <returns>A <see cref="DomainListPage"/> with the domains.</returns> /// <remarks> /// <para> /// This method can be used to retrieve one or more pages of domain /// results. You'll pass <paramref name="pageSize"/> as the maximum number /// of domains to be returned per page. The <see cref="DomainListPage"/> /// returned will list the domains and if there are more domains waiting /// to be returned, will return token that can be used in a subsequent /// call to retrieve the next page of results. /// </para> /// <note> /// <see cref="DomainListPage.NextPageToken"/> will be set to <c>null</c> /// when there are no more result pages remaining. /// </note> /// </remarks> public async Task <DomainListPage> ListDomainsAsync(int pageSize, byte[] nextPageToken = null) { await SyncContext.Clear; Covenant.Requires <ArgumentException>(pageSize >= 1, nameof(pageSize)); EnsureNotDisposed(); var reply = (DomainListReply) await CallProxyAsync( new DomainListRequest() { PageSize = pageSize, NextPageToken = nextPageToken }); reply.ThrowOnError(); var domains = new List <DomainDescription>(reply.Domains.Count); foreach (var domain in reply.Domains) { domains.Add(domain.ToPublic()); } nextPageToken = reply.NextPageToken; if (nextPageToken != null && nextPageToken.Length == 0) { nextPageToken = null; } return(new DomainListPage() { Domains = domains, NextPageToken = nextPageToken }); }
//--------------------------------------------------------------------- // Implementation /// <summary> /// Performs development related cluster checks with information on potential /// problems being written to STDOUT. /// </summary> /// <param name="clusterLogin">Specifies the target cluster login.</param> /// <param name="k8s">Specifies the cluster's Kubernertes client.</param> /// <returns><c>true</c> when there are no problems, <c>false</c> otherwise.</returns> public static async Task <bool> CheckAsync(ClusterLogin clusterLogin, IKubernetes k8s) { Covenant.Requires <ArgumentNullException>(clusterLogin != null, nameof(clusterLogin)); Covenant.Requires <ArgumentNullException>(k8s != null, nameof(k8s)); var error = false; if (!await CheckNodeContainerImagesAsync(clusterLogin, k8s)) { error = true; } if (!await CheckPodPrioritiesAsync(clusterLogin, k8s)) { error = true; } if (!await CheckResourcesAsync(clusterLogin, k8s)) { error = true; } return(error); }
/// <summary> /// Constructs a reverse proxy. /// </summary> /// <param name="serviceName"></param> /// <param name="localPort">The local port.</param> /// <param name="remotePort">The remote port.</param> /// <param name="@namespace"></param> /// Optionally specifies an acceptable server certificate. This can be used /// as a way to allow access for a specific self-signed certificate. Passing /// a certificate implies <paramref name="remoteTls"/><c>=true</c>. /// </param> /// <param name="clientCertificate"> /// Optionally specifies a client certificate. Passing a certificate implies /// <paramref name="remoteTls"/><c>=true</c>. /// </param> /// <param name="requestHandler">Optional request hook.</param> /// <param name="responseHandler">Optional response hook.</param> public PortForward( string serviceName, int localPort, int remotePort, string @namespace = "default") { Covenant.Requires <ArgumentException>(NetHelper.IsValidPort(localPort)); Covenant.Requires <ArgumentException>(NetHelper.IsValidPort(remotePort)); if (!NeonHelper.IsWindows) { throw new NotSupportedException($"[{nameof(PortForward)}] is supported only on Windows."); } this.serviceName = serviceName; this.localPort = localPort; this.remotePort = remotePort; this.@namespace = @namespace; this.kubectlProxyProcess = new Process(); // Create the client. KubeHelper.PortForward(serviceName, remotePort, localPort, @namespace, kubectlProxyProcess); }
/// <summary> /// Uploads a file as multilpe assets to a release, publishes the release and then /// return the <see cref="Download"/> details. /// </summary> /// <param name="release">The target brelease.</param> /// <param name="name">The download name.</param> /// <param name="version">The download version.</param> /// <param name="partCount">The number of parts to be uploaded.</param> /// <param name="partSize">The size of each part.</param> /// <returns>The <see cref="Download"/> describing how to download the parts.</returns> /// <remarks> /// Each part will be filled with bytes where the byte of each part will start /// with the part number and the following bytes will increment the previous byte /// value. /// </remarks> private DownloadManifest PublishMultipartAsset(Release release, string name, string version, int partCount, long partSize) { Covenant.Requires <ArgumentNullException>(release != null, nameof(release)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(name), nameof(name)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(version), nameof(version)); Covenant.Requires <ArgumentException>(partCount > 0, nameof(release)); Covenant.Requires <ArgumentException>(partSize > 0, nameof(release)); using (var tempFile = new TempFile()) { using (var output = new FileStream(tempFile.Path, System.IO.FileMode.Create, FileAccess.ReadWrite)) { for (int partNumber = 0; partNumber < partCount; partNumber++) { for (long i = 0; i < partSize; i++) { output.WriteByte((byte)i); } } } return(GitHub.Releases.UploadMultipartAsset(repo, release, tempFile.Path, version: version, name: name, maxPartSize: partSize)); } }
/// <summary> /// Registers an activity type. /// </summary> /// <param name="client">The associated client.</param> /// <param name="activityType">The activity type.</param> /// <param name="activityTypeName">The name used to identify the implementation.</param> /// <returns><c>true</c> if the activity was already registered.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> internal static bool Register(CadenceClient client, Type activityType, string activityTypeName) { Covenant.Requires <ArgumentNullException>(client != null); CadenceHelper.ValidateActivityImplementation(activityType); activityTypeName = GetActivityTypeKey(client, activityTypeName); var constructInfo = new ActivityInvokeInfo(); constructInfo.ActivityType = activityType; constructInfo.Constructor = constructInfo.ActivityType.GetConstructor(noTypeArgs); if (constructInfo.Constructor == null) { throw new ArgumentException($"Activity type [{constructInfo.ActivityType.FullName}] does not have a default constructor."); } lock (syncLock) { if (nameToInvokeInfo.TryGetValue(activityTypeName, out var existingEntry)) { if (!object.ReferenceEquals(existingEntry.ActivityType, constructInfo.ActivityType)) { throw new InvalidOperationException($"Conflicting activity type registration: Activity type [{activityType.FullName}] is already registered for workflow type name [{activityTypeName}]."); } return(true); } else { nameToInvokeInfo[activityTypeName] = constructInfo; return(false); } } }
/// <summary> /// Gets a list of taints that are currently applied to all nodes matching the given node label/value pair. /// </summary> /// <param name="controller">The setup controller.</param> /// <param name="labelKey">The target nodes label key.</param> /// <param name="labelValue">The target nodes label value.</param> /// <returns>The taint list.</returns> public static async Task <List <V1Taint> > GetTaintsAsync(ISetupController controller, string labelKey, string labelValue) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(controller != null, nameof(controller)); var taints = new List <V1Taint>(); foreach (var node in (await GetK8sClient(controller).ListNodeAsync()).Items.Where(node => node.Metadata.Labels.Any(label => label.Key == labelKey && label.Value == labelValue))) { if (node.Spec.Taints?.Count() > 0) { foreach (var taint in node.Spec.Taints) { if (!taints.Any(t => t.Key == taint.Key && t.Effect == taint.Effect && t.Value == taint.Value)) { taints.Add(taint); } } } } return(taints); }
/// <summary> /// Sets the Linux file permissions. /// </summary> /// <param name="path">Path to the target file or directory.</param> /// <param name="mode">Linux file permissions.</param> /// <param name="recursive">Optionally apply the permissions recursively.</param> public static void Set(string path, string mode, bool recursive = false) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(path)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(mode)); // $todo(jeff.lill): // // We're going to hack this by running [chmod MODE PATH]. Eventually, // we could convert this to using a low-level package but I didn't // want to spend time trying to figure that out right now. // // https://www.nuget.org/packages/Mono.Posix.NETStandard/1.0.0 if (!NeonHelper.IsLinux) { throw new NotSupportedException("This method requires Linux."); } object[] args; if (recursive) { args = new object[] { "-R", mode, path }; } else { args = new object[] { mode, path }; } var response = NeonHelper.ExecuteCaptureAsync("chmod", new object[] { mode, path }).Result; if (response.ExitCode != 0) { throw new IOException(response.ErrorText); } }
/// <summary> /// Executes a SQL query and returns the data reader to be used to process the results. /// </summary> /// <param name="connection">The database connection.</param> /// <param name="cmdText">The SQL command.</param> /// <param name="behavior">Optionally specifies the command behavior.</param> /// <param name="transaction">Optionally specifies the transaction.</param> /// <returns>The <see cref="NpgsqlDataReader"/>.</returns> /// <remarks> /// <note> /// Although this method is convenient, consider explictly creating and /// preparing <see cref="NpgsqlCommand"/> for frequently executed commands /// for better performance. /// </note> /// </remarks> public static NpgsqlDataReader ExecuteReader( this NpgsqlConnection connection, string cmdText, CommandBehavior behavior = CommandBehavior.Default, NpgsqlTransaction transaction = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(cmdText), nameof(cmdText)); NpgsqlCommand command; if (transaction == null) { command = new NpgsqlCommand(cmdText, connection); } else { command = new NpgsqlCommand(cmdText, connection, transaction); } using (command) { return(command.ExecuteReader(behavior)); } }
/// <summary> /// Returns the value associated with a command line option if the option was present /// on the command line otherwise, the specified default value will be returned. /// </summary> /// <param name="optionName">The case sensitive option name (including the leading dashes (<b>-</b>).</param> /// <param name="def">The default value.</param> /// <returns>The option value if present, the specified default value otherwise.</returns> /// <remarks> /// <para> /// If the <paramref name="optionName"/> was included in a previous <see cref="DefineOption"/> /// call, then all aliases for the option will be searched. If the option is not /// present on the command line and <paramref name="def"/> is <c>null</c>, then the default /// defined default value will be returned otherwise <paramref name="def"/> will override /// the definition. /// </para> /// </remarks> public string GetOption(string optionName, string def = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(optionName)); OptionDefinition definition; string value; if (optionDefinitions.TryGetValue(optionName, out definition)) { foreach (var name in definition.Names) { if (options.TryGetValue(name, out value) && !string.IsNullOrEmpty(value)) { return(value); } } if (def != null) { return(def); } else { return(definition.Default); } } else { if (options.TryGetValue(optionName, out value)) { return(value); } return(def); } }
/// <summary> /// Performs common node configuration. /// </summary> /// <param name="controller">The setup controller.</param> /// <param name="clusterManifest">The cluster manifest.</param> public void SetupNode(ISetupController controller, ClusterManifest clusterManifest) { Covenant.Requires <ArgumentNullException>(controller != null, nameof(controller)); Covenant.Requires <ArgumentNullException>(clusterManifest != null, nameof(clusterManifest)); var nodeDefinition = NeonHelper.CastTo <NodeDefinition>(Metadata); var clusterDefinition = Cluster.Definition; var hostingManager = controller.Get <IHostingManager>(KubeSetupProperty.HostingManager); InvokeIdempotent("setup/node", () => { PrepareNode(controller); ConfigureEnvironmentVariables(controller); SetupPackageProxy(controller); UpdateHostname(controller); NodeInitialize(controller); NodeInstallCriO(controller, clusterManifest); NodeInstallIPVS(controller); NodeInstallPodman(controller); NodeInstallKubernetes(controller); SetupKublet(controller); }); }