Beispiel #1
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if another test manager instance is active.</exception>
        public KubeTestManager()
        {
            lock (syncLock)
            {
                if (Current != null)
                {
                    throw new InvalidOperationException("Another test manager is active.");
                }

                try
                {
                    tempFolder = new TempFolder();
                    Current    = this;

                    KubeHelper.SetTestMode(tempFolder.Path);
                    Environment.SetEnvironmentVariable(KubeConst.TestModeFolderVar, tempFolder.Path);
                }
                catch
                {
                    Environment.SetEnvironmentVariable(KubeConst.TestModeFolderVar, null);
                    Current = null;
                    throw;
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Exits the program returning the specified process exit code.
        /// </summary>
        /// <param name="exitCode">The exit code.</param>
        public static void Exit(int exitCode)
        {
            // Ensure that all sensitive files and folders are encrypted at rest.  We're
            // running this after every command just to be super safe.

            KubeHelper.EncryptSensitiveFiles();
            throw new ProgramExitException(exitCode);
        }
Beispiel #3
0
 /// <summary>
 /// Handles the <b>Logout</b> command.
 /// </summary>
 /// <param name="sender">The sender.</param>
 /// <param name="args">The arguments.</param>
 private void OnLogoutCommand(object sender, EventArgs args)
 {
     if (KubeHelper.CurrentContext != null)
     {
         ShowToast($"Logging out of: {KubeHelper.CurrentContext.Name}");
         KubeHelper.SetCurrentContext((string)null);
         PostUpdateUIState();
     }
 }
Beispiel #4
0
        public void CreateKubernetesObject_NoConsts()
        {
            // Verify that [NotSupportedException] is thrown when the required constants
            // are not defined by the Kubernetes object type.

            Assert.Throws <NotSupportedException>(() => KubeHelper.CreateKubeObject <V1TestWithoutGroup>("test"));
            Assert.Throws <NotSupportedException>(() => KubeHelper.CreateKubeObject <V1TestWithoutApiVersion>("test"));
            Assert.Throws <NotSupportedException>(() => KubeHelper.CreateKubeObject <V1TestWithoutKind>("test"));
        }
Beispiel #5
0
        /// <inheritdoc/>
        public void Dispose()
        {
            if (tempFolder != null)
            {
                KubeHelper.ResetTestMode();
                tempFolder.Dispose();

                tempFolder = null;
                Current    = null;
            }
        }
Beispiel #6
0
        /// <inheritdoc/>
        public void Dispose()
        {
            KubeHelper.ResetClusterspaceMode();

            if (tempFolder != null)
            {
                tempFolder.Dispose();

                tempFolder = null;
                Current    = null;
            }
        }
Beispiel #7
0
        /// <summary>
        /// Static constructor.
        /// </summary>
        static Pod()
        {
            // Initializes these properties from environment variables when we're running
            // in a cluster, otherwise configure test values when running on workstation.

            if (NeonHelper.IsDevWorkstation)
            {
                Namespace = "default";
                Name      = KubeHelper.GetEmulatedPodName("neon-cluster-operator");
            }
            else
            {
                Namespace = Environment.GetEnvironmentVariable("POD_NAMESPACE");
                Name      = Environment.GetEnvironmentVariable("POD_NAME");
            }
        }
Beispiel #8
0
        /// <inheritdoc/>
        public override void Run(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption || commandLine.Arguments.Length == 0)
            {
                Console.WriteLine(usage);
                Program.Exit(0);
            }

            Console.Error.WriteLine();

            var currentContext = KubeHelper.CurrentContext;
            var newContextName = KubeContextName.Parse(commandLine.Arguments.First());

            // Ensure that the new context exists.

            if (KubeHelper.Config.GetContext(newContextName) == null)
            {
                Console.Error.WriteLine($"*** Context [{newContextName}] not found.");
                Program.Exit(1);
            }

            // Check whether we're already logged into the cluster.

            if (KubeHelper.CurrentContext != null && newContextName == KubeContextName.Parse(KubeHelper.CurrentContext.Name))
            {
                Console.Error.WriteLine($"*** You are already logged into: {newContextName}");
                Program.Exit(0);
            }

            // Logout of the current cluster.

            if (currentContext != null)
            {
                Console.Error.WriteLine($"Logging out of [{currentContext.Name}].");
                KubeHelper.SetCurrentContext((string)null);
            }

            // ...and log into the new context.

            KubeHelper.SetCurrentContext(newContextName);
            Console.WriteLine($"*** Logged into [{newContextName}].");

            // Notify the desktop application.

            KubeHelper.Desktop.Login().Wait();
        }
Beispiel #9
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            Console.WriteLine();

            // Actually logout.

            if (KubeHelper.CurrentContext == null)
            {
                Console.WriteLine($"You are not logged into a neonKUBE cluster.");
                return;
            }

            Console.WriteLine($"Logout: {KubeHelper.CurrentContext.Name}");
            KubeHelper.SetCurrentContext((string)null);

            await Task.CompletedTask;
        }
Beispiel #10
0
        /// <summary>
        /// <para>
        /// Synchronizes the UI state with the current cluster configuration.
        /// </para>
        /// <note>
        /// This method is somewhat special in that it must be executed on the UI
        /// thread and if it's called on another thread, then the method will post
        /// a message to itself to invoke itself shortly on the UI thread.
        /// </note>
        /// </summary>
        public async Task UpdateUIStateAsync()
        {
            if (InvokeRequired)
            {
                PostUpdateUIState();
                return;
            }

            KubeHelper.LoadConfig();
            await UpdateProxiesAsync();

            if (InErrorState)
            {
                return;
            }

            if (!operationInProgress)
            {
                notifyIcon.Icon = IsConnected ? connectedIcon : disconnectedIcon;

                if (notifyStack.Count > 0 && !string.IsNullOrEmpty(notifyStack.Peek().BalloonText))
                {
                    SetBalloonText(notifyStack.Peek().BalloonText);
                }
                else if (IsConnected)
                {
                    SetBalloonText($"{Text}: {KubeHelper.CurrentContextName}");
                }
                else
                {
                    SetBalloonText($"{Text}: disconnected");
                }
            }
            else if (remoteOperation != null && NeonHelper.GetProcessById(remoteOperation.ProcessId) == null)
            {
                // The original [neon-cli] process is no longer running;
                // it must have terminated before signalling the end
                // of the operation.  We're going to clear the operation
                // status.
                //
                // This is an important fail-safe.

                StopOperation();
                return;
            }
        }
Beispiel #11
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            Console.WriteLine();

            var currentContextName = KubeHelper.CurrentContextName;

            if (currentContextName == null)
            {
                Console.Error.WriteLine("*** ERROR: No cluster selected.");
                Program.Exit(1);
            }

            var currentLogin = KubeHelper.GetClusterLogin(currentContextName);

            NeonHelper.OpenBrowser($"https://{currentLogin.ClusterDefinition.Domain}");
            await Task.CompletedTask;
        }
Beispiel #12
0
        /// <inheritdoc/>
        public override void Run(CommandLine commandLine)
        {
            Console.WriteLine("");

            // Actually logout.

            if (KubeHelper.CurrentContext == null)
            {
                Console.WriteLine($"You are not logged into a cluster.");
                return;
            }

            Console.WriteLine($"Logging out of: {KubeHelper.CurrentContext.Name}");
            KubeHelper.SetCurrentContext((string)null);
            Console.WriteLine("");

            // Notify the desktop application.

            KubeHelper.Desktop.Logout().Wait();
        }
Beispiel #13
0
        public void CreateIso()
        {
            // Verify that we can create a new ISO file from a folder.

            using (var tempFolder = new TempFolder())
            {
                using (var tempIso = new TempFile(suffix: ".iso"))
                {
                    for (int i = 0; i < 10; i++)
                    {
                        File.WriteAllText(Path.Combine(tempFolder.Path, $"{i}.txt"), $"{i}");
                    }

                    KubeHelper.CreateIsoFile(tempFolder.Path, tempIso.Path, "TEST");

                    using (var file = new FileStream(tempIso.Path, FileMode.Open, FileAccess.Read))
                    {
                        Assert.True(file.Length > 0);
                    }
                }
            }
        }
