diff --git a/go.mod b/go.mod index 88f196d4..936d94ac 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,9 @@ module github.com/equinix/cloud-provider-equinix-metal go 1.21 require ( - github.com/google/uuid v1.5.0 - github.com/hashicorp/go-retryablehttp v0.7.5 - github.com/packethost/packet-api-server v0.0.0-20230223042617-bc7d1539adbb - github.com/packethost/packngo v0.31.0 + github.com/equinix/equinix-sdk-go v0.30.0 + github.com/google/uuid v1.3.1 + github.com/gorilla/mux v1.8.1 github.com/pallinder/go-randomdata v1.2.0 go.universe.tf/metallb v0.13.7 golang.org/x/exp v0.0.0-20231006140011-7918f672742d @@ -17,6 +16,7 @@ require ( k8s.io/cloud-provider v0.26.10 k8s.io/component-base v0.26.10 k8s.io/klog/v2 v2.100.1 + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 sigs.k8s.io/controller-runtime v0.14.7 sigs.k8s.io/yaml v1.3.0 ) @@ -52,10 +52,8 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -112,7 +110,6 @@ require ( k8s.io/controller-manager v0.26.10 // indirect k8s.io/kms v0.26.10 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.37 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/go.sum b/go.sum index e2857471..3051de0b 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,6 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -109,6 +107,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/equinix/equinix-sdk-go v0.30.0 h1:u/+/p00mfAhDhoLvP1jTKruXndAYWoTwqN65BTbAPCg= +github.com/equinix/equinix-sdk-go v0.30.0/go.mod h1:qnpdRzVftHFNaJFk1VSIrAOTLrIoeDrxzUr3l8ARyvQ= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -224,12 +224,12 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= @@ -240,12 +240,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -295,7 +289,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -307,10 +300,6 @@ github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/packethost/packet-api-server v0.0.0-20230223042617-bc7d1539adbb h1:Tq95IyiReWp+eWNco4KoDNHIALQbNHFg5dD4TELEg9o= -github.com/packethost/packet-api-server v0.0.0-20230223042617-bc7d1539adbb/go.mod h1:xX9d7NVrCzFoIFdy5hav33pCMZwiE37ZKgd1XM9qMEY= -github.com/packethost/packngo v0.31.0 h1:LLH90ardhULWbagBIc3I3nl2uU75io0a7AwY6hyi0S4= -github.com/packethost/packngo v0.31.0/go.mod h1:Io6VJqzkiqmIEQbpOjeIw9v8q9PfcTEq8TEY/tMQsfw= github.com/pallinder/go-randomdata v1.2.0 h1:EJPxw+sgM1mbMW8RBu5zkG4FbloJpDOCCqPccdWto8A= github.com/pallinder/go-randomdata v1.2.0/go.mod h1:p8CasZQiWDLuaKy5ihVvdshqc7LlL6ovmRxAuNXgi3U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -366,18 +355,14 @@ github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ai github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= @@ -449,7 +434,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= diff --git a/metal/bgp.go b/metal/bgp.go index 6cd6dd0c..e4031866 100644 --- a/metal/bgp.go +++ b/metal/bgp.go @@ -1,24 +1,25 @@ package metal import ( + "context" "errors" "fmt" "strings" - "github.com/packethost/packngo" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" ) type bgp struct { project string - client *packngo.Client + client *metal.BGPApiService k8sclient kubernetes.Interface localASN int bgpPass string } -func newBGP(client *packngo.Client, k8sclient kubernetes.Interface, metalConfig Config) (*bgp, error) { +func newBGP(client *metal.BGPApiService, k8sclient kubernetes.Interface, metalConfig Config) (*bgp, error) { b := &bgp{ client: client, k8sclient: k8sclient, @@ -38,7 +39,9 @@ func newBGP(client *packngo.Client, k8sclient kubernetes.Interface, metalConfig // enableBGP enable bgp on the project func (b *bgp) enableBGP() error { // first check if it is enabled before trying to create it - bgpConfig, _, err := b.client.BGPConfig.Get(b.project, &packngo.GetOptions{}) + bgpConfig, _, err := b.client. + FindBgpConfigByProject(context.Background(), b.project). + Execute() // if we already have a config, just return // we need some extra handling logic because the API always returns 200, even if // not BGP config is in place. @@ -47,35 +50,42 @@ func (b *bgp) enableBGP() error { // - bgpConfig struct exists // - bgpConfig struct has non-blank ID // - bgpConfig struct does not have Status=="disabled" - if err == nil && bgpConfig != nil && bgpConfig.ID != "" && strings.ToLower(bgpConfig.Status) != "disabled" { - b.localASN = bgpConfig.Asn - b.bgpPass = bgpConfig.Md5 + if err == nil && bgpConfig != nil && bgpConfig.GetId() != "" && bgpConfig.GetStatus() != metal.BGPCONFIGSTATUS_DISABLED { + b.localASN = int(bgpConfig.GetAsn()) + b.bgpPass = *bgpConfig.Md5.Get() return nil } // we did not have a valid one, so create it - req := packngo.CreateBGPConfigRequest{ - Asn: b.localASN, - Md5: b.bgpPass, + req := metal.BgpConfigRequestInput{ + Asn: int32(b.localASN), + Md5: &b.bgpPass, DeploymentType: "local", - UseCase: "kubernetes-load-balancer", + UseCase: metal.PtrString("kubernetes-load-balancer"), } - _, err = b.client.BGPConfig.Create(b.project, req) + _, err = b.client. + RequestBgpConfig(context.Background(), b.project). + BgpConfigRequestInput(req). + Execute() return err } // ensureNodeBGPEnabled check if the node has bgp enabled, and set it if it does not -func ensureNodeBGPEnabled(id string, client *packngo.Client) error { +func ensureNodeBGPEnabled(id string, client *metal.APIClient) error { // if we are rnning ccm properly, then the provider ID will be on the node object id, err := deviceIDFromProviderID(id) if err != nil { return err } // fortunately, this is idempotent, so just create - req := packngo.CreateBGPSessionRequest{ - AddressFamily: "ipv4", + req := metal.BGPSessionInput{ + AddressFamily: metal.BGPSESSIONINPUTADDRESSFAMILY_IPV4.Ptr(), } - _, response, err := client.BGPSessions.Create(id, req) + _, response, err := client.DevicesApi. + CreateBgpSession(context.Background(), id). + BGPSessionInput(req). + Execute() + // if we already had one, then we can ignore the error // this really should be a 409, but 422 is what is returned if response.StatusCode == 422 && strings.Contains(fmt.Sprintf("%s", err), "already has session") { @@ -85,18 +95,22 @@ func ensureNodeBGPEnabled(id string, client *packngo.Client) error { } // getNodeBGPConfig get the BGP config for a specific node -func getNodeBGPConfig(providerID string, client *packngo.Client) (peer *packngo.BGPNeighbor, err error) { +func getNodeBGPConfig(providerID string, client *metal.APIClient) (peer *metal.BgpNeighborData, err error) { id, err := deviceIDFromProviderID(providerID) if err != nil { return nil, err } - neighbours, _, err := client.Devices.ListBGPNeighbors(id, nil) + bgpSessions, _, err := client.DevicesApi. + GetBgpNeighborData(context.Background(), id). + Execute() if err != nil { return nil, fmt.Errorf("failed to get device neighbours for device %s: %w", id, err) } + + bgpNeighbours := bgpSessions.GetBgpNeighbors() // we need the ipv4 neighbour - for _, n := range neighbours { - if n.AddressFamily == 4 { + for _, n := range bgpNeighbours { + if n.GetAddressFamily() == 4 { return &n, nil } } diff --git a/metal/cloud.go b/metal/cloud.go index d48e5045..00be36da 100644 --- a/metal/cloud.go +++ b/metal/cloud.go @@ -4,8 +4,7 @@ import ( "fmt" "io" - "github.com/packethost/packngo" - + metal "github.com/equinix/equinix-sdk-go/services/metalv1" cloudprovider "k8s.io/cloud-provider" "k8s.io/component-base/version" "k8s.io/klog/v2" @@ -27,7 +26,7 @@ const ( // cloud implements cloudprovider.Interface type cloud struct { - client *packngo.Client + client *metal.APIClient config Config instances *instances loadBalancer *loadBalancers @@ -39,7 +38,7 @@ type cloud struct { var _ cloudprovider.Interface = (*cloud)(nil) -func newCloud(metalConfig Config, client *packngo.Client) (cloudprovider.Interface, error) { +func newCloud(metalConfig Config, client *metal.APIClient) (cloudprovider.Interface, error) { return &cloud{ client: client, config: metalConfig, @@ -59,8 +58,10 @@ func init() { printMetalConfig(metalConfig) // set up our client and create the cloud interface - client := packngo.NewClientWithAuth("cloud-provider-equinix-metal", metalConfig.AuthToken, nil) - client.UserAgent = fmt.Sprintf("cloud-provider-equinix-metal/%s %s", version.Get(), client.UserAgent) + configuration := metal.NewConfiguration() + configuration.AddDefaultHeader("X-Auth-Token", metalConfig.AuthToken) + configuration.UserAgent = fmt.Sprintf("cloud-provider-equinix-metal/%s %s", version.Get(), configuration.UserAgent) + client := metal.NewAPIClient(configuration) cloud, err := newCloud(metalConfig, client) if err != nil { return nil, fmt.Errorf("failed to create new cloud handler: %w", err) @@ -77,7 +78,7 @@ func (c *cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, klog.V(5).Info("called Initialize") clientset := clientBuilder.ClientOrDie("cloud-provider-equinix-metal-shared-informers") // initialize the individual services - epm, err := newControlPlaneEndpointManager(clientset, stop, c.config.EIPTag, c.config.ProjectID, c.client.DeviceIPs, c.client.ProjectIPs, c.config.APIServerPort, c.config.EIPHealthCheckUseHostIP) + epm, err := newControlPlaneEndpointManager(clientset, stop, c.config.EIPTag, c.config.ProjectID, c.client, c.config.APIServerPort, c.config.EIPHealthCheckUseHostIP) if err != nil { klog.Fatalf("could not initialize ControlPlaneEndpointManager: %v", err) } @@ -85,18 +86,18 @@ func (c *cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, if err != nil { klog.Fatalf("could not initialize ControlPlaneEndpointManager: %v", err) } - bgp, err := newBGP(c.client, clientset, c.config) + bgp, err := newBGP(c.client.BGPApi, clientset, c.config) if err != nil { klog.Fatalf("could not initialize BGP: %v", err) } - lb, err := newLoadBalancers(c.client, clientset, c.config.ProjectID, c.config.Metro, c.config.Facility, c.config.LoadBalancerSetting, bgp.localASN, bgp.bgpPass, c.config.AnnotationNetworkIPv4Private, c.config.AnnotationLocalASN, c.config.AnnotationPeerASN, c.config.AnnotationPeerIP, c.config.AnnotationSrcIP, c.config.AnnotationBGPPass, c.config.AnnotationEIPMetro, c.config.AnnotationEIPFacility, c.config.BGPNodeSelector, c.config.EIPTag) + lb, err := newLoadBalancers(c.client, clientset, c.config.AuthToken, c.config.ProjectID, c.config.Metro, c.config.Facility, c.config.LoadBalancerSetting, bgp.localASN, bgp.bgpPass, c.config.AnnotationNetworkIPv4Private, c.config.AnnotationLocalASN, c.config.AnnotationPeerASN, c.config.AnnotationPeerIP, c.config.AnnotationSrcIP, c.config.AnnotationBGPPass, c.config.AnnotationEIPMetro, c.config.AnnotationEIPFacility, c.config.BGPNodeSelector, c.config.EIPTag) if err != nil { klog.Fatalf("could not initialize LoadBalancers: %v", err) } c.loadBalancer = lb c.bgp = bgp - c.instances = newInstances(c.client, c.config.ProjectID) + c.instances = newInstances(c.client.DevicesApi, c.config.ProjectID) c.controlPlaneEndpointManager = epm c.controlPlaneLoadBalancerManager = lbm diff --git a/metal/cloud_test.go b/metal/cloud_test.go index 774cfc4f..7dd83c2d 100644 --- a/metal/cloud_test.go +++ b/metal/cloud_test.go @@ -1,20 +1,20 @@ package metal import ( + "fmt" "net/http/httptest" "net/url" "testing" - "github.com/google/uuid" - retryablehttp "github.com/hashicorp/go-retryablehttp" - emServer "github.com/packethost/packet-api-server/pkg/server" - "github.com/packethost/packet-api-server/pkg/store" - "github.com/packethost/packngo" + metaltest "github.com/equinix/cloud-provider-equinix-metal/metal/testing" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/google/uuid" clientset "k8s.io/client-go/kubernetes" k8sfake "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" cloudprovider "k8s.io/cloud-provider" + "k8s.io/component-base/version" ) const ( @@ -23,9 +23,6 @@ const ( validRegionCode = "ME" validRegionName = "Metro" validZoneCode = "ewr1" - validZoneName = "Parsippany, NJ" - validPlanSlug = "hourly" - validPlanName = "Bill by the hour" ) // mockControllerClientBuilder mock implementation of https://pkg.go.dev/k8s.io/cloud-provider#ControllerClientBuilder @@ -48,27 +45,12 @@ func (m mockControllerClientBuilder) ClientOrDie(name string) clientset.Interfac return k8sfake.NewSimpleClientset() } -type apiServerError struct { - t *testing.T -} - -func (a *apiServerError) Error(err error) { - a.t.Fatal(err) -} - // create a valid cloud with a client -func testGetValidCloud(t *testing.T, LoadBalancerSetting string) (*cloud, *store.Memory) { +func testGetValidCloud(t *testing.T, LoadBalancerSetting string) (*cloud, *metaltest.MockMetalServer) { + mockServer := metaltest.NewMockMetalServer(t) + // mock endpoint so our client can handle it - backend := store.NewMemory() - fake := emServer.PacketServer{ - Store: backend, - ErrorHandler: &apiServerError{ - t: t, - }, - } - // ensure we have a single region - _, _ = backend.CreateFacility(validZoneName, validZoneCode) - ts := httptest.NewServer(fake.CreateHandler()) + ts := httptest.NewServer(mockServer.CreateHandler()) url, _ := url.Parse(ts.URL) urlString := url.String() @@ -84,7 +66,7 @@ func testGetValidCloud(t *testing.T, LoadBalancerSetting string) (*cloud, *store ccb := &mockControllerClientBuilder{} c.Initialize(ccb, nil) - return c.(*cloud), backend + return c.(*cloud), mockServer } func TestLoadBalancerDefaultDisabled(t *testing.T) { @@ -211,14 +193,19 @@ func TestHasClusterID(t *testing.T) { } // builds an Equinix Metal client -func constructClient(authToken string, baseURL *string) *packngo.Client { - client := retryablehttp.NewClient() +func constructClient(authToken string, baseUrl *string) *metal.APIClient { + configuration := metal.NewConfiguration() + configuration.AddDefaultHeader("X-Auth-Token", authToken) + configuration.UserAgent = fmt.Sprintf("cloud-provider-equinix-metal/%s %s", version.Get(), configuration.UserAgent) - // client.Transport = logging.NewTransport("EquinixMetal", client.Transport) - if baseURL != nil { - // really should handle error, but packngo does not distinguish now or handle errors, so ignoring for now - client, _ := packngo.NewClientWithBaseURL(ConsumerToken, authToken, client.StandardClient(), *baseURL) - return client + if baseUrl != nil { + configuration.Servers = metal.ServerConfigurations{ + { + URL: *baseUrl, + Description: "No description provided", + }, + } } - return packngo.NewClientWithAuth(ConsumerToken, authToken, client.StandardClient()) + + return metal.NewAPIClient(configuration) } diff --git a/metal/common_test.go b/metal/common_test.go index c4a94332..8629dd60 100644 --- a/metal/common_test.go +++ b/metal/common_test.go @@ -4,52 +4,22 @@ import ( "fmt" "math/rand" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" "github.com/google/uuid" - "github.com/packethost/packet-api-server/pkg/store" - "github.com/packethost/packngo" - "github.com/packethost/packngo/metadata" randomdata "github.com/pallinder/go-randomdata" ) var randomID = uuid.New().String() -// find an ewr1 region or create it -func testGetOrCreateValidZone(name, code string, backend store.DataStore) (*packngo.Facility, error) { - facility, err := backend.GetFacilityByCode(code) - if err != nil { - return nil, err - } - // if we already have it, use it - if facility != nil { - return facility, nil - } - // we do not have it, so create it - return backend.CreateFacility(name, code) -} - -// find an ewr1 region or create it -func testGetOrCreateValidPlan(name, slug string, backend store.DataStore) (*packngo.Plan, error) { - plan, err := backend.GetPlanBySlug(slug) - if err != nil { - return nil, err - } - // if we already have it, use it - if plan != nil { - return plan, nil - } - // we do not have it, so create it - return backend.CreatePlan(slug, name) -} - // get a unique name func testGetNewDevName() string { return fmt.Sprintf("device-%d", rand.Intn(1000)) } -func testCreateAddress(ipv6, public bool) *packngo.IPAddressAssignment { - family := metadata.IPv4 +func testCreateAddress(ipv6, public bool) metal.IPAssignment { + family := int32(metal.IPADDRESSADDRESSFAMILY__4) if ipv6 { - family = metadata.IPv6 + family = int32(metal.IPADDRESSADDRESSFAMILY__6) } ipaddr := "" if ipv6 { @@ -57,12 +27,10 @@ func testCreateAddress(ipv6, public bool) *packngo.IPAddressAssignment { } else { ipaddr = randomdata.IpV4Address() } - address := &packngo.IPAddressAssignment{ - IpAddressCommon: packngo.IpAddressCommon{ - Address: ipaddr, - Public: public, - AddressFamily: int(family), - }, + address := metal.IPAssignment{ + Address: &ipaddr, + Public: &public, + AddressFamily: &family, } return address } diff --git a/metal/devices.go b/metal/devices.go index e83e6928..32ec659d 100644 --- a/metal/devices.go +++ b/metal/devices.go @@ -6,9 +6,7 @@ import ( "fmt" "strings" - "github.com/packethost/packngo" - "github.com/packethost/packngo/metadata" - + metal "github.com/equinix/equinix-sdk-go/services/metalv1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" cloudprovider "k8s.io/cloud-provider" @@ -17,13 +15,13 @@ import ( ) type instances struct { - client *packngo.Client + client *metal.DevicesApiService project string } var _ cloudprovider.InstancesV2 = (*instances)(nil) -func newInstances(client *packngo.Client, projectID string) *instances { +func newInstances(client *metal.DevicesApiService, projectID string) *instances { return &instances{client: client, project: projectID} } @@ -35,7 +33,7 @@ func (i *instances) InstanceShutdown(ctx context.Context, node *v1.Node) (bool, return false, err } - return device.State == "inactive", nil + return device.GetState() == metal.DEVICESTATE_INACTIVE, nil } // InstanceExists returns true if the node exists in cloudprovider @@ -74,7 +72,7 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud var p, r, z string if device.Plan != nil { - p = device.Plan.Slug + p = device.Plan.GetSlug() } // "A zone represents a logical failure domain" @@ -85,11 +83,8 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud // // https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesiozone - if device.Facility != nil { - z = device.Facility.Code - } if device.Metro != nil { - r = device.Metro.Code + r = device.Metro.GetCode() } return &cloudprovider.InstanceMetadata{ @@ -101,13 +96,13 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud }, nil } -func nodeAddresses(device *packngo.Device, providedNodeIP string) ([]v1.NodeAddress, error) { +func nodeAddresses(device *metal.Device, providedNodeIP string) ([]v1.NodeAddress, error) { var ( addresses []v1.NodeAddress unique = map[string]bool{} privateIP, publicIP string ) - addr := v1.NodeAddress{Type: v1.NodeHostName, Address: device.Hostname} + addr := v1.NodeAddress{Type: v1.NodeHostName, Address: device.GetHostname()} unique[addr.Address] = true addresses = append(addresses, addr) @@ -120,17 +115,17 @@ func nodeAddresses(device *packngo.Device, providedNodeIP string) ([]v1.NodeAddr addresses = append(addresses, addr) } } - for _, address := range device.Network { - if address.AddressFamily == int(metadata.IPv4) { + for _, address := range device.IpAddresses { + if address.GetAddressFamily() == int32(metal.IPADDRESSADDRESSFAMILY__4) { var addrType v1.NodeAddressType - if address.Public { - publicIP = address.Address + if address.GetPublic() { + publicIP = address.GetAddress() addrType = v1.NodeExternalIP } else { - privateIP = address.Address + privateIP = address.GetAddress() addrType = v1.NodeInternalIP } - addr = v1.NodeAddress{Type: addrType, Address: address.Address} + addr = v1.NodeAddress{Type: addrType, Address: address.GetAddress()} if _, ok := unique[addr.Address]; !ok { unique[addr.Address] = true @@ -150,7 +145,7 @@ func nodeAddresses(device *packngo.Device, providedNodeIP string) ([]v1.NodeAddr return addresses, nil } -func (i *instances) deviceByNode(node *v1.Node) (*packngo.Device, error) { +func (i *instances) deviceByNode(node *v1.Node) (*metal.Device, error) { if node.Spec.ProviderID != "" { return i.deviceFromProviderID(node.Spec.ProviderID) } @@ -158,30 +153,30 @@ func (i *instances) deviceByNode(node *v1.Node) (*packngo.Device, error) { return deviceByName(i.client, i.project, types.NodeName(node.GetName())) } -func deviceByID(client *packngo.Client, id string) (*packngo.Device, error) { +func deviceByID(client *metal.DevicesApiService, id string) (*metal.Device, error) { klog.V(2).Infof("called deviceByID with ID %s", id) - device, _, err := client.Devices.Get(id, nil) - if isNotFound(err) { + device, resp, err := client.FindDeviceById(context.Background(), id).Execute() + if isNotFound(resp, err) { return nil, cloudprovider.InstanceNotFound } return device, err } // deviceByName returns an instance whose hostname matches the kubernetes node.Name -func deviceByName(client *packngo.Client, projectID string, nodeName types.NodeName) (*packngo.Device, error) { +func deviceByName(client *metal.DevicesApiService, projectID string, nodeName types.NodeName) (*metal.Device, error) { klog.V(2).Infof("called deviceByName with projectID %s nodeName %s", projectID, nodeName) if string(nodeName) == "" { return nil, errors.New("node name cannot be empty string") } - devices, _, err := client.Devices.List(projectID, nil) + devices, _, err := client.FindProjectDevices(context.Background(), projectID).Execute() if err != nil { klog.V(2).Infof("error listing devices for project %s: %v", projectID, err) return nil, err } - for _, device := range devices { - if device.Hostname == string(nodeName) { - klog.V(2).Infof("Found device %s for nodeName %s", device.ID, nodeName) + for _, device := range devices.GetDevices() { + if device.GetHostname() == string(nodeName) { + klog.V(2).Infof("Found device %s for nodeName %s", device.GetId(), nodeName) return &device, nil } } @@ -218,7 +213,7 @@ func deviceIDFromProviderID(providerID string) (string, error) { } // deviceFromProviderID uses providerID to get the device id and return the device -func (i *instances) deviceFromProviderID(providerID string) (*packngo.Device, error) { +func (i *instances) deviceFromProviderID(providerID string) (*metal.Device, error) { klog.V(2).Infof("called deviceFromProviderID with providerID %s", providerID) id, err := deviceIDFromProviderID(providerID) if err != nil { @@ -229,6 +224,6 @@ func (i *instances) deviceFromProviderID(providerID string) (*packngo.Device, er } // providerIDFromDevice returns a providerID from a device -func providerIDFromDevice(device *packngo.Device) string { - return fmt.Sprintf("%s://%s", ProviderName, device.ID) +func providerIDFromDevice(device *metal.Device) string { + return fmt.Sprintf("%s://%s", ProviderName, device.GetId()) } diff --git a/metal/devices_test.go b/metal/devices_test.go index 879127c9..3a3ffd7e 100644 --- a/metal/devices_test.go +++ b/metal/devices_test.go @@ -6,15 +6,13 @@ import ( "strings" "testing" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" "github.com/google/uuid" - "github.com/packethost/packngo" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cpapi "k8s.io/cloud-provider/api" ) -var projectID = uuid.New().String() - // testNode provides a simple Node object satisfying the lookup requirements of InstanceMetadata() func testNodeWithIP(providerID, nodeName, nodeIP string) *v1.Node { node := testNode(providerID, nodeName) @@ -36,39 +34,46 @@ func testNode(providerID, nodeName string) *v1.Node { } func TestNodeAddresses(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() if inst == nil { t.Fatal("inst is nil") } devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } + server.DeviceStore[uid] = dev + project := server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, dev) + server.ProjectStore[vc.config.ProjectID] = project + // update the addresses on the device; normally created by Equinix Metal itself - networks := []*packngo.IPAddressAssignment{ + networks := []metal.IPAssignment{ testCreateAddress(false, false), // private ipv4 testCreateAddress(false, true), // public ipv4 testCreateAddress(true, true), // public ipv6 } kubeletNodeIP := testCreateAddress(false, false) - dev.Network = networks - err := backend.UpdateDevice(dev.ID, dev) - if err != nil { - t.Fatalf("unable to update inactive device: %v", err) - } + dev.IpAddresses = networks validAddresses := []v1.NodeAddress{ {Type: v1.NodeHostName, Address: devName}, - {Type: v1.NodeInternalIP, Address: networks[0].Address}, - {Type: v1.NodeExternalIP, Address: networks[1].Address}, + {Type: v1.NodeInternalIP, Address: networks[0].GetAddress()}, + {Type: v1.NodeExternalIP, Address: networks[1].GetAddress()}, } validAddressesWithProvidedIP := []v1.NodeAddress{ {Type: v1.NodeHostName, Address: devName}, - {Type: v1.NodeInternalIP, Address: kubeletNodeIP.Address}, - {Type: v1.NodeInternalIP, Address: networks[0].Address}, - {Type: v1.NodeExternalIP, Address: networks[1].Address}, + {Type: v1.NodeInternalIP, Address: kubeletNodeIP.GetAddress()}, + {Type: v1.NodeInternalIP, Address: networks[0].GetAddress()}, + {Type: v1.NodeExternalIP, Address: networks[1].GetAddress()}, } tests := []struct { @@ -79,12 +84,11 @@ func TestNodeAddresses(t *testing.T) { }{ {"empty node name", testNode("", ""), nil, fmt.Errorf("node name cannot be empty")}, {"instance not found", testNode("", nodeName), nil, fmt.Errorf("instance not found")}, - {"invalid id", testNode("equinixmetal://123", nodeName), nil, fmt.Errorf("123 is not a valid UUID")}, {"unknown name", testNode("equinixmetal://"+randomID, nodeName), nil, fmt.Errorf("instance not found")}, - {"valid both", testNode("equinixmetal://"+dev.ID, devName), validAddresses, nil}, - {"valid provider id", testNode("equinixmetal://"+dev.ID, nodeName), validAddresses, nil}, + {"valid both", testNode("equinixmetal://"+dev.GetId(), devName), validAddresses, nil}, + {"valid provider id", testNode("equinixmetal://"+dev.GetId(), nodeName), validAddresses, nil}, {"valid node name", testNode("", devName), validAddresses, nil}, - {"with node IP", testNodeWithIP("equinixmetal://"+dev.ID, nodeName, kubeletNodeIP.Address), validAddressesWithProvidedIP, nil}, + {"with node IP", testNodeWithIP("equinixmetal://"+dev.GetId(), nodeName, kubeletNodeIP.GetAddress()), validAddressesWithProvidedIP, nil}, } for i, tt := range tests { @@ -106,28 +110,35 @@ func TestNodeAddresses(t *testing.T) { } func TestNodeAddressesByProviderID(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } + + server.DeviceStore[uid] = dev + project := server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, dev) + server.ProjectStore[vc.config.ProjectID] = project + // update the addresses on the device; normally created by Equinix Metal itself - networks := []*packngo.IPAddressAssignment{ + networks := []metal.IPAssignment{ testCreateAddress(false, false), // private ipv4 testCreateAddress(false, true), // public ipv4 testCreateAddress(true, true), // public ipv6 } - dev.Network = networks - err := backend.UpdateDevice(dev.ID, dev) - if err != nil { - t.Fatalf("unable to update inactive device: %v", err) - } + dev.IpAddresses = networks validAddresses := []v1.NodeAddress{ {Type: v1.NodeHostName, Address: devName}, - {Type: v1.NodeInternalIP, Address: networks[0].Address}, - {Type: v1.NodeExternalIP, Address: networks[1].Address}, + {Type: v1.NodeInternalIP, Address: networks[0].GetAddress()}, + {Type: v1.NodeExternalIP, Address: networks[1].GetAddress()}, } tests := []struct { @@ -140,9 +151,9 @@ func TestNodeAddressesByProviderID(t *testing.T) { {"invalid format", randomID, nil, fmt.Errorf("instance not found")}, {"not equinixmetal", "aws://" + randomID, nil, fmt.Errorf("provider name from providerID should be equinixmetal")}, {"unknown ID", "equinixmetal://" + randomID, nil, fmt.Errorf("instance not found")}, - {"valid prefix", fmt.Sprintf("equinixmetal://%s", dev.ID), validAddresses, nil}, - {"valid legacy prefix", fmt.Sprintf("packet://%s", dev.ID), validAddresses, nil}, - {"valid without prefix", dev.ID, validAddresses, nil}, + {"valid prefix", fmt.Sprintf("equinixmetal://%s", dev.GetId()), validAddresses, nil}, + {"valid legacy prefix", fmt.Sprintf("packet://%s", dev.GetId()), validAddresses, nil}, + {"valid without prefix", dev.GetId(), validAddresses, nil}, } for i, tt := range tests { @@ -165,7 +176,7 @@ func TestNodeAddressesByProviderID(t *testing.T) { /* func TestInstanceID(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() devName := testGetNewDevName() facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) @@ -179,7 +190,7 @@ func TestNodeAddressesByProviderID(t *testing.T) { }{ {"", "", fmt.Errorf("node name cannot be empty")}, // empty name {"thisdoesnotexist", "", fmt.Errorf("instance not found")}, // unknown name - {devName, dev.ID, nil}, // valid + {devName, dev.GetId(), nil}, // valid } for i, tt := range tests { @@ -199,17 +210,38 @@ func TestNodeAddressesByProviderID(t *testing.T) { } */ func TestInstanceType(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } + server.DeviceStore[uid] = dev + project := server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, dev) + server.ProjectStore[vc.config.ProjectID] = project + privateIP := "10.1.1.2" publicIP := "25.50.75.100" - dev.Network = append(dev.Network, []*packngo.IPAddressAssignment{ - {IpAddressCommon: packngo.IpAddressCommon{Address: privateIP, Management: true, AddressFamily: 4}}, - {IpAddressCommon: packngo.IpAddressCommon{Address: publicIP, Public: true, AddressFamily: 4}}, + trueBool := true + ipv4 := int32(metal.IPADDRESSADDRESSFAMILY__4) + dev.IpAddresses = append(dev.IpAddresses, []metal.IPAssignment{ + { + Address: &privateIP, + Management: &trueBool, + AddressFamily: &ipv4, + }, + { + Address: &publicIP, + Public: &trueBool, + AddressFamily: &ipv4, + }, }...) tests := []struct { @@ -219,9 +251,8 @@ func TestInstanceType(t *testing.T) { err error }{ {"empty name", "", "", fmt.Errorf("instance not found")}, - {"invalid id", "thisdoesnotexist", "", fmt.Errorf("thisdoesnotexist is not a valid UUID")}, {"unknown name", randomID, "", fmt.Errorf("instance not found")}, - {"valid", "equinixmetal://" + dev.ID, dev.Plan.Slug, nil}, + {"valid", "equinixmetal://" + dev.GetId(), dev.Plan.GetSlug(), nil}, } for i, tt := range tests { @@ -242,48 +273,69 @@ func TestInstanceType(t *testing.T) { } func TestInstanceZone(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } + privateIP := "10.1.1.2" publicIP := "25.50.75.100" - metro := &packngo.Metro{ID: "123", Code: validRegionCode, Name: validRegionName, Country: "Country"} + + metroId := "123" + regionCode := validRegionCode + regionName := validRegionName + country := "Country" + metro := &metal.DeviceMetro{Id: &metroId, Code: ®ionCode, Name: ®ionName, Country: &country} dev.Metro = metro - facility.Metro = metro - dev.Network = append(dev.Network, []*packngo.IPAddressAssignment{ - {IpAddressCommon: packngo.IpAddressCommon{Address: privateIP, Management: true, AddressFamily: 4}}, - {IpAddressCommon: packngo.IpAddressCommon{Address: publicIP, Public: true, AddressFamily: 4}}, + + trueBool := true + ipv4 := int32(metal.IPADDRESSADDRESSFAMILY__4) + dev.IpAddresses = append(dev.IpAddresses, []metal.IPAssignment{ + { + Address: &privateIP, + Management: &trueBool, + AddressFamily: &ipv4, + }, + { + Address: &publicIP, + Public: &trueBool, + AddressFamily: &ipv4, + }, }...) + server.DeviceStore[uid] = dev + project := server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, dev) + server.ProjectStore[vc.config.ProjectID] = project + tests := []struct { testName string name string region string - zone string err error }{ - {"empty name", "", "", "", fmt.Errorf("instance not found")}, - {"invalid id", "thisdoesnotexist", "", "", fmt.Errorf("thisdoesnotexist is not a valid UUID")}, - {"unknown name", randomID, "", "", fmt.Errorf("instance not found")}, - {"valid", "equinixmetal://" + dev.ID, validRegionCode, validZoneCode, nil}, + {"empty name", "", "", fmt.Errorf("instance not found")}, + {"unknown name", randomID, "", fmt.Errorf("instance not found")}, + {"valid", "equinixmetal://" + dev.GetId(), validRegionCode, nil}, } for i, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - var zone, region string + var region string md, err := inst.InstanceMetadata(context.TODO(), testNode(tt.name, nodeName)) if md != nil { - zone = md.Zone region = md.Region } switch { case (err == nil && tt.err != nil) || (err != nil && tt.err == nil) || (err != nil && tt.err != nil && !strings.HasPrefix(err.Error(), tt.err.Error())): t.Errorf("%d: mismatched errors, actual %v expected %v", i, err, tt.err) - case zone != tt.zone: - t.Errorf("%d: mismatched zone, actual %v expected %v", i, zone, tt.zone) case region != tt.region: t.Errorf("%d: mismatched region, actual %v expected %v", i, region, tt.region) } @@ -293,12 +345,17 @@ func TestInstanceZone(t *testing.T) { /* func TestInstanceTypeByProviderID(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.Instances() devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } tests := []struct { id string @@ -309,8 +366,8 @@ func TestInstanceTypeByProviderID(t *testing.T) { {randomID, "", fmt.Errorf("instance not found")}, // invalid format {"aws://" + randomID, "", fmt.Errorf("provider name from providerID should be equinixmetal")}, // not equinixmetalk {"equinixmetal://" + randomID, "", fmt.Errorf("instance not found")}, // unknown ID - {fmt.Sprintf("equinixmetal://%s", dev.ID), dev.Plan.Name, nil}, // valid - {fmt.Sprintf("packet://%s", dev.ID), dev.Plan.Name, nil}, // valid + {fmt.Sprintf("equinixmetal://%s", dev.GetId()), dev.Plan.Name, nil}, // valid + {fmt.Sprintf("packet://%s", dev.GetId()), dev.Plan.Name, nil}, // valid } for i, tt := range tests { @@ -342,11 +399,16 @@ func TestCurrentNodeName(t *testing.T) { expectedName = types.NodeName(devName) ) - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } - md, err := inst.InstanceMetadata(context.TODO(), testNode("equinixmetal://"+dev.ID, nodeName)) + md, err := inst.InstanceMetadata(context.TODO(), testNode("equinixmetal://"+dev.GetId(), nodeName)) if err != expectedError { t.Errorf("mismatched errors, actual %v expected %v", err, expectedError) @@ -359,12 +421,22 @@ func TestCurrentNodeName(t *testing.T) { */ func TestInstanceExistsByProviderID(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - dev, _ := backend.CreateDevice(projectID, devName, plan, facility) + uid := uuid.New().String() + state := metal.DEVICESTATE_ACTIVE + dev := &metal.Device{ + Id: &uid, + Hostname: &devName, + State: &state, + Plan: metal.NewPlan(), + } + + server.DeviceStore[uid] = dev + project := server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, dev) + server.ProjectStore[vc.config.ProjectID] = project tests := []struct { id string @@ -375,9 +447,9 @@ func TestInstanceExistsByProviderID(t *testing.T) { {randomID, false, nil}, // invalid format {"aws://" + randomID, false, fmt.Errorf("provider name from providerID should be equinixmetal")}, // not equinixmetal {"equinixmetal://" + randomID, false, nil}, // unknown ID - {fmt.Sprintf("equinixmetal://%s", dev.ID), true, nil}, // valid - {fmt.Sprintf("packet://%s", dev.ID), true, nil}, // valid - {dev.ID, true, nil}, // valid + {fmt.Sprintf("equinixmetal://%s", dev.GetId()), true, nil}, // valid + {fmt.Sprintf("packet://%s", dev.GetId()), true, nil}, // valid + {dev.GetId(), true, nil}, // valid } for i, tt := range tests { @@ -392,18 +464,34 @@ func TestInstanceExistsByProviderID(t *testing.T) { } func TestInstanceShutdownByProviderID(t *testing.T) { - vc, backend := testGetValidCloud(t, "") + vc, server := testGetValidCloud(t, "") inst, _ := vc.InstancesV2() devName := testGetNewDevName() - facility, _ := testGetOrCreateValidZone(validZoneName, validZoneCode, backend) - plan, _ := testGetOrCreateValidPlan(validPlanName, validPlanSlug, backend) - devActive, _ := backend.CreateDevice(projectID, devName, plan, facility) - devInactive, _ := backend.CreateDevice(projectID, devName, plan, facility) - devInactive.State = "inactive" - err := backend.UpdateDevice(devInactive.ID, devInactive) - if err != nil { - t.Fatalf("unable to update inactive device: %v", err) + activeDevUid := uuid.New().String() + activeState := metal.DEVICESTATE_ACTIVE + devActive := &metal.Device{ + Id: &activeDevUid, + Hostname: &devName, + State: &activeState, + Plan: metal.NewPlan(), + } + server.DeviceStore[activeDevUid] = devActive + project := server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, devActive) + server.ProjectStore[vc.config.ProjectID] = project + + inactiveDevUid := uuid.New().String() + inactiveState := metal.DEVICESTATE_INACTIVE + devInactive := &metal.Device{ + Id: &inactiveDevUid, + Hostname: &devName, + State: &inactiveState, + Plan: metal.NewPlan(), } + server.DeviceStore[inactiveDevUid] = devInactive + project = server.ProjectStore[vc.config.ProjectID] + project.Devices = append(project.Devices, devInactive) + server.ProjectStore[vc.config.ProjectID] = project tests := []struct { id string @@ -414,12 +502,12 @@ func TestInstanceShutdownByProviderID(t *testing.T) { {randomID, false, fmt.Errorf("instance not found")}, // invalid format {"aws://" + randomID, false, fmt.Errorf("provider name from providerID should be equinixmetal")}, // not equinixmetal {"equinixmetal://" + randomID, false, fmt.Errorf("instance not found")}, // unknown ID - {fmt.Sprintf("equinixmetal://%s", devActive.ID), false, nil}, // valid - {fmt.Sprintf("packet://%s", devActive.ID), false, nil}, // valid - {devActive.ID, false, nil}, // valid - {fmt.Sprintf("equinixmetal://%s", devInactive.ID), true, nil}, // valid - {fmt.Sprintf("packet://%s", devInactive.ID), true, nil}, // valid - {devInactive.ID, true, nil}, // valid + {fmt.Sprintf("equinixmetal://%s", devActive.GetId()), false, nil}, // valid + {fmt.Sprintf("packet://%s", devActive.GetId()), false, nil}, // valid + {devActive.GetId(), false, nil}, // valid + {fmt.Sprintf("equinixmetal://%s", devInactive.GetId()), true, nil}, // valid + {fmt.Sprintf("packet://%s", devInactive.GetId()), true, nil}, // valid + {devInactive.GetId(), true, nil}, // valid } for i, tt := range tests { diff --git a/metal/eip_controlplane_reconciliation.go b/metal/eip_controlplane_reconciliation.go index e4576dc1..5d413315 100644 --- a/metal/eip_controlplane_reconciliation.go +++ b/metal/eip_controlplane_reconciliation.go @@ -9,7 +9,8 @@ import ( "sync" "time" - "github.com/packethost/packngo" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -52,8 +53,7 @@ type controlPlaneEndpointManager struct { apiServerPort int32 // node on which the EIP is listening nodeAPIServerPort int32 // port on which the api server is listening on the control plane nodes eipTag string - deviceIPSrv packngo.DeviceIPService - ipResSvr packngo.ProjectIPService + apiClient *metal.APIClient projectID string httpClient *http.Client k8sclient kubernetes.Interface @@ -64,7 +64,7 @@ type controlPlaneEndpointManager struct { useHostIP bool } -func newControlPlaneEndpointManager(k8sclient kubernetes.Interface, stop <-chan struct{}, eipTag, projectID string, deviceIPSrv packngo.DeviceIPService, ipResSvr packngo.ProjectIPService, apiServerPort int32, useHostIP bool) (*controlPlaneEndpointManager, error) { +func newControlPlaneEndpointManager(k8sclient kubernetes.Interface, stop <-chan struct{}, eipTag, projectID string, client *metal.APIClient, apiServerPort int32, useHostIP bool) (*controlPlaneEndpointManager, error) { klog.V(2).Info("newControlPlaneEndpointManager()") if eipTag == "" { @@ -81,8 +81,7 @@ func newControlPlaneEndpointManager(k8sclient kubernetes.Interface, stop <-chan }, eipTag: eipTag, projectID: projectID, - ipResSvr: ipResSvr, - deviceIPSrv: deviceIPSrv, + apiClient: client, apiServerPort: apiServerPort, k8sclient: k8sclient, useHostIP: useHostIP, @@ -228,7 +227,7 @@ func newControlPlaneEndpointManager(k8sclient kubernetes.Interface, stop <-chan return m, nil } -func (m *controlPlaneEndpointManager) reassign(ctx context.Context, nodes []*v1.Node, ip *packngo.IPAddressReservation, eipURL string) error { +func (m *controlPlaneEndpointManager) reassign(_ context.Context, nodes []*v1.Node, ip *metal.IPReservation, eipURL string) error { klog.V(2).Info("controlPlaneEndpoint.reassign") // must have figured out the node port first, or nothing to do if m.nodeAPIServerPort == 0 { @@ -268,13 +267,17 @@ func (m *controlPlaneEndpointManager) reassign(ctx context.Context, nodes []*v1. return err } if len(ip.Assignments) == 1 { - if _, err := m.deviceIPSrv.Unassign(ip.Assignments[0].ID); err != nil { + if _, err := m.apiClient.IPAddressesApi. + DeleteIPAddress(context.Background(), ip.Assignments[0].GetId()). + Execute(); err != nil { return err } } - if _, _, err := m.deviceIPSrv.Assign(deviceID, &packngo.AddressStruct{ - Address: ip.Address, - }); err != nil { + if _, _, err := m.apiClient.DevicesApi. + CreateIPAssignment(context.Background(), deviceID). + IPAssignmentInput(metal.IPAssignmentInput{ + Address: ip.GetAddress(), + }).Execute(); err != nil { return err } klog.Infof("control plane endpoint assigned to new device %s", node.Name) @@ -296,10 +299,8 @@ func isControlPlaneNode(node *v1.Node) bool { return false } -func (m *controlPlaneEndpointManager) getControlPlaneEndpointReservation() (*packngo.IPAddressReservation, error) { - ipList, _, err := m.ipResSvr.List(m.projectID, &packngo.ListOptions{ - Includes: []string{"assignments"}, - }) +func (m *controlPlaneEndpointManager) getControlPlaneEndpointReservation() (*metal.IPReservation, error) { + ipList, _, err := m.apiClient.IPAddressesApi.FindIPReservations(context.Background(), m.projectID).Include([]string{"assignments"}).Execute() if err != nil { return nil, err } @@ -311,16 +312,16 @@ func (m *controlPlaneEndpointManager) getControlPlaneEndpointReservation() (*pac } if len(controlPlaneEndpoint.Assignments) > 1 { - return nil, fmt.Errorf("the elastic ip %s has more than one node assigned to it and this is currently not supported. Fix it manually unassigning devices", controlPlaneEndpoint.ID) + return nil, fmt.Errorf("the elastic ip %s has more than one node assigned to it and this is currently not supported. Fix it manually unassigning devices", controlPlaneEndpoint.GetId()) } return controlPlaneEndpoint, nil } -func (m *controlPlaneEndpointManager) nodeIsAssigned(ctx context.Context, node *v1.Node, ipReservation *packngo.IPAddressReservation) (bool, error) { +func (m *controlPlaneEndpointManager) nodeIsAssigned(_ context.Context, node *v1.Node, ipReservation *metal.IPReservation) (bool, error) { for _, a := range ipReservation.Assignments { for _, na := range node.Status.Addresses { - if na.Address == a.Address { + if na.Address == a.GetAddress() { return true, nil } } @@ -402,7 +403,7 @@ func (m *controlPlaneEndpointManager) tryReassignAwayFromSelf(ctx context.Contex } // Anything calling this function should be wrapped by a lock on m.assignmentMutex -func (m *controlPlaneEndpointManager) tryReassign(ctx context.Context, controlPlaneEndpoint *packngo.IPAddressReservation, filters ...nodeFilter) error { +func (m *controlPlaneEndpointManager) tryReassign(ctx context.Context, controlPlaneEndpoint *metal.IPReservation, filters ...nodeFilter) error { controlPlaneHealthURL := m.healthURLFromControlPlaneEndpoint(controlPlaneEndpoint) nodeSet := newNodeSet() @@ -426,8 +427,8 @@ func (m *controlPlaneEndpointManager) tryReassign(ctx context.Context, controlPl return nil } -func (m *controlPlaneEndpointManager) healthURLFromControlPlaneEndpoint(controlPlaneEndpoint *packngo.IPAddressReservation) string { - return fmt.Sprintf("https://%s:%d/healthz", controlPlaneEndpoint.Address, m.apiServerPort) +func (m *controlPlaneEndpointManager) healthURLFromControlPlaneEndpoint(controlPlaneEndpoint *metal.IPReservation) string { + return fmt.Sprintf("https://%s:%d/healthz", controlPlaneEndpoint.GetAddress(), m.apiServerPort) } func (m *controlPlaneEndpointManager) syncEndpoints(ctx context.Context, k8sEndpoints *v1.Endpoints) error { @@ -473,7 +474,7 @@ func (m *controlPlaneEndpointManager) syncService(ctx context.Context, k8sServic } // for ease of use - eip := controlPlaneEndpoint.Address + eip := controlPlaneEndpoint.GetAddress() applyConfig := v1applyconfig.Service(externalServiceName, externalServiceNamespace). WithAnnotations(map[string]string{metallbAnnotation: metallbDisabledtag}). @@ -543,7 +544,7 @@ func (m *controlPlaneEndpointManager) doHealthCheck(ctx context.Context, node *v if m.useHostIP { for _, a := range node.Status.Addresses { // Find the non EIP external address for the node to use for the health check - if a.Type == v1.NodeExternalIP && a.Address != controlPlaneEndpoint.Address { + if a.Type == v1.NodeExternalIP && a.Address != controlPlaneEndpoint.GetAddress() { controlPlaneHealthURL = fmt.Sprintf("https://%s:%d/healthz", a.Address, m.nodeAPIServerPort) } } diff --git a/metal/errors.go b/metal/errors.go index 5b31ec1d..27283f76 100644 --- a/metal/errors.go +++ b/metal/errors.go @@ -1,19 +1,17 @@ package metal import ( - "github.com/packethost/packngo" + "net/http" + "strings" ) // isNotFound check if an error is a 404 not found -func isNotFound(err error) bool { +func isNotFound(resp *http.Response, err error) bool { if err == nil { return false } - if perr, ok := err.(*packngo.ErrorResponse); ok { - if perr.Response == nil { - return false - } - return perr.Response.StatusCode == 404 + if resp.StatusCode == http.StatusNotFound || strings.Contains(err.Error(), "Not Found") { + return true } return false } diff --git a/metal/ipreservations.go b/metal/ipreservations.go index 19698117..8b43e5e0 100644 --- a/metal/ipreservations.go +++ b/metal/ipreservations.go @@ -1,12 +1,12 @@ package metal import ( - "github.com/packethost/packngo" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" ) -// ipReservationByAllTags given a set of packngo.IPAddressReservation and a set of tags, find +// ipReservationByAllTags given a set of metal.IPReservation and a set of tags, find // the first reservation that has all of those tags -func ipReservationByAllTags(targetTags []string, ips []packngo.IPAddressReservation) *packngo.IPAddressReservation { +func ipReservationByAllTags(targetTags []string, ips *metal.IPReservationList) *metal.IPReservation { ret := ipReservationsByAllTags(targetTags, ips) if len(ret) > 0 { return ret[0] @@ -14,18 +14,18 @@ func ipReservationByAllTags(targetTags []string, ips []packngo.IPAddressReservat return nil } -// ipReservationsByAllTags given a set of packngo.IPAddressReservation and a set of tags, find +// ipReservationsByAllTags given a set of metal.IPReservation and a set of tags, find // all of the reservations that have all of those tags -func ipReservationsByAllTags(targetTags []string, ips []packngo.IPAddressReservation) []*packngo.IPAddressReservation { +func ipReservationsByAllTags(targetTags []string, ips *metal.IPReservationList) []*metal.IPReservation { // cycle through the IPs, looking for one that matches ours - ret := []*packngo.IPAddressReservation{} + ret := []*metal.IPReservation{} ips: - for i, ip := range ips { + for i, ip := range ips.GetIpAddresses() { tagMatches := map[string]bool{} for _, t := range targetTags { tagMatches[t] = false } - for _, tag := range ip.Tags { + for _, tag := range ip.IPReservation.Tags { if _, ok := tagMatches[tag]; ok { tagMatches[tag] = true } @@ -38,7 +38,7 @@ ips: } } // if we made it here, we matched - ret = append(ret, &ips[i]) + ret = append(ret, ips.GetIpAddresses()[i].IPReservation) } return ret } diff --git a/metal/ipreservations_test.go b/metal/ipreservations_test.go index 9cd25fa0..91b81412 100644 --- a/metal/ipreservations_test.go +++ b/metal/ipreservations_test.go @@ -3,16 +3,18 @@ package metal import ( "testing" - "github.com/packethost/packngo" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" ) func TestIPReservationByAllTags(t *testing.T) { - ips := []packngo.IPAddressReservation{ - {IpAddressCommon: packngo.IpAddressCommon{Tags: []string{"a", "b"}}}, - {IpAddressCommon: packngo.IpAddressCommon{Tags: []string{"c", "d"}}}, - {IpAddressCommon: packngo.IpAddressCommon{Tags: []string{"a", "d"}}}, - {IpAddressCommon: packngo.IpAddressCommon{Tags: []string{"b", "c"}}}, - {IpAddressCommon: packngo.IpAddressCommon{Tags: []string{"b", "q"}}}, + ips := &metal.IPReservationList{ + IpAddresses: []metal.IPReservationListIpAddressesInner{ + {IPReservation: &metal.IPReservation{Tags: []string{"a", "b"}}}, + {IPReservation: &metal.IPReservation{Tags: []string{"c", "d"}}}, + {IPReservation: &metal.IPReservation{Tags: []string{"a", "d"}}}, + {IPReservation: &metal.IPReservation{Tags: []string{"b", "c"}}}, + {IPReservation: &metal.IPReservation{Tags: []string{"b", "q"}}}, + }, } tests := []struct { tags []string @@ -36,7 +38,7 @@ func TestIPReservationByAllTags(t *testing.T) { t.Errorf("%d: found a match but expected none", i) case matched == nil && tt.match < 0: // this is good - case matched != &ips[tt.match]: + case matched != ips.GetIpAddresses()[tt.match].IPReservation: t.Errorf("%d: match did not find index %d", i, tt.match) } } diff --git a/metal/loadbalancers.go b/metal/loadbalancers.go index 5e635a09..ce7aa434 100644 --- a/metal/loadbalancers.go +++ b/metal/loadbalancers.go @@ -17,8 +17,8 @@ import ( "github.com/equinix/cloud-provider-equinix-metal/metal/loadbalancers/empty" "github.com/equinix/cloud-provider-equinix-metal/metal/loadbalancers/kubevip" "github.com/equinix/cloud-provider-equinix-metal/metal/loadbalancers/metallb" - "github.com/packethost/packngo" + metal "github.com/equinix/equinix-sdk-go/services/metalv1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -26,10 +26,11 @@ import ( "k8s.io/client-go/kubernetes" cloudprovider "k8s.io/cloud-provider" "k8s.io/klog/v2" + "k8s.io/utils/pointer" ) type loadBalancers struct { - client *packngo.Client + client *metal.APIClient k8sclient kubernetes.Interface project string metro string @@ -52,7 +53,7 @@ type loadBalancers struct { usesBGP bool } -func newLoadBalancers(client *packngo.Client, k8sclient kubernetes.Interface, projectID, metro, facility, config string, localASN int, bgpPass, annotationNetwork, annotationLocalASN, annotationPeerASN, annotationPeerIP, annotationSrcIP, annotationBgpPass, eipMetroAnnotation, eipFacilityAnnotation, nodeSelector, eipTag string) (*loadBalancers, error) { +func newLoadBalancers(client *metal.APIClient, k8sclient kubernetes.Interface, authToken, projectID, metro, facility, config string, localASN int, bgpPass, annotationNetwork, annotationLocalASN, annotationPeerASN, annotationPeerIP, annotationSrcIP, annotationBgpPass, eipMetroAnnotation, eipFacilityAnnotation, nodeSelector, eipTag string) (*loadBalancers, error) { selector := labels.Everything() if nodeSelector != "" { selector, _ = labels.Parse(nodeSelector) @@ -99,7 +100,7 @@ func newLoadBalancers(client *packngo.Client, k8sclient kubernetes.Interface, pr impl = empty.NewLB(k8sclient, lbconfig) case "emlb": klog.Info("loadbalancer implementation enabled: emlb") - impl = emlb.NewLB(k8sclient, lbconfig, client.APIKey, projectID) + impl = emlb.NewLB(k8sclient, lbconfig, authToken, projectID) // TODO remove when common BGP code has been refactored to somewhere else l.usesBGP = false default: @@ -128,7 +129,9 @@ func (l *loadBalancers) GetLoadBalancer(ctx context.Context, clusterName string, if l.usesBGP { // get IP address reservations and check if they any exists for this svc - ips, _, err := l.client.ProjectIPs.List(l.project, &packngo.ListOptions{}) + ips, _, err := l.client.IPAddressesApi. + FindIPReservations(context.Background(), l.project). + Execute() if err != nil { return nil, false, fmt.Errorf("unable to retrieve IP reservations for project %s: %w", l.project, err) } @@ -143,7 +146,7 @@ func (l *loadBalancers) GetLoadBalancer(ctx context.Context, clusterName string, } return &v1.LoadBalancerStatus{ Ingress: []v1.LoadBalancerIngress{ - {IP: ipReservation.Address}, + {IP: ipReservation.GetAddress()}, }, }, true, nil } else { @@ -228,7 +231,7 @@ func (l *loadBalancers) UpdateLoadBalancer(ctx context.Context, clusterName stri return fmt.Errorf("failed to annotate node %s: %w", node.Name, err) } var ( - peer *packngo.BGPNeighbor + peer *metal.BgpNeighborData err error ) if peer, err = getNodeBGPConfig(id, l.client); err != nil || peer == nil { @@ -236,11 +239,11 @@ func (l *loadBalancers) UpdateLoadBalancer(ctx context.Context, clusterName stri } n = append(n, loadbalancers.Node{ Name: node.Name, - LocalASN: peer.CustomerAs, - PeerASN: peer.PeerAs, - SourceIP: peer.CustomerIP, - Peers: peer.PeerIps, - Password: peer.Md5Password, + LocalASN: int(peer.GetCustomerAs()), + PeerASN: int(peer.GetPeerAs()), + SourceIP: peer.GetCustomerIp(), + Peers: peer.GetPeerIps(), + Password: peer.GetMd5Password(), }) } } @@ -267,7 +270,9 @@ func (l *loadBalancers) EnsureLoadBalancerDeleted(ctx context.Context, clusterNa if l.usesBGP { // get IP address reservations and check if they any exists for this svc - ips, _, err := l.client.ProjectIPs.List(l.project, &packngo.ListOptions{}) + ips, _, err := l.client.IPAddressesApi. + FindIPReservations(context.Background(), l.project). + Execute() if err != nil { return fmt.Errorf("unable to retrieve IP reservations for project %s: %w", l.project, err) } @@ -282,12 +287,12 @@ func (l *loadBalancers) EnsureLoadBalancerDeleted(ctx context.Context, clusterNa return nil } // delete the reservation - klog.V(2).Infof("EnsureLoadBalancerDeleted(): remove: for %s EIP ID %s", svcName, ipReservation.ID) - if _, err := l.client.ProjectIPs.Remove(ipReservation.ID); err != nil { - return fmt.Errorf("failed to remove IP address reservation %s from project: %w", ipReservation.String(), err) + klog.V(2).Infof("EnsureLoadBalancerDeleted(): remove: for %s EIP ID %s", svcName, ipReservation.GetId()) + if _, err := l.client.IPAddressesApi.DeleteIPAddress(context.Background(), ipReservation.GetId()).Execute(); err != nil { + return fmt.Errorf("failed to remove IP address reservation %s from project: %w", ipReservation.GetAddress(), err) } // remove it from any implementation-specific parts - svcIPCidr = fmt.Sprintf("%s/%d", ipReservation.Address, ipReservation.CIDR) + svcIPCidr = fmt.Sprintf("%s/%d", ipReservation.GetAddress(), ipReservation.GetCidr()) klog.V(2).Infof("EnsureLoadBalancerDeleted(): remove: for %s entry %s", svcName, svcIPCidr) } @@ -338,9 +343,9 @@ func (l *loadBalancers) annotateNode(ctx context.Context, node *v1.Node) error { klog.Errorf("got BGP info for node %s but it had no peer IPs", node.Name) default: // the localASN and peerASN are the same across peers - localASN := strconv.Itoa(peer.CustomerAs) - peerASN := strconv.Itoa(peer.PeerAs) - bgpPass := base64.StdEncoding.EncodeToString([]byte(peer.Md5Password)) + localASN := strconv.Itoa(int(peer.GetCustomerAs())) + peerASN := strconv.Itoa(int(peer.GetPeerAs())) + bgpPass := base64.StdEncoding.EncodeToString([]byte(peer.GetMd5Password())) // we always set the peer IPs as a sorted list, so that 0, 1, n are // consistent in ordering @@ -363,7 +368,7 @@ func (l *loadBalancers) annotateNode(ctx context.Context, node *v1.Node) error { annotations[annotationLocalASN] = localASN annotations[annotationPeerASN] = peerASN annotations[annotationPeerIP] = ip - annotations[annotationSrcIP] = peer.CustomerIP + annotations[annotationSrcIP] = peer.GetCustomerIp() annotations[annotationBgpPass] = bgpPass } } @@ -401,12 +406,12 @@ func (l *loadBalancers) addService(ctx context.Context, svc *v1.Service, nodes [ svcIPCidr string err error n []loadbalancers.Node - ips []packngo.IPAddressReservation + ips *metal.IPReservationList ) if l.usesBGP { // get IP address reservations and check if they any exists for this svc - ips, _, err = l.client.ProjectIPs.List(l.project, &packngo.ListOptions{}) + ips, _, err = l.client.IPAddressesApi.FindIPReservations(context.Background(), l.project).Execute() if err != nil { return "", fmt.Errorf("unable to retrieve IP reservations for project %s: %w", l.project, err) } @@ -431,34 +436,42 @@ func (l *loadBalancers) addService(ctx context.Context, svc *v1.Service, nodes [ // 5. Return error, cannot set an EIP facility := l.facility metro := l.metro - req := packngo.IPReservationRequest{ - Type: "public_ipv4", - Quantity: 1, - Description: ccmIPDescription, + input := &metal.IPReservationRequestInput{ + Type: "public_ipv4", + Quantity: 1, + Details: pointer.String(ccmIPDescription), Tags: []string{ emTag, svcTag, clsTag, }, - FailOnApprovalRequired: true, + FailOnApprovalRequired: pointer.Bool(true), + } + req := &metal.RequestIPReservationRequest{ + IPReservationRequestInput: input, } switch { case svcRegion != "": - req.Metro = &svcRegion + input.Metro = &svcRegion case svcZone != "": - req.Facility = &svcZone + input.Facility = &svcZone case metro != "": - req.Metro = &metro + input.Metro = &metro case facility != "": - req.Facility = &facility + input.Facility = &facility default: return "", errors.New("unable to create load balancer when no IP, region or zone specified, either globally or on service") } - ipReservation, _, err = l.client.ProjectIPs.Request(l.project, &req) - if err != nil { + resp, _, err := l.client.IPAddressesApi. + RequestIPReservation(context.Background(), l.project). + RequestIPReservationRequest(*req). + Execute() + if err != nil || resp == nil { return "", fmt.Errorf("failed to request an IP for the load balancer: %w", err) } + + ipReservation = resp.IPReservation } // if we have no IP from existing or a new reservation, log it and return @@ -469,7 +482,7 @@ func (l *loadBalancers) addService(ctx context.Context, svc *v1.Service, nodes [ // we have an IP, either found from existing reservations or a new reservation. // map and assign it - svcIP = ipReservation.Address + svcIP = ipReservation.GetAddress() // assign the IP and save it klog.V(2).Infof("assigning IP %s to %s", svcIP, svcName) @@ -489,9 +502,9 @@ func (l *loadBalancers) addService(ctx context.Context, svc *v1.Service, nodes [ klog.V(2).Infof("successfully assigned %s update service %s", svcIP, svcName) } // our default CIDR for each address is 32 - cidr := 32 + cidr := int32(32) if ipReservation != nil { - cidr = ipReservation.CIDR + cidr = ipReservation.GetCidr() } svcIPCidr = fmt.Sprintf("%s/%d", svcIP, cidr) // now need to pass it the nodes @@ -521,11 +534,11 @@ func (l *loadBalancers) addService(ctx context.Context, svc *v1.Service, nodes [ } n = append(n, loadbalancers.Node{ Name: node.Name, - LocalASN: peer.CustomerAs, - PeerASN: peer.PeerAs, - SourceIP: peer.CustomerIP, - Peers: peer.PeerIps, - Password: peer.Md5Password, + LocalASN: int(peer.GetCustomerAs()), + PeerASN: int(peer.GetPeerAs()), + SourceIP: peer.GetCustomerIp(), + Peers: peer.GetPeerIps(), + Password: peer.GetMd5Password(), }) } } @@ -539,7 +552,7 @@ func (l *loadBalancers) retrieveIPByTag(ctx context.Context, svc *v1.Service, ta cidr := 32 // get IP address reservations and check if they any exists for this svc - ips, _, err := l.client.ProjectIPs.List(l.project, &packngo.ListOptions{}) + ips, _, err := l.client.IPAddressesApi.FindIPReservations(context.Background(), l.project).Execute() if err != nil { return "", err } @@ -559,7 +572,7 @@ func (l *loadBalancers) retrieveIPByTag(ctx context.Context, svc *v1.Service, ta } // we have an IP, map and assign it - svcIP = ipReservation.Address + svcIP = ipReservation.GetAddress() // assign the IP and save it klog.V(2).Infof("assigning IP %s to %s", svcIP, svcName) @@ -579,7 +592,7 @@ func (l *loadBalancers) retrieveIPByTag(ctx context.Context, svc *v1.Service, ta klog.V(2).Infof("successfully assigned %s update service %s", svcIP, svcName) } if ipReservation != nil { - cidr = ipReservation.CIDR + cidr = int(ipReservation.GetCidr()) } svcIPCidr = fmt.Sprintf("%s/%d", svcIP, cidr) @@ -616,21 +629,24 @@ func clusterTag(clusterID string) string { } // getNodePrivateNetwork use the Equinix Metal API to get the CIDR of the private network given a providerID. -func getNodePrivateNetwork(deviceID string, client *packngo.Client) (string, error) { - device, _, err := client.Devices.Get(deviceID, &packngo.GetOptions{Includes: []string{"ip_addresses.parent_block,parent_block"}}) +func getNodePrivateNetwork(deviceID string, client *metal.APIClient) (string, error) { + device, _, err := client.DevicesApi. + FindDeviceById(context.Background(), deviceID). + Include([]string{"ip_addresses.parent_block,parent_block"}). + Execute() if err != nil { return "", err } - for _, net := range device.Network { + for _, net := range device.GetIpAddresses() { // we only want the private, management, ipv4 network - if net.Public || !net.Management || net.AddressFamily != 4 { + if net.GetPublic() || !net.GetManagement() || net.GetAddressFamily() != 4 { continue } parent := net.ParentBlock - if parent == nil || parent.Network == "" || parent.CIDR == 0 { - return "", fmt.Errorf("no network information provided for private address %s", net.String()) + if parent == nil || parent.GetNetwork() == "" || parent.GetCidr() == 0 { + return "", fmt.Errorf("no network information provided for private address %s", net.GetAddress()) } - return fmt.Sprintf("%s/%d", parent.Network, parent.CIDR), nil + return fmt.Sprintf("%s/%d", parent.GetNetwork(), parent.GetCidr()), nil } return "", nil } diff --git a/metal/testing/server.go b/metal/testing/server.go new file mode 100644 index 00000000..7346816b --- /dev/null +++ b/metal/testing/server.go @@ -0,0 +1,88 @@ +package testing + +import ( + "encoding/json" + "net/http" + "testing" + + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/gorilla/mux" +) + +type MockMetalServer struct { + DeviceStore map[string]*metal.Device + ProjectStore map[string]struct { + Devices []*metal.Device + BgpEnabled bool + } + + T *testing.T +} + +func NewMockMetalServer(t *testing.T) *MockMetalServer { + return &MockMetalServer{ + DeviceStore: map[string]*metal.Device{}, + ProjectStore: map[string]struct { + Devices []*metal.Device + BgpEnabled bool + }{}, + T: t, + } +} + +func (s *MockMetalServer) CreateHandler() http.Handler { + r := mux.NewRouter() + // create a BGP config for a project + r.HandleFunc("/projects/{projectID}/bgp-configs", s.createBGPHandler).Methods("POST") + // get all devices for a project + r.HandleFunc("/projects/{projectID}/devices", s.listDevicesHandler).Methods("GET") + // get a single device + r.HandleFunc("/devices/{deviceID}", s.getDeviceHandler).Methods("GET") + // handle metadata requests + return r +} + +func (s *MockMetalServer) listDevicesHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + projectID := vars["projectID"] + + data := s.ProjectStore[projectID] + devices := data.Devices + var resp = struct { + Devices []*metal.Device `json:"devices"` + }{ + Devices: devices, + } + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(&resp); err != nil { + s.T.Fatal(err.Error()) + } +} + +// get information about a specific device +func (s *MockMetalServer) getDeviceHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + volID := vars["deviceID"] + dev := s.DeviceStore[volID] + w.Header().Set("Content-Type", "application/json") + if dev != nil { + err := json.NewEncoder(w).Encode(dev) + if err != nil { + s.T.Fatal(err) + } + return + } + w.WriteHeader(http.StatusNotFound) +} + +// createBGPHandler enable BGP for a project +func (s *MockMetalServer) createBGPHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + projectID := vars["projectID"] + projectData := s.ProjectStore[projectID] + projectData.BgpEnabled = true + s.ProjectStore[projectID] = projectData + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) +}