diff --git a/assets/screenshot.png b/assets/screenshot.png index 549fb07..d7d589a 100644 Binary files a/assets/screenshot.png and b/assets/screenshot.png differ diff --git a/go.mod b/go.mod index 554e475..8b251a1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,11 @@ module github.com/darox/k8s-iperf go 1.16 require ( + github.com/fatih/color v1.7.0 github.com/spf13/cobra v1.2.1 + github.com/stretchr/testify v1.7.0 + golang.org/x/term v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 diff --git a/go.sum b/go.sum index 24e60ef..f226299 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -220,7 +221,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -279,6 +282,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -293,6 +297,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -315,6 +320,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -350,6 +356,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -389,8 +397,10 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -415,6 +425,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -462,12 +474,20 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -475,8 +495,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -533,6 +556,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 5970ae4..fcad9f6 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -23,6 +23,9 @@ var runCmd = &cobra.Command{ image, _ := cmd.Flags().GetString("k8s-image") serverNode, _ := cmd.Flags().GetString("k8s-server-node") clientNode, _ := cmd.Flags().GetString("k8s-client-node") + domain, _ := cmd.Flags().GetString("k8s-domain") + cleanup, _ := cmd.Flags().GetBool("k8s-cleanup") + client, err := k8s.NewClient() if err != nil { return fmt.Errorf("failed to create Kubernetes client: %w", err) @@ -41,6 +44,8 @@ var runCmd = &cobra.Command{ IperfArgs: iperfArgs, ServerNode: serverNode, ClientNode: clientNode, + Domain: domain, + Cleanup: cleanup, } return iperf.RunTest(config) @@ -52,6 +57,8 @@ func init() { runCmd.Flags().StringP("k8s-image", "", "dariomader/iperf3:latest", "Docker image to use for the test") runCmd.Flags().StringP("k8s-server-node", "", "", "Server node to use for the test") runCmd.Flags().StringP("k8s-client-node", "", "", "Client node to use for the test") + runCmd.Flags().StringP("k8s-domain", "", "cluster.local", "Kubernetes domain to use for the test") + runCmd.Flags().BoolP("k8s-cleanup", "", true, "Cleanup resources after the test") rootCmd.AddCommand(runCmd) } diff --git a/pkg/iperf/test.go b/pkg/iperf/test.go index a8f0a71..e2deeba 100644 --- a/pkg/iperf/test.go +++ b/pkg/iperf/test.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/signal" + "strings" "syscall" "time" @@ -15,19 +16,34 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + + "encoding/json" + + "github.com/fatih/color" ) // At the package level, add this static error: var errIperf3ClientPodFailed = errors.New("iperf3 client pod failed") +// Add static error variables +var ( + errEmptyLogs = errors.New("iperf3 client returned empty logs") + errMissingStartField = errors.New("missing or invalid 'start' field in JSON data") + errMissingEndField = errors.New("missing or invalid 'end' field in JSON data") + errMissingConnectedField = errors.New("missing or invalid 'connected' field in JSON data") + errInvalidConnectedData = errors.New("invalid 'connected' data structure in JSON data") +) + // TestConfig holds the configuration for the iperf3 test type TestConfig struct { Client *kubernetes.Clientset Namespace string + Domain string Image string IperfArgs []string ServerNode string ClientNode string + Cleanup bool } func RunTest(config TestConfig) error { @@ -58,35 +74,49 @@ func RunTest(config TestConfig) error { } func runTestInternal(ctx context.Context, config TestConfig) error { + // Defer cleanup if Cleanup is set to true + if config.Cleanup { + defer func() { + if err := cleanup(config.Client, config.Namespace); err != nil { + fmt.Printf("Failed to cleanup resources: %v\n", err) + } + }() + } + // Deploy iperf3 server if err := deployIperf3Server(config); err != nil { + color.Red("✘ Failed to deploy iperf3 server: %v", err) return fmt.Errorf("failed to deploy iperf3 server: %w", err) } - fmt.Println("iperf3 server deployed successfully") + color.Green("✔ iperf3 server deployed successfully") // Create service for iperf3 server if err := createIperf3Service(config.Client, config.Namespace); err != nil { + color.Red("✘ Failed to create iperf3 service: %v", err) return fmt.Errorf("failed to create iperf3 service: %w", err) } - fmt.Println("iperf3 service created successfully") + color.Green("✔ iperf3 service created successfully") // Wait for server to be ready if err := waitForDeploymentReady(config.Client, config.Namespace, "iperf3-server", 60*time.Second); err != nil { + color.Red("✘ iperf3 server failed to become ready: %v", err) return fmt.Errorf("iperf3 server failed to become ready: %w", err) } - // Run iperf3 client - fmt.Println("Running iperf3 test................") + // Add this: Wait for the iperf3 server pod to be ready + if err := waitForPodReady(ctx, config.Client, config.Namespace, "app=iperf3-server", 60*time.Second); err != nil { + color.Red("✘ iperf3 server pod failed to become ready: %v", err) + return fmt.Errorf("iperf3 server pod failed to become ready: %w", err) + } + + color.Green("✔ iperf3 server is up and ready") + err := runIperf3Client(ctx, config) if err != nil { + color.Red("✘ iperf3 client test failed: %v", err) return fmt.Errorf("iperf3 client test failed: %w", err) } - // Cleanup resources - if err := cleanup(config.Client, config.Namespace); err != nil { - return fmt.Errorf("failed to cleanup resources: %w", err) - } - return nil } @@ -132,6 +162,15 @@ func deployIperf3Server(config TestConfig) error { ReadOnlyRootFilesystem: boolPtr(false), }, ImagePullPolicy: corev1.PullAlways, + StartupProbe: &corev1.Probe{ + Handler: corev1.Handler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(5201), + }, + }, + InitialDelaySeconds: 5, + FailureThreshold: 1, + }, }, }, }, @@ -176,7 +215,8 @@ func createIperf3Service(client *kubernetes.Clientset, namespace string) error { } func runIperf3Client(ctx context.Context, config TestConfig) error { - args := []string{"-c", "iperf3-server"} + serverFQDN := fmt.Sprintf("iperf3-server.%s.svc.%s", config.Namespace, config.Domain) + args := []string{"-c", serverFQDN, "-J"} args = append(args, config.IperfArgs...) pod := &corev1.Pod{ @@ -220,6 +260,10 @@ func runIperf3Client(ctx context.Context, config TestConfig) error { return err } + color.Green("✔ iperf3 client pod created successfully") + + color.Green("► Starting iperf3 test") + // Wait for the client pod to complete var podPhase corev1.PodPhase for { @@ -248,10 +292,112 @@ PodCompleted: return err } - fmt.Println(string(logs)) + // Check if logs are empty + if len(logs) == 0 { + color.Red("✘ iperf3 client returned empty logs") + return errEmptyLogs + } + + // Parse and print the formatted summary + if err := printIperfSummary(logs); err != nil { + color.Red("✘ Failed to parse iperf output: %v", err) + return fmt.Errorf("failed to parse iperf output: %w", err) + } if podPhase == corev1.PodFailed { - return fmt.Errorf("iperf3 client execution: %w", errIperf3ClientPodFailed) + return errIperf3ClientPodFailed + } + + return nil +} + +func printIperfSummary(jsonData []byte) error { + var result map[string]interface{} + if err := json.Unmarshal(jsonData, &result); err != nil { + return fmt.Errorf("failed to unmarshal JSON data: %w", err) + } + + // Check if the required fields exist + start, ok := result["start"].(map[string]interface{}) + if !ok { + return errMissingStartField + } + + end, ok := result["end"].(map[string]interface{}) + if !ok { + return errMissingEndField + } + + connected, ok := start["connected"].([]interface{}) + if !ok || len(connected) == 0 { + return errMissingConnectedField + } + + connectedMap, ok := connected[0].(map[string]interface{}) + if !ok { + return errInvalidConnectedData + } + + // Extract relevant information + testStart := start["test_start"].(map[string]interface{}) + sumSent := end["sum_sent"].(map[string]interface{}) + sumReceived := end["sum_received"].(map[string]interface{}) + + // Print summary + fmt.Println() // Add a line break here + summaryTitle := "iPerf3 Test Summary" + color.Cyan(summaryTitle) + + cpuUtil := end["cpu_utilization_percent"].(map[string]interface{}) + + // Prepare all lines + lines := []string{ + fmt.Sprintf("Connection Details:"), + fmt.Sprintf(" Local: %s:%d", connectedMap["local_host"], int(connectedMap["local_port"].(float64))), + fmt.Sprintf(" Remote: %s:%d", connectedMap["remote_host"], int(connectedMap["remote_port"].(float64))), + fmt.Sprintf(""), + fmt.Sprintf("Test Configuration:"), + fmt.Sprintf(" Protocol: %s", testStart["protocol"]), + fmt.Sprintf(" Duration: %.2f seconds", sumSent["seconds"]), + fmt.Sprintf(" Parallel Streams: %d", int(testStart["num_streams"].(float64))), + fmt.Sprintf(""), + fmt.Sprintf("Results:"), + fmt.Sprintf(" Sent: %.2f Mbits/sec", sumSent["bits_per_second"].(float64)/1e6), + fmt.Sprintf(" Received: %.2f Mbits/sec", sumReceived["bits_per_second"].(float64)/1e6), + } + + if retransmits, ok := sumSent["retransmits"]; ok { + lines = append(lines, fmt.Sprintf(" Retransmits: %d", int(retransmits.(float64)))) + } + + lines = append(lines, + fmt.Sprintf(""), + fmt.Sprintf("CPU Utilization:"), + fmt.Sprintf(" Local: %.2f%%", cpuUtil["host_total"].(float64)), + fmt.Sprintf(" Remote: %.2f%%", cpuUtil["remote_total"].(float64)), + ) + + // Calculate the length of the longest line + maxLength := len(summaryTitle) + for _, line := range lines { + if len(line) > maxLength { + maxLength = len(line) + } + } + + // Print dashes + color.Cyan(strings.Repeat("-", maxLength)) + + // Print all lines + for _, line := range lines { + if strings.HasPrefix(line, "Connection Details:") || + strings.HasPrefix(line, "Test Configuration:") || + strings.HasPrefix(line, "Results:") || + strings.HasPrefix(line, "CPU Utilization:") { + color.Yellow(line) + } else { + fmt.Println(line) + } } return nil @@ -291,3 +437,29 @@ func waitForDeploymentReady(client *kubernetes.Clientset, namespace, deploymentN return deployment.Status.ReadyReplicas == *deployment.Spec.Replicas, nil }) } + +// Add this new function +func waitForPodReady(ctx context.Context, client *kubernetes.Clientset, namespace, labelSelector string, timeout time.Duration) error { + return wait.PollImmediate(time.Second, timeout, func() (bool, error) { + pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector}) + if err != nil { + return false, err + } + + if len(pods.Items) == 0 { + return false, nil + } + + for _, pod := range pods.Items { + if pod.Status.Phase == corev1.PodRunning { + for _, condition := range pod.Status.Conditions { + if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { + return true, nil + } + } + } + } + + return false, nil + }) +}