Beispiel #14
0
        public void CreateKubernetesObject()
        {
            // Create a few global Kubernetes objects and verify that their ApiVersion
            // and Kind properties are initialized properly.

            var configmap = KubeHelper.CreateKubeObject <V1ConfigMap>("test");

            Assert.Equal(V1ConfigMap.KubeApiVersion, configmap.ApiVersion);
            Assert.Equal(V1ConfigMap.KubeKind, configmap.Kind);

            var deployment = KubeHelper.CreateKubeObject <V1Deployment>("test");

            Assert.Equal($"{V1Deployment.KubeGroup}/{V1Deployment.KubeApiVersion}", deployment.ApiVersion);
            Assert.Equal(V1Deployment.KubeKind, deployment.Kind);

            // Verify that a custom object can be created.

            var test = KubeHelper.CreateKubeObject <V1Test>("test");

            Assert.Equal($"{V1Test.KubeGroup}/{V1Test.KubeApiVersion}", test.ApiVersion);
            Assert.Equal(V1Test.KubeKind, test.Kind);
        }
Beispiel #15
0
        /// <summary>
        /// Handles cluster context commands.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The arguments.</param>
        private void OnClusterContext(object sender, EventArgs args)
        {
            // The cluster context name is the text of the sending menu item.

            var menuItem    = (MenuItem)sender;
            var contextName = menuItem.Text;

            StartOperation(connectingAnimation);

            try
            {
                KubeHelper.SetCurrentContext(contextName);
                ShowToast($"Logged into: {contextName}");
            }
            catch
            {
                StopFailedOperation($"Cannot log into: {contextName}");
            }
            finally
            {
                StopOperation();
            }
        }
Beispiel #16
0
        /// <summary>
        /// Constructor.
        /// </summary>
        public MainForm()
        {
            MainForm.Current = this;

            InitializeComponent();

            Load  += MainForm_Load;
            Shown += (s, a) => Visible = false; // The main form should always be hidden

            // Ensure that temporary files are written to the users temporary folder because
            // there's a decent chance that this folder will be encrypted at rest.

            TempFile.Root   = KubeHelper.TempFolder;
            TempFolder.Root = KubeHelper.TempFolder;

            // Preload the notification icons and animations for better performance.

            appIcon             = new Icon(@"Images\app.ico");
            connectedIcon       = new Icon(@"Images\connected.ico");
            disconnectedIcon    = new Icon(@"Images\disconnected.ico");
            errorIcon           = new Icon(@"Images\error.ico");
            connectingAnimation = AnimatedIcon.Load("Images", "connecting", animationFrameRate);
            workingAnimation    = AnimatedIcon.Load("Images", "working", animationFrameRate);
            errorAnimation      = AnimatedIcon.Load("Images", "error", animationFrameRate);
            notifyStack         = new Stack <NotifyState>();

            // Initialize the cluster hosting provider components.

            HostingLoader.Initialize();

            // Initialize the client state.

            proxies      = new List <ReverseProxy>();
            portForwards = new List <PortForward>();
            Headend      = new HeadendClient();
            KubeHelper.LoadClientConfig();
        }
Beispiel #17
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="mode">Optionally specifies the test mode.  This defaults to <see cref="KubeClusterspaceMode.EnabledWithSharedCache"/>.</param>
        /// <exception cref="InvalidOperationException">Thrown if another test manager instance is active.</exception>
        public KubeTestManager(KubeClusterspaceMode mode = KubeClusterspaceMode.EnabledWithSharedCache)
        {
            lock (syncLock)
            {
                if (Current != null)
                {
                    throw new InvalidOperationException("Another test manager is already active.");
                }

                try
                {
                    tempFolder = new TempFolder();
                    Current    = this;

                    KubeHelper.SetClusterSpaceMode(mode, tempFolder.Path);
                }
                catch
                {
                    KubeHelper.ResetClusterspaceMode();
                    Current = null;
                    throw;
                }
            }
        }
Beispiel #18
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            if (commandLine.Arguments.Length != 2)
            {
                Help();
                Program.Exit(1);
            }

            var sourceFolder = commandLine.Arguments.ElementAtOrDefault(0);
            var isoPath      = commandLine.Arguments.ElementAtOrDefault(1);
            var linux        = commandLine.GetFlag("--linux");
            var label        = commandLine.GetOption("--label");

            if (string.IsNullOrEmpty(sourceFolder) || string.IsNullOrEmpty(isoPath))
            {
                Help();
                Program.Exit(1);
            }

            if (linux)
            {
                foreach (var file in Directory.EnumerateFiles(sourceFolder))
                {
                    var text = File.ReadAllText(file);

                    text = NeonHelper.ToLinuxLineEndings(text);

                    File.WriteAllText(file, text);
                }
            }

            KubeHelper.CreateIsoFile(sourceFolder, isoPath, label);

            Program.Exit(0);
            await Task.CompletedTask;
        }
Beispiel #19
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            KubeContextName contextName = null;

            var path    = commandLine.Arguments.FirstOrDefault();
            var rawName = commandLine.GetOption("--context");

            if (rawName != null)
            {
                contextName = KubeContextName.Parse(rawName);

                if (!contextName.IsNeonKube)
                {
                    Console.Error.WriteLine($"*** ERROR: [{contextName}] is not a neonKUBE context.");
                    Program.Exit(1);
                }
            }
            else
            {
                contextName = KubeHelper.CurrentContextName;

                if (contextName == null)
                {
                    Console.Error.WriteLine($"*** ERROR: You are not logged into a neonKUBE cluster.");
                    Program.Exit(1);
                }
            }

            var context = KubeHelper.Config.GetContext(contextName);

            if (context == null)
            {
                Console.Error.WriteLine($"*** ERROR: Context [{contextName}] not found.");
                Program.Exit(1);
            }

            var cluster = KubeHelper.Config.GetCluster(context.Properties.Cluster);
            var user    = KubeHelper.Config.GetUser(context.Properties.User);

            if (context == null)
            {
                Console.Error.WriteLine($"*** ERROR: Context [{contextName}] not found.");
                Program.Exit(1);
            }

            if (user == null)
            {
                Console.Error.WriteLine($"*** ERROR: User [{context.Properties.User}] not found.");
                Program.Exit(1);
            }

            var login = new ClusterLoginExport()
            {
                Cluster    = cluster,
                Context    = context,
                Extensions = KubeHelper.GetClusterLogin(contextName),
                User       = user
            };

            var yaml = NeonHelper.YamlSerialize(login);

            if (path == null)
            {
                Console.WriteLine(yaml);
            }
            else
            {
                File.WriteAllText(path, yaml);
            }

            await Task.CompletedTask;
        }
Beispiel #20
0
 public RCONController(KubeHelper client)
 {
     _client = client;
 }
