示例#1
0
        /// <summary>
        /// Reads and deserializes the Vault object located at the specified path as JSON,
        /// returning the default value if the path doesn't exist.
        /// </summary>
        /// <typeparam name="T">The type being read.</typeparam>
        /// <param name="path">The object path.</param>
        /// <param name="cancellationToken">The optional <see cref="CancellationToken"/>.</param>
        /// <returns>The result as a <c>dynamic</c> object or <c>null</c> if the path doesn't exist.</returns>
        /// <exception cref="HttpException">Thrown for Vault communication problems.</exception>
        public async Task <T> ReadJsonOrDefaultAsync <T>(string path, CancellationToken cancellationToken = default)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(path));

            try
            {
                var jsonText = (await jsonClient.GetAsync($"/{vaultApiVersion}/{Normalize(path)}", null, cancellationToken))
                               .AsDynamic()
                               .data
                               .ToString();

                return(NeonHelper.JsonDeserialize <T>(jsonText));
            }
            catch (HttpException e)
            {
                if (e.StatusCode == HttpStatusCode.NotFound)
                {
                    return(default(T));
                }

                throw new HttpException($"[status={e.StatusCode}]: Unable to read Vault bytes from [path={path}]", e);
            }
        }
示例#2
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <exception cref="ClusterDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(ClusterDefinition clusterDefinition)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            var googleHostingOptionsPrefix = $"{nameof(ClusterDefinition.Hosting)}.{nameof(ClusterDefinition.Hosting.Google)}";

            // Verify subnets

            if (!NetworkCidr.TryParse(VnetSubnet, out var vnetSubnet))
            {
                throw new ClusterDefinitionException($"[{googleHostingOptionsPrefix}.{nameof(VnetSubnet)}={VnetSubnet}] is not a valid subnet.");
            }

            if (!NetworkCidr.TryParse(NodeSubnet, out var nodeSubnet))
            {
                throw new ClusterDefinitionException($"[{googleHostingOptionsPrefix}.{nameof(NodeSubnet)}={NodeSubnet}] is not a valid subnet.");
            }

            if (!vnetSubnet.Contains(nodeSubnet))
            {
                throw new ClusterDefinitionException($"[{googleHostingOptionsPrefix}.{nameof(NodeSubnet)}={NodeSubnet}] is contained within [{nameof(VnetSubnet)}={VnetSubnet}].");
            }
        }
示例#3
0
        /// <summary>
        /// Constructs an instance of <paramref name="resultType"/> from a <see cref="JObject"/>.
        /// </summary>
        /// <param name="resultType">The result type.</param>
        /// <param name="jObject">The source <see cref="JObject"/>.</param>
        /// <returns>The new instance as an <see cref="object"/>.</returns>
        public static object CreateFrom(Type resultType, JObject jObject)
        {
            Covenant.Requires(resultType != null);
            Covenant.Requires(jObject != null);
#if DEBUG
            Covenant.Requires <ArgumentException>(resultType.Implements <IRoundtripData>());
#endif
            MethodInfo createMethod;

            lock (classNameToJObjectCreateMethod)
            {
                if (!classNameToJObjectCreateMethod.TryGetValue(resultType.FullName, out createMethod))
                {
                    createMethod = resultType.GetMethod("CreateFrom", BindingFlags.Public | BindingFlags.Static, null, createFromJObjectArgTypes, null);
#if DEBUG
                    Covenant.Assert(createMethod != null, $"Cannot locate generated [{resultType.FullName}.CreateFrom(JObject)] method.");
#endif
                    classNameToJObjectCreateMethod.Add(resultType.FullName, createMethod);
                }
            }

            return(createMethod.Invoke(null, new object[] { jObject }));
        }
