|
1 |
| -using System; |
| 1 | +using System; |
2 | 2 | using System.Collections.Generic;
|
3 | 3 | using System.Collections.ObjectModel;
|
4 | 4 | using System.Diagnostics;
|
|
14 | 14 | using k8s.Autorest;
|
15 | 15 | using Nito.AsyncEx;
|
16 | 16 | using Xunit;
|
| 17 | +using ICSharpCode.SharpZipLib.Tar; |
| 18 | +using System.Text; |
| 19 | +using System.Security.Cryptography; |
17 | 20 |
|
18 | 21 | namespace k8s.E2E
|
19 | 22 | {
|
@@ -398,7 +401,7 @@ void Cleanup()
|
398 | 401 | async Task<V1Pod> Pod()
|
399 | 402 | {
|
400 | 403 | var pods = client.CoreV1.ListNamespacedPod(namespaceParameter);
|
401 |
| - var pod = pods.Items.First(); |
| 404 | + var pod = pods.Items.First(p => p.Metadata.Name == podName); |
402 | 405 | while (pod.Status.Phase != "Running")
|
403 | 406 | {
|
404 | 407 | await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
@@ -548,6 +551,181 @@ await genericPods.CreateNamespacedAsync(
|
548 | 551 | }
|
549 | 552 |
|
550 | 553 |
|
| 554 | + [MinikubeFact] |
| 555 | + public async Task CopyToPodTestAsync() |
| 556 | + { |
| 557 | + var namespaceParameter = "default"; |
| 558 | + var podName = "k8scsharp-e2e-cp-pod"; |
| 559 | + |
| 560 | + var client = CreateClient(); |
| 561 | + |
| 562 | + async Task<int> CopyFileToPodAsync(string name, string @namespace, string container, Stream inputFileStream, string destinationFilePath, CancellationToken cancellationToken = default(CancellationToken)) |
| 563 | + { |
| 564 | + // The callback which processes the standard input, standard output and standard error of exec method |
| 565 | + var handler = new ExecAsyncCallback(async (stdIn, stdOut, stdError) => |
| 566 | + { |
| 567 | + var fileInfo = new FileInfo(destinationFilePath); |
| 568 | + try |
| 569 | + { |
| 570 | + using (var memoryStream = new MemoryStream()) |
| 571 | + { |
| 572 | + using (var tarOutputStream = new TarOutputStream(memoryStream, Encoding.Default)) |
| 573 | + { |
| 574 | + tarOutputStream.IsStreamOwner = false; |
| 575 | + |
| 576 | + var fileSize = inputFileStream.Length; |
| 577 | + var entry = TarEntry.CreateTarEntry(fileInfo.Name); |
| 578 | + |
| 579 | + entry.Size = fileSize; |
| 580 | + |
| 581 | + tarOutputStream.PutNextEntry(entry); |
| 582 | + await inputFileStream.CopyToAsync(tarOutputStream).ConfigureAwait(false); |
| 583 | + tarOutputStream.CloseEntry(); |
| 584 | + } |
| 585 | + |
| 586 | + memoryStream.Position = 0; |
| 587 | + |
| 588 | + await memoryStream.CopyToAsync(stdIn).ConfigureAwait(false); |
| 589 | + await memoryStream.FlushAsync().ConfigureAwait(false); |
| 590 | + stdIn.Close(); |
| 591 | + } |
| 592 | + } |
| 593 | + catch (Exception ex) |
| 594 | + { |
| 595 | + throw new IOException($"Copy command failed: {ex.Message}"); |
| 596 | + } |
| 597 | + |
| 598 | + using StreamReader streamReader = new StreamReader(stdError); |
| 599 | + while (streamReader.EndOfStream == false) |
| 600 | + { |
| 601 | + string error = await streamReader.ReadToEndAsync().ConfigureAwait(false); |
| 602 | + throw new IOException($"Copy command failed: {error}"); |
| 603 | + } |
| 604 | + }); |
| 605 | + |
| 606 | + string destinationFolder = Path.GetDirectoryName(destinationFilePath).Replace("\\", "/"); |
| 607 | + |
| 608 | + return await client.NamespacedPodExecAsync( |
| 609 | + name, |
| 610 | + @namespace, |
| 611 | + container, |
| 612 | + new string[] { "tar", "-xmf", "-", "-C", destinationFolder }, |
| 613 | + false, |
| 614 | + handler, |
| 615 | + cancellationToken).ConfigureAwait(false); |
| 616 | + } |
| 617 | + |
| 618 | + |
| 619 | + void Cleanup() |
| 620 | + { |
| 621 | + var pods = client.CoreV1.ListNamespacedPod(namespaceParameter); |
| 622 | + while (pods.Items.Any(p => p.Metadata.Name == podName)) |
| 623 | + { |
| 624 | + try |
| 625 | + { |
| 626 | + client.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); |
| 627 | + } |
| 628 | + catch (HttpOperationException e) |
| 629 | + { |
| 630 | + if (e.Response.StatusCode == System.Net.HttpStatusCode.NotFound) |
| 631 | + { |
| 632 | + return; |
| 633 | + } |
| 634 | + } |
| 635 | + } |
| 636 | + } |
| 637 | + |
| 638 | + try |
| 639 | + { |
| 640 | + Cleanup(); |
| 641 | + |
| 642 | + client.CoreV1.CreateNamespacedPod( |
| 643 | + new V1Pod() |
| 644 | + { |
| 645 | + Metadata = new V1ObjectMeta { Name = podName, }, |
| 646 | + Spec = new V1PodSpec |
| 647 | + { |
| 648 | + Containers = new[] |
| 649 | + { |
| 650 | + new V1Container() |
| 651 | + { |
| 652 | + Name = "container", |
| 653 | + Image = "ubuntu", |
| 654 | + // Image = "busybox", // TODO not work with busybox |
| 655 | + Command = new[] { "sleep" }, |
| 656 | + Args = new[] { "infinity" }, |
| 657 | + }, |
| 658 | + }, |
| 659 | + }, |
| 660 | + }, |
| 661 | + namespaceParameter); |
| 662 | + |
| 663 | + var lines = new List<string>(); |
| 664 | + var started = new ManualResetEvent(false); |
| 665 | + |
| 666 | + async Task<V1Pod> Pod() |
| 667 | + { |
| 668 | + var pods = client.CoreV1.ListNamespacedPod(namespaceParameter); |
| 669 | + var pod = pods.Items.First(p => p.Metadata.Name == podName); |
| 670 | + while (pod.Status.Phase != "Running") |
| 671 | + { |
| 672 | + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); |
| 673 | + return await Pod().ConfigureAwait(false); |
| 674 | + } |
| 675 | + |
| 676 | + return pod; |
| 677 | + } |
| 678 | + |
| 679 | + var pod = await Pod().ConfigureAwait(false); |
| 680 | + |
| 681 | + |
| 682 | + async Task AssertMd5sumAsync(string file, byte[] orig) |
| 683 | + { |
| 684 | + var ws = await client.WebSocketNamespacedPodExecAsync( |
| 685 | + pod.Metadata.Name, |
| 686 | + pod.Metadata.NamespaceProperty, |
| 687 | + new string[] { "md5sum", file }, |
| 688 | + "container").ConfigureAwait(false); |
| 689 | + |
| 690 | + var demux = new StreamDemuxer(ws); |
| 691 | + demux.Start(); |
| 692 | + |
| 693 | + var buff = new byte[4096]; |
| 694 | + var stream = demux.GetStream(1, 1); |
| 695 | + var read = stream.Read(buff, 0, 4096); |
| 696 | + var remotemd5 = Encoding.Default.GetString(buff); |
| 697 | + remotemd5 = remotemd5.Substring(0, 32); |
| 698 | + |
| 699 | + var md5 = MD5.Create().ComputeHash(orig); |
| 700 | + var localmd5 = BitConverter.ToString(md5).Replace("-", string.Empty).ToLower(); |
| 701 | + |
| 702 | + Assert.Equal(localmd5, remotemd5); |
| 703 | + } |
| 704 | + |
| 705 | + |
| 706 | + // |
| 707 | + { |
| 708 | + // small |
| 709 | + var content = new byte[1 * 1024 * 1024]; |
| 710 | + new Random().NextBytes(content); |
| 711 | + await CopyFileToPodAsync(pod.Metadata.Name, pod.Metadata.NamespaceProperty, "container", new MemoryStream(content), "/tmp/test").ConfigureAwait(false); |
| 712 | + await AssertMd5sumAsync("/tmp/test", content).ConfigureAwait(false); |
| 713 | + } |
| 714 | + |
| 715 | + { |
| 716 | + // big |
| 717 | + var content = new byte[40 * 1024 * 1024]; |
| 718 | + new Random().NextBytes(content); |
| 719 | + await CopyFileToPodAsync(pod.Metadata.Name, pod.Metadata.NamespaceProperty, "container", new MemoryStream(content), "/tmp/test").ConfigureAwait(false); |
| 720 | + await AssertMd5sumAsync("/tmp/test", content).ConfigureAwait(false); |
| 721 | + } |
| 722 | + } |
| 723 | + finally |
| 724 | + { |
| 725 | + Cleanup(); |
| 726 | + } |
| 727 | + } |
| 728 | + |
551 | 729 | public static IKubernetes CreateClient()
|
552 | 730 | {
|
553 | 731 | return new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
|
|
0 commit comments