Beispiel #21
0
        /// <summary>
        /// Updates the running proxies to match the current cluster
        /// (if there is one).  This may only be called on the UI thread.
        /// </summary>
        private async Task UpdateProxiesAsync()
        {
            if (InvokeRequired)
            {
                throw new InvalidOperationException($"[{nameof(UpdateProxiesAsync)}()] may only be called on the UI thread.");
            }

            if (KubeHelper.CurrentContext == null)
            {
                StopProxies();
                StopPortForwards();
            }
            else
            {
                var cluster = Program.GetCluster();

                // We're going to use the current Kubenetes context name and cluster ID
                // to determine whether we're still connected to the same cluster.

                if (proxiedContext != null)
                {
                    if (proxiedContext.Name == KubeHelper.CurrentContext.Name &&
                        proxiedContext.Extension.ClusterId == KubeHelper.CurrentContext.Extension.ClusterId)
                    {
                        // We're still proxying the same cluster so no changes
                        // are required.

                        return;
                    }
                }

                StopProxies();
                StopPortForwards();

                if (KubeHelper.CurrentContext == null)
                {
                    // Wr're not logged into a cluster so don't start any proxies.

                    return;
                }

                try
                {
                    // Start the connecting animation if we're not already in the error state.

                    if (!InErrorState)
                    {
                        StartNotifyAnimation(connectingAnimation, $"{KubeHelper.CurrentContextName}: Connecting...", isTransient: true);
                    }

                    //-------------------------------------------------------------
                    // The Kubernetes dashboard reverse proxy.

                    // Setup a callback that transparently adds an [Authentication] header
                    // to all requests with the correct bearer token.  We'll need to
                    // obtain the token secret via two steps:
                    //
                    //      1. Identify the dashboard token secret by listing all secrets
                    //         in the [kube-system] namespace looking for one named like
                    //         [root-user-token-*].
                    //
                    //      2. Reading that secret and extracting the value.

                    var response       = (ExecuteResponse)null;
                    var secretName     = string.Empty;
                    var dashboardToken = string.Empty;

                    await Task.Run(
                        async() =>
                    {
                        response = KubeHelper.Kubectl("--namespace", "kube-system", "get", "secrets", "-o=name");
                        await Task.CompletedTask;
                    });

                    if (response.ExitCode != 0)
                    {
                        try
                        {
                            response.EnsureSuccess();
                        }
                        catch (Exception e)
                        {
                            Program.LogError(e);
                            SetErrorState($"{KubeHelper.CurrentContextName}: Kubernetes API failure");
                            return;
                        }
                    }

                    // Step 1: Determine the secret name.

                    using (var reader = new StringReader(response.OutputText))
                    {
                        const string secretPrefix = "secret/";

                        secretName = reader.Lines().FirstOrDefault(line => line.StartsWith($"{secretPrefix}root-user-token-"));

                        Covenant.Assert(!string.IsNullOrEmpty(secretName));

                        secretName = secretName.Substring(secretPrefix.Length);
                    }

                    // Step 2: Describe the secret and extract the token value.  This
                    //         is a bit of a hack because I'm making assumptions about
                    //         the output format.

                    await Task.Run(
                        async() =>
                    {
                        response = KubeHelper.Kubectl("--namespace", "kube-system", "describe", "secret", secretName);
                        await Task.CompletedTask;
                    });

                    if (response.ExitCode != 0)
                    {
                        try
                        {
                            response.EnsureSuccess();
                        }
                        catch (Exception e)
                        {
                            Program.LogError(e);
                            SetErrorState($"{KubeHelper.CurrentContextName}: Kubernetes API failure");
                            return;
                        }
                    }

                    using (var reader = new StringReader(response.OutputText))
                    {
                        var tokenLine = reader.Lines().FirstOrDefault(line => line.StartsWith("token:"));

                        Covenant.Assert(!string.IsNullOrEmpty(tokenLine));

                        dashboardToken = tokenLine.Split(new char[] { ' ' }, 2).Skip(1).First().Trim();
                    }

                    Action <RequestContext> dashboardRequestHandler =
                        context =>
                    {
                        context.Request.Headers.Add("Authorization", $"Bearer {dashboardToken}");
                    };

                    // Start the proxy.

                    var userContext   = KubeHelper.Config.GetUser(KubeHelper.CurrentContext.Properties.User);
                    var certPem       = Encoding.UTF8.GetString(Convert.FromBase64String(userContext.Properties.ClientCertificateData));
                    var keyPem        = Encoding.UTF8.GetString(Convert.FromBase64String(userContext.Properties.ClientKeyData));
                    var dashboardCert = TlsCertificate.Parse(KubeHelper.CurrentContext.Extension.KubernetesDashboardCertificate).ToX509(publicOnly: true);

                    var kubeDashboardProxy =
                        new ReverseProxy(
                            localPort: KubeHelper.ClientConfig.KubeDashboardProxyPort,
                            remotePort: KubeHostPorts.KubeDashboard,
                            remoteHost: cluster.GetReachableMaster().PrivateAddress.ToString(),
                            validCertificate: dashboardCert,
                            requestHandler: dashboardRequestHandler);

                    proxies.Add(kubeDashboardProxy);

                    var kibanaDashboardProxy =
                        new PortForward(
                            serviceName: "kibana-kibana",
                            localPort: KubeConst.KibanaDashboardProxyPort,
                            remotePort: KubeConst.KibanaDashboardProxyPort,
                            @namespace: "logging");

                    portForwards.Add(kibanaDashboardProxy);

                    var prometheusDashboardProxy =
                        new PortForward(
                            serviceName: "prometheus",
                            localPort: KubeConst.PrometheusDashboardProxyPort,
                            remotePort: KubeConst.PrometheusDashboardProxyPort,
                            @namespace: "istio-system");

                    portForwards.Add(prometheusDashboardProxy);

                    var kialiDashboardProxy =
                        new PortForward(
                            serviceName: "kiali",
                            localPort: KubeConst.KialiDashboardProxyPort,
                            remotePort: KubeConst.KialiDashboardProxyPort,
                            @namespace: "istio-system");

                    portForwards.Add(kialiDashboardProxy);

                    var grafanaDashboardProxy =
                        new PortForward(
                            serviceName: "grafana",
                            localPort: KubeConst.GrafanaDashboardProxyPort,
                            remotePort: KubeConst.GrafanaDashboardProxyPort,
                            @namespace: "istio-system");

                    portForwards.Add(grafanaDashboardProxy);

                    //-------------------------------------------------------------
                    // Remember which cluster context we're proxying.

                    proxiedContext = KubeHelper.CurrentContext;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                finally
                {
                    // Stop any non-error transient animation on the top of the notify stack.

                    if (notifyStack.Count > 0 && notifyStack.Peek().IsTransient&& !notifyStack.Peek().IsError)
                    {
                        StopNotifyAnimation();
                    }
                }
            }
        }
Beispiel #22
0
        /// <inheritdoc/>
        public override void Run(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption)
            {
                Help();
                Program.Exit(0);
            }

            // Special-case handling of the [--remove-templates] option.

            if (commandLine.HasOption("--remove-templates"))
            {
                Console.WriteLine("Removing cached virtual machine templates.");

                foreach (var fileName in Directory.GetFiles(KubeHelper.VmTemplatesFolder, "*.*", SearchOption.TopDirectoryOnly))
                {
                    File.Delete(fileName);
                }

                Program.Exit(0);
            }

            // Implement the command.

            if (KubeHelper.CurrentContext != null)
            {
                Console.Error.WriteLine("*** ERROR: You are logged into a cluster.  You need to logout before preparing another.");
                Program.Exit(1);
            }

            if (commandLine.Arguments.Length == 0)
            {
                Console.Error.WriteLine($"*** ERROR: CLUSTER-DEF expected.");
                Program.Exit(1);
            }

            clusterDefPath = commandLine.Arguments[0];
            force          = commandLine.GetFlag("--force");

            ClusterDefinition.ValidateFile(clusterDefPath, strict: true);

            var clusterDefinition = ClusterDefinition.FromFile(clusterDefPath, strict: true);

            clusterDefinition.Provisioner = $"neon-cli:{Program.Version}";  // Identify this tool/version as the cluster provisioner

            // NOTE:
            //
            // Azure has a more restrictive password policy and our default
            // machine password does not meet the requirements:
            //
            // The supplied password must be between 6-72 characters long and must
            // satisfy at least 3 of password complexity requirements from the following:
            //
            //      1. Contains an uppercase character
            //      2. Contains a lowercase character
            //      3. Contains a numeric digit
            //      4. Contains a special character
            //      5. Control characters are not allowed
            //
            // It's also probably not a great idea to use a static password when
            // provisioning VMs in public clouds because it might be possible for
            // somebody to use this fact the SSH into nodes while the cluster is
            // being setup and before we set the secure password at the end.
            //
            // This is less problematic for non-cloud environments because it's
            // likely that the hosts won't initially be able to receive inbound
            // Internet traffic and besides, we need to have a known password
            // embedded into the VM templates.
            //
            // We're going to handle this for cloud environments by looking
            // at [Program.MachinePassword].  If this is set to the default
            // machine password then we're going to replace it with a randomlly
            // generated password with a few extra characters to ensure that
            // it meets the target cloud's password requirements.  We'll use
            // a non-default password if the operator specified one.

            if (clusterDefinition.Hosting.IsCloudProvider && Program.MachinePassword == KubeConst.DefaulVmTemplatePassword)
            {
                Program.MachinePassword = NeonHelper.GetCryptoRandomPassword(20);

                // Append a string that guarantees that the generated password meets
                // cloud minimum requirements.

                Program.MachinePassword += ".Aa0";
            }

            // NOTE: Cluster prepare starts new log files.

            cluster = new ClusterProxy(clusterDefinition, Program.CreateNodeProxy <NodeDefinition>, appendToLog: false, defaultRunOptions: RunOptions.LogOutput | RunOptions.FaultOnError);

            if (KubeHelper.Config.GetContext(cluster.Definition.Name) != null)
            {
                Console.Error.WriteLine($"*** ERROR: A context named [{cluster.Definition.Name}] already exists.");
                Program.Exit(1);
            }

            // Configure global options.

            if (commandLine.HasOption("--unredacted"))
            {
                cluster.SecureRunOptions = RunOptions.None;
            }

            var failed = false;

            try
            {
                KubeHelper.Desktop.StartOperationAsync($"Preparing [{cluster.Name}]").Wait();

                //-----------------------------------------------------------------
                // Try to ensure that no servers are already deployed on the IP addresses defined
                // for cluster nodes because provisoning over an existing cluster will likely
                // corrupt the existing cluster and also probably prevent the new cluster from
                // provisioning correctly.
                //
                // Note that we're not going to perform this check for the [Machine] hosting
                // environment because we're expecting the bare machines to be already running
                // with the assigned addresses and we're also not going to do this for cloud
                // environments because we're assuming that the cluster will run in its own
                // private network so there'll ne no possibility of conflicts.

                if (cluster.Definition.Hosting.Environment != HostingEnvironments.Machine &&
                    !cluster.Definition.Hosting.IsCloudProvider)
                {
                    Console.WriteLine();
                    Console.WriteLine(" Scanning for IP address conflicts...");
                    Console.WriteLine();

                    var pingOptions   = new PingOptions(ttl: 32, dontFragment: true);
                    var pingTimeout   = TimeSpan.FromSeconds(2);
                    var pingConflicts = new List <NodeDefinition>();
                    var pingAttempts  = 2;

                    // I'm going to use up to 20 threads at a time here for simplicity
                    // rather then doing this as async operations.

                    var parallelOptions = new ParallelOptions()
                    {
                        MaxDegreeOfParallelism = 20
                    };

                    Parallel.ForEach(cluster.Definition.NodeDefinitions.Values, parallelOptions,
                                     node =>
                    {
                        using (var pinger = new Pinger())
                        {
                            // We're going to try pinging up to [pingAttempts] times for each node
                            // just in case the network it sketchy and we're losing reply packets.

                            for (int i = 0; i < pingAttempts; i++)
                            {
                                var reply = pinger.SendPingAsync(node.PrivateAddress, (int)pingTimeout.TotalMilliseconds).Result;

                                if (reply.Status == IPStatus.Success)
                                {
                                    lock (pingConflicts)
                                    {
                                        pingConflicts.Add(node);
                                    }

                                    break;
                                }
                            }
                        }
                    });

                    if (pingConflicts.Count > 0)
                    {
                        Console.Error.WriteLine($"*** ERROR: Cannot provision the cluster because [{pingConflicts.Count}] other");
                        Console.Error.WriteLine($"***        machines conflict with the following cluster nodes:");
                        Console.Error.WriteLine();

                        foreach (var node in pingConflicts.OrderBy(n => NetHelper.AddressToUint(IPAddress.Parse(n.PrivateAddress))))
                        {
                            Console.Error.WriteLine($"{node.PrivateAddress, 16}:    {node.Name}");
                        }

                        Program.Exit(1);
                    }
                }

                //-----------------------------------------------------------------
                // Perform basic environment provisioning.  This creates basic cluster components
                // such as virtual machines, networks, load balancers, public IP addresses, security
                // groups,... as required for the environment.

                hostingManager = new HostingManagerFactory(() => HostingLoader.Initialize()).GetMaster(cluster, Program.LogPath);

                if (hostingManager == null)
                {
                    Console.Error.WriteLine($"*** ERROR: No hosting manager for the [{cluster.Definition.Hosting.Environment}] hosting environment could be located.");
                    Program.Exit(1);
                }

                hostingManager.HostUsername = Program.MachineUsername;
                hostingManager.HostPassword = Program.MachinePassword;
                hostingManager.ShowStatus   = !Program.Quiet;
                hostingManager.MaxParallel  = Program.MaxParallel;
                hostingManager.WaitSeconds  = Program.WaitSeconds;

                if (hostingManager.RequiresAdminPrivileges)
                {
                    Program.VerifyAdminPrivileges($"Provisioning to [{cluster.Definition.Hosting.Environment}] requires elevated administrator privileges.");
                }

                if (!hostingManager.Provision(force))
                {
                    Program.Exit(1);
                }

                // Get the mounted drive prefix from the hosting manager.

                cluster.Definition.DrivePrefix = hostingManager.DrivePrefix;

                // Ensure that the nodes have valid IP addresses.

                cluster.Definition.ValidatePrivateNodeAddresses();

                var ipAddressToServer = new Dictionary <IPAddress, SshProxy <NodeDefinition> >();

                foreach (var node in cluster.Nodes.OrderBy(n => n.Name))
                {
                    SshProxy <NodeDefinition> duplicateServer;

                    if (node.PrivateAddress == IPAddress.Any)
                    {
                        throw new ArgumentException($"Node [{node.Name}] has not been assigned an IP address.");
                    }

                    if (ipAddressToServer.TryGetValue(node.PrivateAddress, out duplicateServer))
                    {
                        throw new ArgumentException($"Nodes [{duplicateServer.Name}] and [{node.Name}] have the same IP address [{node.Metadata.PrivateAddress}].");
                    }

                    ipAddressToServer.Add(node.PrivateAddress, node);
                }

                // We're going to use the masters as package caches unless the user
                // specifies something else.

                packageCaches = commandLine.GetOption("--package-cache");     // This overrides the cluster definition, if specified.

                if (!string.IsNullOrEmpty(packageCaches))
                {
                    cluster.Definition.PackageProxy = packageCaches;
                }

                if (string.IsNullOrEmpty(cluster.Definition.PackageProxy))
                {
                    var sbProxies = new StringBuilder();

                    foreach (var master in cluster.Masters)
                    {
                        sbProxies.AppendWithSeparator($"{master.PrivateAddress}:{NetworkPorts.AppCacherNg}");
                    }

                    cluster.Definition.PackageProxy = sbProxies.ToString();
                }

                //-----------------------------------------------------------------
                // Prepare the cluster.

                // Write the operation begin marker to all cluster node logs.

                cluster.LogLine(logBeginMarker);

                var nodesText = cluster.Nodes.Count() == 1 ? "node" : "nodes";
                var operation = $"Preparing [{cluster.Definition.Name}] {nodesText}";

                var controller =
                    new SetupController <NodeDefinition>(operation, cluster.Nodes)
                {
                    ShowStatus  = !Program.Quiet,
                    MaxParallel = Program.MaxParallel
                };

                controller.AddGlobalStep("setup details",
                                         () =>
                {
                    using (var client = new HeadendClient())
                    {
                        kubeSetupInfo = client.GetSetupInfoAsync(cluster.Definition).Result;
                    }
                });

                // Prepare the nodes.

                controller.AddWaitUntilOnlineStep(timeout: TimeSpan.FromMinutes(15));
                hostingManager.AddPostProvisionSteps(controller);
                controller.AddStep("verify OS", CommonSteps.VerifyOS);

                controller.AddStep("prepare",
                                   (node, stepDelay) =>
                {
                    Thread.Sleep(stepDelay);
                    CommonSteps.PrepareNode(node, cluster.Definition, kubeSetupInfo, shutdown: false);
                },
                                   stepStaggerSeconds: cluster.Definition.Setup.StepStaggerSeconds);

                if (!controller.Run())
                {
                    // Write the operation end/failed marker to all cluster node logs.

                    cluster.LogLine(logFailedMarker);

                    Console.Error.WriteLine("*** ERROR: One or more configuration steps failed.");
                    Program.Exit(1);
                }

                // Persist the cluster context extension.

                var contextExtensionsPath = KubeHelper.GetContextExtensionPath((KubeContextName)$"{KubeConst.RootUser}@{clusterDefinition.Name}");
                var contextExtension      = new KubeContextExtension(contextExtensionsPath)
                {
                    ClusterDefinition = clusterDefinition,
                    SshUsername       = Program.MachineUsername,
                    SshPassword       = Program.MachinePassword,
                    SetupDetails      = new KubeSetupDetails()
                    {
                        SetupPending = true
                    }
                };

                contextExtension.Save();

                // Write the operation end marker to all cluster node logs.

                cluster.LogLine(logEndMarker);
            }
            catch
            {
                failed = true;
                throw;
            }
            finally
            {
                if (!failed)
                {
                    KubeHelper.Desktop.EndOperationAsync($"Cluster [{cluster.Name}] has been prepared and is ready for setup.").Wait();
                }
                else
                {
                    KubeHelper.Desktop.EndOperationAsync($"Cluster [{cluster.Name}] prepare has failed.", failed: true).Wait();
                }
            }
        }