示例#4
0
        /// <summary>
        /// Constructs an instance of <paramref name="resultType"/> from a byte array.
        /// </summary>
        /// <param name="resultType">The result type.</param>
        /// <param name="bytes">The source bytes.</param>
        /// <returns>The new instance as an <see cref="object"/>.</returns>
        public static object CreateFrom(Type resultType, byte[] bytes)
        {
            Covenant.Requires(resultType != null);
            Covenant.Requires(bytes != null);

            var json   = Encoding.UTF8.GetString(bytes);   // $debug(jeff.lill): DELETE THIS!
            var jToken = JToken.Parse(json);

            switch (jToken.Type)
            {
            case JTokenType.Null:

                return(null);

            case JTokenType.Object:

                return(CreateFrom(resultType, JObject.Parse(json)));

            default:

                throw new ArgumentException("Invalid JSON: Expecting an object or NULL.");
            }
        }
示例#5
0
        public static async Task AssertThrowsAsync <TException>(Func <Task> action)
            where TException : Exception
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(action != null, nameof(action));

            try
            {
                await action();

                throw new AssertException($"Expected: {typeof(TException).FullName}\r\nActual:   (no exception thrown)");
            }
            catch (Exception e)
            {
                if (e is TException || e.Contains <TException>())
                {
                    return;
                }

                throw new AssertException($"Expected: {typeof(TException).FullName}\r\nActual:   {e.GetType().Name}");
            }
        }
示例#6
0
        /// <summary>
        /// Asynchronously reads all bytes from the current position to the end of the stream.
        /// </summary>
        /// <returns>The byte array.</returns>
        public static async Task <byte[]> ReadToEndAsync(this Stream stream)
        {
            await SyncContext.ClearAsync;

            Covenant.Requires <ArgumentNullException>(stream != null, nameof(stream));

            var buffer = new byte[16 * 1024];

            using (var ms = new MemoryStream(16 * 1024))
            {
                while (true)
                {
                    var cb = await stream.ReadAsync(buffer, 0, buffer.Length);

                    if (cb == 0)
                    {
                        return(ms.ToArray());
                    }

                    ms.Write(buffer, 0, cb);
                }
            }
        }
示例#7
0
        /// <summary>
        /// Connects to a XenServer/XCP-ng host and removes any VMs matching the name or file
        /// wildcard pattern, forceably shutting the VMs down when necessary.  Note that the
        /// VM's drives will also be removed.
        /// </summary>
        /// <param name="addressOrFQDN">Specifies the IP address or hostname for the target XenServer host machine.</param>
        /// <param name="username">Specifies the username to be used to connect to the host.</param>
        /// <param name="password">Specifies the host password.</param>
        /// <param name="nameOrPattern">Specifies the VM name or pattern including '*' or '?' wildcards to be used to remove VMs.</param>
        public static void RemoveVMs(string addressOrFQDN, string username, string password, string nameOrPattern)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(addressOrFQDN), nameof(addressOrFQDN));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(username), nameof(username));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(password), nameof(password));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(nameOrPattern), nameof(nameOrPattern));

            var nameRegex = NeonHelper.FileWildcardRegex(nameOrPattern);

            using (var client = new XenClient(addressOrFQDN, username, password))
            {
                foreach (var vm in client.Machine.List()
                         .Where(vm => nameRegex.IsMatch(vm.NameLabel)))
                {
                    if (vm.IsRunning)
                    {
                        client.Machine.Shutdown(vm, turnOff: true);
                    }

                    client.Machine.Remove(vm, keepDrives: false);
                }
            }
        }
示例#8
0
        /// <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);
        }
示例#9
0
        /// <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();
        }
示例#10
0
        /// <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('+', '.'));
        }
示例#11
0
        /// <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;
        }
示例#12
0
        /// <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);
        }
示例#13
0
        /// <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);
            });
        }
示例#14
0
        /// <summary>
        /// Performs an HTTP <b>POST</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> PostAsync(
            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.PostAsync(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);
                }
            }));
        }
