diff --git a/go.mod b/go.mod index a04297e1ea2d..3faed3a670d9 100644 --- a/go.mod +++ b/go.mod @@ -83,6 +83,8 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require github.com/shirou/gopsutil/v4 v4.24.12 + require ( dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect @@ -131,6 +133,7 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.8.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect @@ -143,6 +146,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -185,6 +189,7 @@ require ( github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lithammer/dedent v1.1.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect @@ -214,6 +219,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect @@ -228,6 +234,8 @@ require ( github.com/spf13/cast v1.7.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b // indirect github.com/x448/float16 v0.8.4 // indirect @@ -237,6 +245,7 @@ require ( github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc // indirect github.com/zmap/zlint/v3 v3.1.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect diff --git a/go.sum b/go.sum index b27ed3e3d4c4..f5c0d826865b 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,8 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arX github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= +github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -205,6 +207,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= @@ -283,6 +287,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -389,6 +394,8 @@ github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffkt github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -500,6 +507,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -543,6 +552,8 @@ github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ula github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= +github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -578,6 +589,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= @@ -608,6 +623,8 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEAB github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= @@ -725,6 +742,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -734,6 +752,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -744,7 +763,9 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/pkg/config/runtime.go b/pkg/config/runtime.go index 7bcfded69e9c..67ce15f4da2a 100644 --- a/pkg/config/runtime.go +++ b/pkg/config/runtime.go @@ -28,6 +28,7 @@ import ( "github.com/k0sproject/k0s/internal/pkg/dir" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" "github.com/k0sproject/k0s/pkg/constant" + "github.com/shirou/gopsutil/v4/process" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -97,9 +98,14 @@ func LoadRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) { // an error is returned, which allows the controller startup to proceed to // initialize a new runtime config. if spec.Pid != 0 { - if err := checkPid(spec.Pid); err != nil { + isRunning, err := checkInstanceRunning(spec.Pid) + if !isRunning || err != nil { defer func() { _ = spec.Cleanup() }() - return nil, errors.Join(ErrK0sNotRunning, err) + if !isRunning { + return nil, errors.Join(ErrK0sNotRunning, err) + } else if err != nil { + return nil, fmt.Errorf("%w: failed to check if instance is running", err) + } } } @@ -192,3 +198,56 @@ func (r *RuntimeConfigSpec) Cleanup() error { } return nil } + +// returns: +// - true, nil if the pid is the same k0s executable and running +// - false, nil if the pid is not running or another executable +// - false, error on any kind of internal error checking the status +// +// process does not exist -> error +// process exists and is same executable -> error +// process exists and is not same executable -> nil +func checkInstanceRunning(pid int) (bool, error) { + // create the process object + proc, err := process.NewProcess(int32(pid)) + if err != nil { + return false, fmt.Errorf("failed to create process object: %w", err) + } + + // first check if the pid is running + isRunning, err := proc.IsRunning() + if err != nil { + return false, fmt.Errorf("failed to check if process is running: %w", err) + } + if !isRunning { + return false, nil + } + + // Get the executable path of the target pid + // - no need to resolve symlinks here, it is already a resolved path + exeTarget, err := proc.Exe() + if err != nil { + return false, fmt.Errorf("failed to get process executable path: %w", err) + } + + // Get the executable path of the current process + currentExe, err := os.Executable() + if err != nil { + return false, fmt.Errorf("failed to get current executable: %w", err) + } + + // Resolve symlinks + currentExe, err = filepath.EvalSymlinks(currentExe) + if err != nil { + return false, fmt.Errorf("failed to resolve current executable symlinks: %w", err) + } + + // check that the executable is the same in order to issue an error, if it would be a different + // than it can't be a running instance of k0s, which the check is all about + if exeTarget != currentExe { + return false, nil + } + + // return that k0s is running witht he same executable image + return true, nil +} diff --git a/pkg/config/runtime_test.go b/pkg/config/runtime_test.go index 0e57882e5870..fd8f8d8f2786 100644 --- a/pkg/config/runtime_test.go +++ b/pkg/config/runtime_test.go @@ -21,7 +21,10 @@ import ( "testing" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" + "github.com/shirou/gopsutil/v4/process" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" ) func TestLoadRuntimeConfig_Legacy(t *testing.T) { @@ -86,6 +89,12 @@ spec: assert.ErrorIs(t, err, ErrK0sNotRunning) } +// The idea behind this test is: +// - test to create a new runtime config from scratch +// - actually test if k0s is still running by checking against: +// - the same executable with the same pid as the runtime config +// - a different executable with the same pid as the runtime config +// - a pid that is not running anymore func TestNewRuntimeConfig(t *testing.T) { // create a temporary directory for k0s files tempDir, err := os.MkdirTemp("", "k0s") @@ -110,6 +119,7 @@ func TestNewRuntimeConfig(t *testing.T) { } // create a new runtime config and check if it's valid + // this will set the pid property to the current process id of the test run spec, err := NewRuntimeConfig(k0sVars) assert.NoError(t, err) assert.NotNil(t, spec) @@ -121,7 +131,94 @@ func TestNewRuntimeConfig(t *testing.T) { assert.Equal(t, "10.0.0.1", cfg.Spec.API.Address) // try to create a new runtime config when one is already active and check if it returns an error + // this will fail with ErrK0sAlreadyRunning since the pid property is this currently + // executing test process (with the unit test) _, err = NewRuntimeConfig(k0sVars) assert.Error(t, err) assert.ErrorIs(t, err, ErrK0sAlreadyRunning) + + // Start a process in the background with a shell that sleeps for a long time + // (infinity is not available on default macOS zsh), this simulates + // a different running process with the same pid as stored in the runtime config, but + // from a different executable image + cmd := getBackgroundCommand() + err = cmd.Start() + require.NoError(t, err, "Failed to start process") + + // remember the newly started PID + pid := cmd.Process.Pid + cleanupPid := pid + + // create the process object + proc, err := process.NewProcess(int32(pid)) + require.NoError(t, err, "failed to create process object") + + // check that the pid is running + isRunning, err := proc.IsRunning() + require.NoError(t, err, "IsRunning works correctly for newly started background process") + require.True(t, isRunning, "background process is running") + + // this is the cleanup function that cleans the process in case of a test failure + cleanupFunc := func() { + if cleanupPid != 0 { + // Shut down the child process (killing it is fine here) + err = proc.Kill() + require.NoError(t, err, "Failed to shut down child process") + + // Wait for the command to exit (ingore the error, since we're just cleaning up) + _ = cmd.Wait() + + // set the cleanupPid to 0 to indicate successful termination and not clean up twice + cleanupPid = 0 + } + } + defer cleanupFunc() + + // modify the pid in the runtime config + err = updateRuntimeConfigPID(k0sVars.RuntimeConfigPath, pid) + require.NoError(t, err) + + // create a new runtime config, but this time with a pid of a process that actually exists + // but is from a different executable image + _, err = NewRuntimeConfig(k0sVars) + require.NoError(t, err) + + // now kill the other process in order to test for the same pid, with the difference + // that we now know it's from a process that is definitely not running anymore + cleanupFunc() + + // modify the pid in the runtime config (again to the same pid, which is not running anymore) + // this needs to be done since the previous PID value was overwritten with the new + // config from the last call to NewRuntimeConfig) + err = updateRuntimeConfigPID(k0sVars.RuntimeConfigPath, pid) + require.NoError(t, err) + + // create a new runtime config, this should also succeed since the pid is not running + _, err = NewRuntimeConfig(k0sVars) + assert.NoError(t, err) +} + +func updateRuntimeConfigPID(path string, pid int) error { + content, err := os.ReadFile(path) + if err != nil { + return err + } + + config := &RuntimeConfig{} + err = yaml.Unmarshal(content, config) + if err != nil { + return err + } + config.Spec.Pid = pid + + content, err = yaml.Marshal(config) + if err != nil { + return err + } + + err = os.WriteFile(path, content, 0600) + if err != nil { + return err + } + return nil } diff --git a/pkg/config/runtime_unix.go b/pkg/config/runtime_unix_test.go similarity index 65% rename from pkg/config/runtime_unix.go rename to pkg/config/runtime_unix_test.go index 134e6a863ee6..ab8fa9b12421 100644 --- a/pkg/config/runtime_unix.go +++ b/pkg/config/runtime_unix_test.go @@ -1,7 +1,7 @@ //go:build unix /* -Copyright 2023 k0s authors +Copyright 2025 k0s authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,20 +19,15 @@ limitations under the License. package config import ( - "fmt" - "os" + "os/exec" "syscall" ) -func checkPid(pid int) error { - proc, err := os.FindProcess(pid) - if err != nil { - return fmt.Errorf("failed to find process: %w", err) +// a simple long running background command for unix systems +func getBackgroundCommand() *exec.Cmd { + cmd := exec.Command("/bin/sh", "-c", "sleep 9999") + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, // Ensure the process runs in a separate process group } - - if err := proc.Signal(syscall.Signal(0)); err != nil { - return fmt.Errorf("failed to signal process: %w", err) - } - - return nil + return cmd } diff --git a/pkg/config/runtime_windows.go b/pkg/config/runtime_windows_test.go similarity index 60% rename from pkg/config/runtime_windows.go rename to pkg/config/runtime_windows_test.go index dd8903c93c0d..c4cf5fbd150c 100644 --- a/pkg/config/runtime_windows.go +++ b/pkg/config/runtime_windows_test.go @@ -1,5 +1,7 @@ +//go:build windows + /* -Copyright 2023 k0s authors +Copyright 2025 k0s authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,17 +19,15 @@ limitations under the License. package config import ( - "fmt" - - "golang.org/x/sys/windows" + "os/exec" + "syscall" ) -func checkPid(pid int) error { - procHandle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid)) - if err != nil { - return fmt.Errorf("failed to find process: %w", err) +// a simple long running background command for windows systems +func getBackgroundCommand() *exec.Cmd { + cmd := exec.Command("cmd.exe") + cmd.SysProcAttr = &syscall.SysProcAttr{ + CmdLine: "cmd.exe /C timeout /T 9999", // provide the full command line here, see docs for exec.Command() } - defer func() { _ = windows.CloseHandle(procHandle) }() - - return nil + return cmd }