Beispiel #23
0
 public PodsController(KubeHelper client)
 {
     _client = client;
 }
Beispiel #24
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            if (commandLine.Arguments.Length > 0)
            {
                Console.Error.WriteLine("*** ERROR: Unexpected argument.");
                Program.Exit(1);
            }

            Console.WriteLine();

            var contextName = KubernetesClientConfiguration.BuildDefaultConfig().CurrentContext;

            if (string.IsNullOrEmpty(contextName))
            {
                Console.Error.WriteLine($"*** ERROR: There is no current cluster.");
                Program.Exit(1);
            }

            var clusterLogin = KubeHelper.GetClusterLogin(KubeContextName.Parse(contextName));

            if (clusterLogin == null)
            {
                Console.Error.WriteLine($"*** ERROR: There is no current cluster or the current cluster is not a neonKUBE cluster.");
                Program.Exit(1);
            }

            // Handle the command line options.

            var all             = commandLine.HasOption("--all");
            var containerImages = commandLine.HasOption("--container-images");
            var priorityClass   = commandLine.HasOption("--priority-class");
            var resources       = commandLine.HasOption("--resources");
            var details         = commandLine.HasOption("--details");

            if (all || (!containerImages && !priorityClass && !resources))
            {
                containerImages = true;
                priorityClass   = true;
                resources       = true;
            }

            // Perform the requested checks.

            var k8s   = new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(KubeHelper.KubeConfigPath));
            var error = false;

            if (containerImages && !await ClusterChecker.CheckNodeContainerImagesAsync(clusterLogin, k8s, details: details))
            {
                error = true;
            }

            if (priorityClass && !await ClusterChecker.CheckPodPrioritiesAsync(clusterLogin, k8s, details: details))
            {
                error = true;
            }

            if (resources && !await ClusterChecker.CheckResourcesAsync(clusterLogin, k8s, details: details))
            {
                error = true;
            }

            if (error)
            {
                Console.Error.WriteLine();
                Console.Error.WriteLine("*** ERROR: Cluster check failed with one or more errors.");
                Program.Exit(1);
            }

            await Task.CompletedTask;
        }