示例#15
0
        /// <summary>
        /// Disables an optional Windows feature.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the feature does't exist or is enabled and waiting for a Windows restart.
        /// </exception>
        /// <remarks>
        /// This method does nothing when the feature is already disabled.
        /// </remarks>
        public static void DisableOptionalWindowsFeature(string feature)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(feature), nameof(feature));

            switch (GetWindowsOptionalFeatureStatus(feature))
            {
            case WindowsFeatureStatus.Unknown:

                throw new InvalidOperationException($"Unknown Windows Feature: {feature}");

            case WindowsFeatureStatus.Enabled:

                var response = NeonHelper.ExecuteCapture("dism.exe",
                                                         new object[]
                {
                    "/Online",
                    "/English",
                    "/Disable-Feature",
                    $"/FeatureName:{feature}"
                });

                response.EnsureSuccess();
                break;

            case WindowsFeatureStatus.EnabledPending:

                throw new InvalidOperationException($"Windows Feature install is pending: {feature}");

            case WindowsFeatureStatus.Disabled:

                return;

            default:

                throw new NotImplementedException();
            }
        }
示例#16
0
        /// <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;
                    }
                }
            }
        }
示例#17
0
        /// <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);
            });
        }
示例#18
0
        /// <summary>
        /// Returns a named broadcast message channel, creating one if it doesn't already
        /// exist.  Broadcast message channels are used to forward messages to one or more
        /// consumers such that each message is delivered to <b>all consumers</b>.
        /// </summary>
        /// <param name="name">The channel name.  This can be a maximum of 250 characters.</param>
        /// <param name="durable">
        /// Optionally specifies that the channel should survive message cluster restarts.
        /// This defaults to <c>false</c>.
        /// </param>
        /// <param name="autoDelete">
        /// Optionally specifies that channel should be automatically deleted when the
        /// last consumer is removed.
        /// </param>
        /// <param name="messageTTL">
        /// <para>
        /// Optionally specifies the maximum time a message can remain in the channel before
        /// being deleted.  This defaults to <c>null</c> which disables this feature.
        /// </para>
        /// <note>
        /// The maximum possible TTL is about <b>24.855 days</b>.
        /// </note>
        /// </param>
        /// <param name="maxLength">
        /// Optionally specifies the maximum number of messages that can be waiting in the channel
        /// before messages at the front of the channel will be deleted.  This defaults
        /// to unconstrained.
        /// </param>
        /// <param name="maxLengthBytes">
        /// Optionally specifies the maximum total bytes of messages that can be waiting in
        /// the channel before messages at the front of the channel will be deleted.  This
        /// defaults to unconstrained.
        /// </param>
        /// <param name="publishOnly">
        /// Optionally specifies that the channel instance returned will only be able
        /// to publish messages and not consume them.  Enabling this avoid the creation
        /// of a queue that will unnecessary for this situation.
        /// </param>
        /// <returns>The requested <see cref="BroadcastChannel"/>.</returns>
        /// <remarks>
        /// <note>
        /// The instance returned should be disposed when you're done with it.
        /// </note>
        /// <note>
        /// The maximum possible <paramref name="messageTTL"/> is <see cref="int.MaxValue"/> or just
        /// under 24 days.  An <see cref="ArgumentException"/> will be thrown if this is exceeded.
        /// </note>
        /// </remarks>
        public BroadcastChannel GetBroadcastChannel(
            string name,
            bool durable        = false,
            bool autoDelete     = false,
            TimeSpan?messageTTL = null,
            int?maxLength       = null,
            int?maxLengthBytes  = null,
            bool publishOnly    = false)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(name));
            Covenant.Requires <ArgumentException>(name.Length <= 250);
            Covenant.Requires <ArgumentException>(!messageTTL.HasValue || messageTTL.Value >= TimeSpan.Zero);
            Covenant.Requires <ArgumentException>(!maxLength.HasValue || maxLength.Value > 0);
            Covenant.Requires <ArgumentException>(!maxLengthBytes.HasValue || maxLengthBytes.Value > 0);

            lock (syncLock)
            {
                CheckDisposed();

                var channel = new BroadcastChannel(
                    this,
                    name,
                    durable: durable,
                    autoDelete: autoDelete,
                    messageTTL: messageTTL,
                    maxLength: maxLength,
                    maxLengthBytes: maxLengthBytes,
                    publishOnly: publishOnly);

                lock (syncLock)
                {
                    channels.Add(channel);
                }

                return(channel);
            }
        }