Beispiel #25
0
        public void VirtualMachines(bool isAdmin)
        {
            try
            {
                using (CreateService(isAdmin))
                {
                    var hyperVProxy = new HyperVProxy(isAdmin, socketPath);

                    // List VMs before we create any below.  We had an issue once where we'd see
                    // a [NullReferenceException] when there were no VMs.

                    var vms = hyperVProxy.ListVms();

                    // Create a VM and verify.

                    hyperVProxy.AddVm(
                        machineName:       TestMachineName1,
                        memorySize:        "1 GiB",
                        processorCount:    2,
                        drivePath:         test1VhdxPath,
                        checkpointDrives:  false,
                        templateDrivePath: templatePath,
                        switchName:        "External");

                    var vm = hyperVProxy.GetVm(machineName: TestMachineName1);

                    Assert.NotNull(vm);
                    Assert.Equal(TestMachineName1, vm.Name);
                    Assert.Equal(VirtualMachineState.Off, vm.State);
                    Assert.Equal("External", vm.SwitchName);

                    // Start the VM and verify.

                    hyperVProxy.StartVm(machineName: TestMachineName1);

                    vm = hyperVProxy.GetVm(machineName: TestMachineName1);

                    Assert.NotNull(vm);
                    Assert.Equal(VirtualMachineState.Running, vm.State);

                    // Fetch the VM network adapters.

                    var adapters = hyperVProxy.GetVmNetworkAdapters(TestMachineName1);

                    Assert.NotNull(adapters);
                    Assert.NotEmpty(adapters);

                    // Save the VM and verify.

                    hyperVProxy.SaveVm(machineName: TestMachineName1);

                    vm = hyperVProxy.GetVm(machineName: TestMachineName1);

                    Assert.NotNull(vm);
                    Assert.Equal(VirtualMachineState.Saved, vm.State);

                    // Create and start another VM and verify.

                    hyperVProxy.AddVm(
                        machineName:       TestMachineName2,
                        memorySize:        "1 GiB",
                        processorCount:    2,
                        drivePath:         test2VhdxPath,
                        checkpointDrives:  false,
                        templateDrivePath: templatePath,
                        switchName:        "External");

                    vm = hyperVProxy.GetVm(machineName: TestMachineName2);

                    Assert.NotNull(vm);
                    Assert.Equal(TestMachineName2, vm.Name);
                    Assert.Equal(VirtualMachineState.Off, vm.State);
                    Assert.Equal("External", vm.SwitchName);

                    hyperVProxy.StartVm(machineName: TestMachineName2);

                    vm = hyperVProxy.GetVm(machineName: TestMachineName2);

                    Assert.Equal(VirtualMachineState.Running, vm.State);

                    // List and check the VM existence.

                    var list = hyperVProxy.ListVms();

                    Assert.Contains(list, item => item.Name == TestMachineName1);
                    Assert.Contains(list, item => item.Name == TestMachineName2);
                    Assert.True(hyperVProxy.VmExists(TestMachineName1));
                    Assert.True(hyperVProxy.VmExists(TestMachineName2));
                    Assert.False(hyperVProxy.VmExists(Guid.NewGuid().ToString("d")));

                    // Test DVD/CD insert and eject operations.

                    using (var tempFolder = new TempFolder())
                    {
                        var isoFolder = Path.Combine(tempFolder.Path, "iso-contents");
                        var isoPath   = Path.Combine(tempFolder.Path, "data.iso");

                        Directory.CreateDirectory(isoFolder);
                        File.WriteAllText(Path.Combine(isoFolder, "hello.txt"), "HELLO WORLD!");
                        KubeHelper.CreateIsoFile(isoFolder, isoPath);

                        // $todo(jefflill): Eject is failing:
                        //
                        //      https://github.com/nforgeio/neonKUBE/issues/1456

                        // hyperVProxy.InsertVmDvd(TestMachineName2, isoPath);
                        // hyperVProxy.EjectVmDvd(TestMachineName2);
                    }

                    // Stop the second VM and verify.

                    hyperVProxy.StopVm(machineName: TestMachineName2, turnOff: true);

                    vm = hyperVProxy.GetVm(machineName: TestMachineName2);

                    Assert.Equal(VirtualMachineState.Off, vm.State);

                    // Add a drive to the second VM and verify.

                    hyperVProxy.AddVmDrive(TestMachineName2,
                                           new VirtualDrive()
                    {
                        Path      = extraVhdxPath,
                        Size      = 1 * ByteUnits.GibiBytes,
                        IsDynamic = true
                    });

                    var drives = hyperVProxy.GetVmDrives(machineName: TestMachineName1);

                    // $todo(jefflill): We should be seeing two drives here:
                    //
                    //      https://github.com/nforgeio/neonKUBE/issues/1455

                    Assert.NotEmpty(drives);
                    // Assert.Equal(2, drives.Count);

                    // Compact the extra drive we added to the second VM.

                    hyperVProxy.CompactDrive(extraVhdxPath);

                    // Remove the VMs and verify.

                    hyperVProxy.RemoveVm(machineName: TestMachineName1, keepDrives: false);

                    Assert.Null(hyperVProxy.GetVm(machineName: TestMachineName1));
                }
            }
            finally
            {
                ClearState();
            }
        }
Beispiel #26
0
        /// <summary>
        /// <para>
        /// Deploys a new test cluster as specified by the cluster definition passed or connects
        /// to a cluster previously deployed by this method when the cluster definition of the
        /// existing cluster and the definition passed here are the same.
        /// </para>
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition model.</param>
        /// <param name="options">
        /// Optionally specifies the options that <see cref="ClusterFixture"/> will use to
        /// manage the test cluster.
        /// </param>
        /// <returns>
        /// <para>
        /// The <see cref="TestFixtureStatus"/>:
        /// </para>
        /// <list type="table">
        /// <item>
        ///     <term><see cref="TestFixtureStatus.Disabled"/></term>
        ///     <description>
        ///     Returned when cluster unit testing is disabled due to the <c>NEON_CLUSTER_TESTING</c> environment
        ///     variable not being present on the current machine which means that <see cref="TestHelper.IsClusterTestingEnabled"/>
        ///     returns <c>false</c>.
        ///     </description>
        /// </item>
        /// <item>
        ///     <term><see cref="TestFixtureStatus.Started"/></term>
        ///     <description>
        ///     Returned when one of the <c>Start()</c> methods is called for the first time for the fixture
        ///     instance, indicating that an existing cluster has been connected or a new cluster has been deployed.
        ///     </description>
        /// </item>
        /// <item>
        ///     <term><see cref="TestFixtureStatus.AlreadyRunning"/></term>
        ///     <description>
        ///     Returned when one of the <c>Start()</c> methods has already been called by your test
        ///     class instance.
        ///     </description>
        /// </item>
        /// </list>
        /// </returns>
        /// <exception cref="NeonKubeException">Thrown when the test cluster could not be deployed.</exception>
        /// <remarks>
        /// <para>
        /// <b>IMPORTANT:</b> Only one <see cref="ClusterFixture"/> can be run at a time on
        /// any one computer.  This is due to the fact that cluster state like the kubeconfig,
        /// neonKUBE logins, logs and other files will be written to <b>~/.neonkube/spaces/$fixture/*</b>
        /// so multiple fixture instances will be confused when trying to manage these same files.
        /// </para>
        /// <para>
        /// This means that not only will running <see cref="ClusterFixture"/> based tests in parallel
        /// within the same instance of Visual Studio fail, but running these tests in different
        /// Visual Studio instances will also fail.
        /// </para>
        /// </remarks>
        public TestFixtureStatus StartWithClusterDefinition(ClusterDefinition clusterDefinition, ClusterFixtureOptions options = null)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            if (clusterDefinition.IsLocked)
            {
                throw new NeonKubeException("Test clusters need to be unlocked.  Please set [isLocked: false] in your cluster definition.");
            }

            if (!TestHelper.IsClusterTestingEnabled)
            {
                return(TestFixtureStatus.Disabled);
            }

            if (started)
            {
                return(TestFixtureStatus.AlreadyRunning);
            }

            options ??= new ClusterFixtureOptions();
            this.options = options.Clone();

            if (this.Cluster != null)
            {
                return(TestFixtureStatus.AlreadyRunning);
            }

            // Set the clusterspace mode, using any previously downloaded node image unless
            // the user specifies a custom image.  We're going to host the fixture state
            // files in this fixed folder:
            //
            //      ~/.neonkube/spaces/$fixture/*

            clusterspaceFolder = KubeHelper.SetClusterSpaceMode(string.IsNullOrEmpty(options.ImageUriOrPath) ? KubeClusterspaceMode.EnabledWithSharedCache : KubeClusterspaceMode.Enabled, KubeHelper.ClusterspacePrefix("fixture"));

            // Figure out whether the user passed an image URI or file path to override
            // the default node image.

            var imageUriOrPath = options.ImageUriOrPath;
            var imageUri       = (string)null;
            var imagePath      = (string)null;

            if (string.IsNullOrEmpty(imageUriOrPath))
            {
                imageUriOrPath = KubeDownloads.GetDefaultNodeImageUri(clusterDefinition.Hosting.Environment);
            }

            if (imageUriOrPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) || imageUriOrPath.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
            {
                imageUri = imageUriOrPath;
            }
            else
            {
                imagePath = imageUriOrPath;
            }

            //-------------------------------------------------------------
            // We need to deal with some scenarios here:
            //
            //  1. No cluster context or login exists for the target cluster.
            //
            //     A conflicting cluster may still exist though, having been deployed
            //     by another computer or perhaps the kubecontext/logins on the current
            //     machine may have been modified.  We need to be sure to remove any
            //     conflicting resources in this case.
            //
            //  2. Cluster context and login exist on the current machine for the target
            //     cluster but the cluster is unhealthy or locked.  We'll abort for locked
            //     clusters and remove and redeploy for unhealth clusters.
            //
            //  3.  Cluster context and login exist and the cluster is healthy.  In this case,
            //      we need to compare the deployed cluster version against the current version
            //      and remove/redeploy when the versions don't match.
            //
            //  4. Cluster context and login exist and the cluster is healthy and cluster versions
            //     match.  In this case,  We'll compare the existing cluster definition with that for
            //     the new cluster and also compare the cluster versions and if they match and
            //     [RemoveClusterOnStart=false] we'll just use the existing cluster.
            //
            //  5. The current cluster matches the target but [RemoveClusterOnStart=true].
            //     We need to remove the current cluster in this case so we'll deploy a
            //     fresh one.

            // Determine whether a test cluster with the same name exists and if
            // its cluster definition matches the test cluster's definition.
            ;
            var clusterExists      = false;
            var clusterContextName = KubeContextName.Parse($"root@{clusterDefinition.Name}");
            var clusterContext     = KubeHelper.Config.GetContext(clusterContextName);
            var clusterLogin       = KubeHelper.GetClusterLogin(clusterContextName);

            if (clusterContext != null && clusterLogin != null && !clusterLogin.SetupDetails.SetupPending)
            {
                clusterExists = ClusterDefinition.AreSimilar(clusterDefinition, clusterLogin.ClusterDefinition);
            }

            if (clusterExists && !options.RemoveClusterOnStart)
            {
                // It looks like the test cluster may already exist.  We'll verify
                // that it's running, healthy, unlocked and the cluster versions match.
                // When all of these conditions are true, we'll use the existing cluster,
                // otherwise we'll remove the cluster as well as its context/login,
                // and deploy a new cluster below.

                using (var cluster = new ClusterProxy(clusterLogin.ClusterDefinition, new HostingManagerFactory()))
                {
                    KubeHelper.SetCurrentContext(clusterContextName);

                    var isLocked      = cluster.IsLockedAsync().ResultWithoutAggregate();
                    var clusterInfo   = cluster.GetClusterInfoAsync().ResultWithoutAggregate();
                    var clusterHealth = cluster.GetClusterHealthAsync().ResultWithoutAggregate();

                    if (isLocked.HasValue && isLocked.Value)
                    {
                        throw new NeonKubeException($"Cluster is locked: {cluster.Name}");
                    }

                    if (clusterHealth.State == ClusterState.Healthy && clusterInfo.ClusterVersion == KubeVersions.NeonKube)
                    {
                        // We need to reset an existing cluster to ensure it's in a known state.

                        cluster.ResetAsync().WaitWithoutAggregate();

                        started   = true;
                        IsRunning = true;
                        Cluster   = new ClusterProxy(KubeHelper.CurrentContext, new HostingManagerFactory());

                        return(TestFixtureStatus.Started);
                    }

                    cluster.RemoveAsync(removeOrphans: true).WaitWithoutAggregate();
                }
            }
            else
            {
                // There is no known existing cluster but there still might be a cluster
                // deployed by another machine or fragments of a partially deployed cluster,
                // so we need to do a preemptive cluster remove.

                using (var cluster = new ClusterProxy(clusterDefinition, new HostingManagerFactory()))
                {
                    cluster.RemoveAsync(removeOrphans: true).WaitWithoutAggregate();
                }
            }

            // Provision the new cluster.

            WriteTestOutputLine($"PREPARE CLUSTER: {clusterDefinition.Name}");

            try
            {
                var controller = KubeSetup.CreateClusterPrepareController(
                    clusterDefinition:   clusterDefinition,
                    nodeImageUri:        imageUri,
                    nodeImagePath:       imagePath,
                    maxParallel:         options.MaxParallel,
                    unredacted:          options.Unredacted,
                    neonCloudHeadendUri: options.NeonCloudHeadendUri);

                switch (controller.RunAsync().ResultWithoutAggregate())
                {
                case SetupDisposition.Succeeded:

                    WriteTestOutputLine("CLUSTER PREPARE: SUCCESS");
                    break;

                case SetupDisposition.Failed:

                    WriteTestOutputLine("CLUSTER PREPARE: FAIL");
                    throw new NeonKubeException("Cluster prepare failed.");

                case SetupDisposition.Cancelled:
                default:

                    throw new NotImplementedException();
                }
            }
            finally
            {
                if (options.CaptureDeploymentLogs)
                {
                    CaptureDeploymentLogs();
                }
            }

            // Setup the cluster.

            WriteTestOutputLine($"SETUP CLUSTER: {clusterDefinition.Name}");

            try
            {
                var controller = KubeSetup.CreateClusterSetupController(
                    clusterDefinition: clusterDefinition,
                    maxParallel:       options.MaxParallel,
                    unredacted:        options.Unredacted);

                switch (controller.RunAsync().ResultWithoutAggregate())
                {
                case SetupDisposition.Succeeded:

                    WriteTestOutputLine("CLUSTER SETUP: SUCCESS");
                    break;

                case SetupDisposition.Failed:

                    WriteTestOutputLine("CLUSTER SETUP: FAILED");
                    throw new NeonKubeException("Cluster setup failed.");

                case SetupDisposition.Cancelled:
                default:

                    throw new NotImplementedException();
                }
            }
            finally
            {
                if (options.CaptureDeploymentLogs)
                {
                    CaptureDeploymentLogs();
                }
            }

            // NOTE: We just deployed brand new cluster so there's no need to reset it.

            started   = true;
            IsRunning = true;
            Cluster   = new ClusterProxy(KubeHelper.CurrentContext, new HostingManagerFactory());

            return(TestFixtureStatus.Started);
        }