示例#19
0
        //---------------------------------------------------------------------
        // Static members

        /// <summary>
        /// Returns the summary from a hive proxy.
        /// </summary>
        /// <param name="hive">The target hive proxy.</param>
        /// <param name="definition">Optionally overrides the hive definition passed within <paramref name="hive"/>.</param>
        /// <returns>The <see cref="HiveSummary"/>.</returns>
        public static HiveSummary FromHive(HiveProxy hive, HiveDefinition definition = null)
        {
            Covenant.Requires <ArgumentNullException>(hive != null);

            var summary = new HiveSummary();

            // Load the hive globals.

            var globals   = hive.Consul.Client.KV.DictionaryOrEmpty(HiveConst.GlobalKey).Result;
            var internals = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase)
            {
                // We don't include these internal globals in the summary.

                HiveGlobals.DefinitionDeflate,
                HiveGlobals.DefinitionHash,
                HiveGlobals.PetsDefinition
            };

            foreach (var item in globals.Where(i => !internals.Contains(i.Key)))
            {
                summary.Globals.Add(item.Key, Encoding.UTF8.GetString(item.Value));
            }

            // Summarize information from the hive definition.

            summary.NodeCount          = definition.Nodes.Count();
            summary.ManagerCount       = definition.Managers.Count();
            summary.WorkerCount        = definition.Workers.Count();
            summary.PetCount           = definition.Pets.Count();
            summary.OperatingSystem    = definition.HiveNode.OperatingSystem;
            summary.HostingEnvironment = definition.Hosting.Environment;
            summary.HiveFSEnabled      = definition.HiveFS.Enabled;
            summary.LogEnabled         = definition.Log.Enabled;
            summary.VpnEnabled         = definition.Vpn.Enabled;

            return(summary);
        }
示例#20
0
        /// <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);
            }
        }
示例#21
0
        public void UpdateCovenantRepositoryTest()
        {
            //Arrange
            ICovenantRepository repository = new CovenantRepository();
            Covenant            Covenant   = _contextForTest.Covenants.Find(1);

            Covenant.Name     = "Teste";
            Covenant.Business = "NDD";
            Covenant.Plan     = "0000";
            Covenant.Coverage = 20;

            //Action
            var updatedCovenant = repository.Update(Covenant);

            //Assert
            var persistedCovenant = _contextForTest.Covenants.Find(1);

            Assert.IsNotNull(updatedCovenant);
            Assert.AreEqual(updatedCovenant.Id, persistedCovenant.Id);
            Assert.AreEqual(updatedCovenant.Name, persistedCovenant.Name);
            Assert.AreEqual(updatedCovenant.Business, persistedCovenant.Business);
            Assert.AreEqual(updatedCovenant.Plan, persistedCovenant.Plan);
            Assert.AreEqual(updatedCovenant.Coverage, persistedCovenant.Coverage);
        }
示例#22
0
        //---------------------------------------------------------------------
        // 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);
        }
示例#23
0
        /// <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);
                }
            }
        }
示例#24
0
        /// <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
            });
        }
示例#25
0
        /// <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);
        }
示例#26
0
        /// <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);
                }
            }));
        }
示例#27
0
        /// <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));
                    }
                }
            }
        }
示例#28
0
        /// <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));
        }
示例#29
0
        /// <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);
            }
        }
示例#30
0
        /// <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());
        }