Beispiel #27
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            if (commandLine.Arguments.Length < 1)
            {
                Console.Error.WriteLine("*** ERROR: [root@CLUSTER-NAME] argument is required.");
                Program.Exit(1);
            }

            Console.WriteLine();

            // Cluster prepare/setup uses the [ProfileClient] to retrieve secrets and profile values.
            // We need to inject an implementation for [PreprocessReader] so it will be able to
            // perform the lookups.

            NeonHelper.ServiceContainer.AddSingleton <IProfileClient>(new ProfileClient());

            var contextName       = KubeContextName.Parse(commandLine.Arguments[0]);
            var kubeCluster       = KubeHelper.Config.GetCluster(contextName.Cluster);
            var unredacted        = commandLine.HasOption("--unredacted");
            var debug             = commandLine.HasOption("--debug");
            var check             = commandLine.HasOption("--check");
            var uploadCharts      = commandLine.HasOption("--upload-charts") || debug;
            var clusterspace      = commandLine.GetOption("--clusterspace");
            var maxParallelOption = commandLine.GetOption("--max-parallel", "6");
            var disablePending    = commandLine.HasOption("--disable-pending");

            if (!int.TryParse(maxParallelOption, out var maxParallel) || maxParallel <= 0)
            {
                Console.Error.WriteLine($"*** ERROR: [--max-parallel={maxParallelOption}] is not valid.");
                Program.Exit(1);
            }

            clusterLogin = KubeHelper.GetClusterLogin(contextName);

            if (clusterLogin == null)
            {
                Console.Error.WriteLine($"*** ERROR: Be sure to prepare the cluster first via: neon cluster prepare...");
                Program.Exit(1);
            }

            if (string.IsNullOrEmpty(clusterLogin.SshPassword))
            {
                Console.Error.WriteLine($"*** ERROR: No cluster node SSH password found.");
                Program.Exit(1);
            }

            if (kubeCluster != null && !clusterLogin.SetupDetails.SetupPending)
            {
                if (commandLine.GetOption("--force") == null && !Program.PromptYesNo($"One or more logins reference [{kubeCluster.Name}].  Do you wish to delete these?"))
                {
                    Program.Exit(0);
                }

                // Remove the cluster from the kubeconfig and remove any
                // contexts that reference it.

                KubeHelper.Config.Clusters.Remove(kubeCluster);

                var delList = new List <KubeConfigContext>();

                foreach (var context in KubeHelper.Config.Contexts)
                {
                    if (context.Properties.Cluster == kubeCluster.Name)
                    {
                        delList.Add(context);
                    }
                }

                foreach (var context in delList)
                {
                    KubeHelper.Config.Contexts.Remove(context);
                }

                if (KubeHelper.CurrentContext != null && KubeHelper.CurrentContext.Properties.Cluster == kubeCluster.Name)
                {
                    KubeHelper.Config.CurrentContext = null;
                }

                KubeHelper.Config.Save();
            }

            kubeContext = new KubeConfigContext(contextName);

            KubeHelper.InitContext(kubeContext);

            // Create and run the cluster setup controller.

            var clusterDefinition = clusterLogin.ClusterDefinition;

            var controller = KubeSetup.CreateClusterSetupController(
                clusterDefinition,
                maxParallel:    maxParallel,
                unredacted:     unredacted,
                debugMode:      debug,
                uploadCharts:   uploadCharts,
                clusterspace:   clusterspace);

            controller.DisablePendingTasks = disablePending;

            controller.StatusChangedEvent +=
                status =>
            {
                status.WriteToConsole();
            };

            switch (await controller.RunAsync())
            {
            case SetupDisposition.Succeeded:

                var pendingGroups = controller.GetPendingGroups();

                if (pendingGroups.Count > 0)
                {
                    Console.WriteLine($"*** ERROR: [{pendingGroups.Count}] pending task groups have not been awaited:");
                    Console.WriteLine();

                    foreach (var groupName in pendingGroups)
                    {
                        Console.WriteLine($"   {groupName}");
                    }

                    Program.Exit(1);
                }

                Console.WriteLine();
                Console.WriteLine($" [{clusterDefinition.Name}] cluster is ready.");
                Console.WriteLine();

                if (check && !debug)
                {
                    var k8s = new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(KubeHelper.KubeConfigPath));

                    if (!await ClusterChecker.CheckAsync(clusterLogin, k8s))
                    {
                        Program.Exit(1);
                    }
                }

                Program.Exit(0);
                break;

            case SetupDisposition.Cancelled:

                Console.WriteLine(" *** CANCELLED: Cluster setup was cancelled.");
                Console.WriteLine();
                Console.WriteLine();
                Program.Exit(1);
                break;

            case SetupDisposition.Failed:

                Console.WriteLine();
                Console.WriteLine(" *** ERROR: Cluster setup failed.  Examine the logs here:");
                Console.WriteLine();
                Console.WriteLine($" {KubeHelper.LogFolder}");
                Console.WriteLine();
                Program.Exit(1);
                break;

            default:

                throw new NotImplementedException();
            }

            await Task.CompletedTask;
        }
Beispiel #28
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption)
            {
                Help();
                Program.Exit(0);
            }

            Console.WriteLine();

            // Cluster prepare/setup uses the [ProfileClient] to retrieve secrets and profile values.
            // We need to inject an implementation for [PreprocessReader] so it will be able to
            // perform the lookups.

            NeonHelper.ServiceContainer.AddSingleton <IProfileClient>(new ProfileClient());

            // Handle the [--remove-templates] option.

            if (commandLine.HasOption("--remove-templates"))
            {
                Console.WriteLine("Removing cached virtual machine templates.");

                foreach (var fileName in Directory.GetFiles(KubeHelper.NodeImageFolder, "*.*", SearchOption.TopDirectoryOnly))
                {
                    File.Delete(fileName);
                }
            }

            var nodeImageUri      = commandLine.GetOption("--node-image-uri");
            var nodeImagePath     = commandLine.GetOption("--node-image-path");
            var debug             = commandLine.HasOption("--debug");
            var baseImageName     = commandLine.GetOption("--base-image-name");
            var clusterspace      = commandLine.GetOption("--clusterspace");
            var headendUri        = commandLine.GetOption("--headend-uri") ?? KubeConst.NeonCloudHeadendUri;
            var maxParallelOption = commandLine.GetOption("--max-parallel", "6");
            var disablePending    = commandLine.HasOption("--disable-pending");

            if (!int.TryParse(maxParallelOption, out var maxParallel) || maxParallel <= 0)
            {
                Console.Error.WriteLine($"*** ERROR: [--max-parallel={maxParallelOption}] is not valid.");
                Program.Exit(1);
            }

            if (debug && string.IsNullOrEmpty(baseImageName))
            {
                Console.Error.WriteLine($"*** ERROR: [--base-image-name] is required for [--debug] mode.");
                Program.Exit(1);
            }

            // Implement the command.

            if (KubeHelper.CurrentContext != null)
            {
                Console.Error.WriteLine("*** ERROR: You are logged into a cluster.  You need to logout before preparing another.");
                Program.Exit(1);
            }

            if (commandLine.Arguments.Length == 0)
            {
                Console.Error.WriteLine($"*** ERROR: CLUSTER-DEF expected.");
                Program.Exit(1);
            }

            // Obtain the cluster definition.

            var clusterDefPath    = commandLine.Arguments[0];
            var clusterDefinition = (ClusterDefinition)null;

            ClusterDefinition.ValidateFile(clusterDefPath, strict: true);

            clusterDefinition = ClusterDefinition.FromFile(clusterDefPath, strict: true);

            // Do a quick sanity check to ensure that the hosting environment has enough
            // resources (memory and disk) to actually host the cluster.

            using (var cluster = new ClusterProxy(clusterDefinition, new HostingManagerFactory()))
            {
                var status = await cluster.GetResourceAvailabilityAsync();

                if (!status.CanBeDeployed)
                {
                    Console.Error.WriteLine();
                    Console.Error.WriteLine($"*** ERROR: Insufficent resources available to deploy cluster.");
                    Console.Error.WriteLine();

                    foreach (var entity in status.Constraints.Keys
                             .OrderBy(key => key, StringComparer.InvariantCultureIgnoreCase))
                    {
                        Console.Error.WriteLine();
                        Console.Error.WriteLine($"{entity}:");

                        foreach (var constraint in status.Constraints[entity])
                        {
                            Console.Error.WriteLine($"    {constraint.ResourceType.ToString().ToUpperInvariant()}: {constraint.Details}");
                        }
                    }

                    Console.Error.WriteLine();
                    Program.Exit(1);
                }
            }

            if (KubeHelper.IsOnPremiseHypervisorEnvironment(clusterDefinition.Hosting.Environment))
            {
                // Use the default node image for the hosting environment unless [--node-image-uri]
                // or [--node-image-path] was specified.

                if (string.IsNullOrEmpty(nodeImageUri) && string.IsNullOrEmpty(nodeImagePath))
                {
                    nodeImageUri = KubeDownloads.GetDefaultNodeImageUri(clusterDefinition.Hosting.Environment);
                }
            }

            // Parse any specified package cache endpoints.

            var packageCaches         = commandLine.GetOption("--package-caches", null);
            var packageCacheEndpoints = new List <IPEndPoint>();

            if (!string.IsNullOrEmpty(packageCaches))
            {
                foreach (var item in packageCaches.Split(' ', StringSplitOptions.RemoveEmptyEntries))
                {
                    if (!NetHelper.TryParseIPv4Endpoint(item, out var endpoint))
                    {
                        Console.Error.WriteLine($"*** ERROR: [{item}] is not a valid package cache IPv4 endpoint.");
                        Program.Exit(1);
                    }

                    packageCacheEndpoints.Add(endpoint);
                }
            }

            // Create and run the cluster prepare controller.

            var controller = KubeSetup.CreateClusterPrepareController(
                clusterDefinition,
                nodeImageUri:           nodeImageUri,
                nodeImagePath:          nodeImagePath,
                maxParallel:            maxParallel,
                packageCacheEndpoints:  packageCacheEndpoints,
                unredacted:             commandLine.HasOption("--unredacted"),
                debugMode:              debug,
                baseImageName:          baseImageName,
                clusterspace:           clusterspace,
                neonCloudHeadendUri:    headendUri);

            controller.DisablePendingTasks = disablePending;

            controller.StatusChangedEvent +=
                status =>
            {
                status.WriteToConsole();
            };

            switch (await controller.RunAsync())
            {
            case SetupDisposition.Succeeded:

                var pendingGroups = controller.GetPendingGroups();

                if (pendingGroups.Count > 0)
                {
                    Console.WriteLine($"*** ERROR: [{pendingGroups.Count}] pending task groups have not been awaited:");
                    Console.WriteLine();

                    foreach (var groupName in pendingGroups)
                    {
                        Console.WriteLine($"   {groupName}");
                    }

                    Program.Exit(1);
                }

                Console.WriteLine();
                Console.WriteLine($" [{clusterDefinition.Name}] cluster is prepared.");
                Console.WriteLine();
                Program.Exit(0);
                break;

            case SetupDisposition.Cancelled:

                Console.WriteLine();
                Console.WriteLine(" *** CANCELLED: Cluster prepare was cancelled.");
                Console.WriteLine();
                Program.Exit(1);
                break;

            case SetupDisposition.Failed:

                Console.WriteLine();
                Console.WriteLine(" *** ERROR: Cluster prepare has failed.  Examine the logs here:");
                Console.WriteLine();
                Console.WriteLine($" {KubeHelper.LogFolder}");
                Console.WriteLine();
                Program.Exit(1);
                break;

            default:

                throw new NotImplementedException();
            }

            await Task.CompletedTask;
        }
Beispiel #29
0
        /// <inheritdoc/>
        public override async Task RunAsync(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption || commandLine.Arguments.Length == 0)
            {
                Console.WriteLine(usage);
                Program.Exit(0);
            }

            Console.Error.WriteLine();

            var currentContext = KubeHelper.CurrentContext;
            var newContextName = KubeContextName.Parse(commandLine.Arguments.First());

            // Ensure that the new context exists.

            if (KubeHelper.Config.GetContext(newContextName) == null)
            {
                Console.Error.WriteLine($"*** Context [{newContextName}] not found.");
                Program.Exit(1);
            }

            // Check whether we're already logged into the cluster.

            if (KubeHelper.CurrentContext != null && newContextName == KubeContextName.Parse(KubeHelper.CurrentContext.Name))
            {
                Console.Error.WriteLine($"*** You are already logged into: {newContextName}");
                Program.Exit(0);
            }

            // Logout of the current cluster.

            if (currentContext != null)
            {
                Console.Error.WriteLine($"Logout: {currentContext.Name}...");
                KubeHelper.SetCurrentContext((string)null);
            }

            // Log into the new context and then send a simple command to ensure
            // that cluster is ready.

            var orgContext = KubeHelper.CurrentContext;

            KubeHelper.SetCurrentContext(newContextName);
            Console.WriteLine($"Login: {newContextName}...");

            try
            {
                using (var k8s = new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(KubeHelper.KubeConfigPath)))
                {
                    await k8s.ListNamespaceAsync();
                }

                if (!string.IsNullOrEmpty(NeonHelper.DockerCli))
                {
                    Console.WriteLine($"Login: Docker to Harbor...");

                    var login = KubeHelper.GetClusterLogin(KubeHelper.CurrentContextName);

                    NeonHelper.Execute(NeonHelper.DockerCli,
                                       new object[]
                    {
                        "login",
                        $"{ClusterDomain.HarborRegistry}.{login.ClusterDefinition.Domain}",
                        "--username",
                        "root",
                        "--password-stdin"
                    },
                                       input: new StringReader(login.SsoPassword));
                }
            }
            catch (Exception e)
            {
                KubeHelper.SetCurrentContext(orgContext?.Name);

                Console.WriteLine("*** ERROR: Cluster is not responding.");
                Console.WriteLine();
                Console.WriteLine(NeonHelper.ExceptionError(e));
                Console.WriteLine();
                Program.Exit(1);
            }

            Console.WriteLine();
            Console.WriteLine($"Now logged into: {newContextName}");

            await Task.CompletedTask;
        }