From f91a04d746d4252cfdf10fcce7f24b06b04e82eb Mon Sep 17 00:00:00 2001 From: zyy17 Date: Thu, 26 Sep 2024 00:45:40 +0800 Subject: [PATCH] feat: add http apiserver for operator --- cmd/operator/app/command.go | 14 ++ cmd/operator/app/options/options.go | 6 + go.mod | 23 +- go.sum | 57 ++++- pkg/apiserver/apiserver.go | 329 ++++++++++++++++++++++++++++ pkg/apiserver/apiserver_test.go | 214 ++++++++++++++++++ 6 files changed, 635 insertions(+), 8 deletions(-) create mode 100644 pkg/apiserver/apiserver.go create mode 100644 pkg/apiserver/apiserver_test.go diff --git a/cmd/operator/app/command.go b/cmd/operator/app/command.go index 34a81081..866679e4 100644 --- a/cmd/operator/app/command.go +++ b/cmd/operator/app/command.go @@ -35,6 +35,7 @@ import ( "github.com/GreptimeTeam/greptimedb-operator/cmd/operator/app/version" "github.com/GreptimeTeam/greptimedb-operator/controllers/greptimedbcluster" "github.com/GreptimeTeam/greptimedb-operator/controllers/greptimedbstandalone" + "github.com/GreptimeTeam/greptimedb-operator/pkg/apiserver" ) const ( @@ -99,6 +100,19 @@ func NewOperatorCommand() *cobra.Command { os.Exit(1) } + if o.EnableAPIServer { + server := apiserver.NewServer(mgr.GetClient(), &apiserver.Options{ + Port: o.APIServerPort, + }) + + go func() { + if err := server.Run(); err != nil { + setupLog.Error(err, "unable to run HTTP service") + os.Exit(1) + } + }() + } + // +kubebuilder:scaffold:builder setupLog.Info("starting manager") diff --git a/cmd/operator/app/options/options.go b/cmd/operator/app/options/options.go index f206b312..b40e9021 100644 --- a/cmd/operator/app/options/options.go +++ b/cmd/operator/app/options/options.go @@ -27,12 +27,16 @@ type Options struct { MetricsAddr string HealthProbeAddr string EnableLeaderElection bool + EnableAPIServer bool + APIServerPort int } func NewDefaultOptions() *Options { return &Options{ MetricsAddr: defaultMetricsAddr, HealthProbeAddr: defaultHealthProbeAddr, + EnableAPIServer: false, + APIServerPort: 8081, } } @@ -40,4 +44,6 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.MetricsAddr, "metrics-bind-address", o.MetricsAddr, "The address the metric endpoint binds to.") fs.StringVar(&o.HealthProbeAddr, "health-probe-bind-address", o.HealthProbeAddr, "The address the probe endpoint binds to.") fs.BoolVar(&o.EnableLeaderElection, "enable-leader-election", o.EnableLeaderElection, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") + fs.BoolVar(&o.EnableAPIServer, "enable-apiserver", o.EnableAPIServer, "Enable API server for GreptimeDB operator.") + fs.IntVar(&o.APIServerPort, "apiserver-port", o.APIServerPort, "The port the API server binds to.") } diff --git a/go.mod b/go.mod index 34fbdee7..ea848595 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( dario.cat/mergo v1.0.1 + github.com/gin-gonic/gin v1.10.0 github.com/jackc/pgx/v5 v5.6.0 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 @@ -26,19 +27,29 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -53,25 +64,31 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect go.etcd.io/etcd/api/v3 v3.5.9 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect + golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/term v0.20.0 // indirect @@ -83,7 +100,7 @@ require ( google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5e94f400..f29d9a3e 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,19 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -27,6 +35,12 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -38,8 +52,18 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -82,6 +106,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -90,8 +118,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -107,6 +139,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -134,6 +168,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -141,8 +176,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.1/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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -162,6 +202,9 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -182,8 +225,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -201,6 +244,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -239,8 +284,8 @@ google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -272,6 +317,8 @@ k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5Ohx k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/controller-runtime v0.16.4 h1:XMh7dF19MlyvMfQCHvH929YGg2WFrIuJ4N5sx3G7U+k= sigs.k8s.io/controller-runtime v0.16.4/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go new file mode 100644 index 00000000..ac17ee5c --- /dev/null +++ b/pkg/apiserver/apiserver.go @@ -0,0 +1,329 @@ +// Copyright 2024 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + greptimev1alpha1 "github.com/GreptimeTeam/greptimedb-operator/apis/v1alpha1" + "github.com/GreptimeTeam/greptimedb-operator/controllers/common" + "github.com/GreptimeTeam/greptimedb-operator/controllers/constant" +) + +const ( + // APIGroup is the base path for the HTTP API. + APIGroup = "/api/v1alpha1" +) + +// Server provides an HTTP API to interact with Greptime CRD resources. +type Server struct { + client.Client + + port int +} + +// Options represents the options for the Server. +type Options struct { + // Port is the port that the HTTP service will listen on. + Port int +} + +// GreptimeDBCluster represents a GreptimeDBCluster resource that is returned by the API. +// This struct is used to serialize the GreptimeDBCluster resource into JSON. +type GreptimeDBCluster struct { + // Name is the name of the GreptimeDBCluster. + Name string `json:"name"` + + // Namespace is the namespace of the GreptimeDBCluster. + Namespace string `json:"namespace"` + + // Spec is the spec of the GreptimeDBCluster. + Spec *greptimev1alpha1.GreptimeDBClusterSpec `json:"spec,omitempty"` + + // Status is the status of the GreptimeDBCluster. + Status *greptimev1alpha1.GreptimeDBClusterStatus `json:"status,omitempty"` + + // Topology is the deployment topology of the GreptimeDBCluster. + Topology *GreptimeDBClusterTopology `json:"topology,omitempty"` +} + +// GreptimeDBClusterTopology represents the deployment topology of a GreptimeDBCluster. +type GreptimeDBClusterTopology struct { + // Meta represents the meta component of the GreptimeDBCluster. + Meta []*Pod `json:"meta,omitempty"` + + // Datanode represents the datanode component of the GreptimeDBCluster. + Datanode []*Pod `json:"datanode,omitempty"` + + // Frontend represents the frontend component of the GreptimeDBCluster. + Frontend []*Pod `json:"frontend,omitempty"` + + // Flownode represents the flownode component of the GreptimeDBCluster. + Flownode []*Pod `json:"flownode,omitempty"` +} + +// Pod is a simplified representation of a Kubernetes Pod. +type Pod struct { + // Name is the name of the Pod. + Name string `json:"name"` + + // Namespace is the namespace of the Pod. + Namespace string `json:"namespace"` + + // IP is the IP address of the Pod. + IP string `json:"ip"` + + // Node is the name of the node where the Pod is running. + Node string `json:"node"` + + // Resource is the resource requirements of the Pod. + Resource corev1.ResourceRequirements `json:"resource,omitempty"` + + // Status is the status of the Pod. + Status string `json:"status"` + + // StartTime is the time when the Pod started. + StartTime *metav1.Time `json:"startTime,omitempty"` +} + +// Response represents a response returned by the API. +type Response struct { + // Success indicates whether the request was successful. + Success bool `json:"success"` + + // Code is the status code that defined by the Server. + Code int `json:"code,omitempty"` + + // Message is additional message returned by the API. + Message string `json:"message,omitempty"` + + // Data is the data returned by the API. + Data interface{} `json:"data,omitempty"` +} + +// NewServer creates a new Server with the given client and options. +func NewServer(client client.Client, opts *Options) *Server { + return &Server{ + Client: client, + port: opts.Port, + } +} + +// Run starts the HTTP service and listens on the specified port. +func (s *Server) Run() error { + gin.SetMode(gin.ReleaseMode) + router := gin.Default() + + api := router.Group(APIGroup) + + // Get all clusters in a specific namespace. If the namespace is 'all', it will return all clusters in all namespaces. + api.GET("/namespaces/:namespace/clusters", s.getClusters) + + // Get a specific cluster in a specific namespace. + api.GET("/namespaces/:namespace/clusters/:name", s.getCluster) + + klog.Infof("HTTP service is running on port %d", s.port) + + return router.Run(fmt.Sprintf(":%d", s.port)) +} + +func (s *Server) getClusters(c *gin.Context) { + namespace := c.Param("namespace") + if namespace == "" { + c.JSON(http.StatusBadRequest, Response{ + Success: false, + Message: "namespace is required", + }) + return + } + + var listOptions []client.ListOption + if namespace != "all" { + listOptions = append(listOptions, client.InNamespace(namespace)) + } + + var clusters greptimev1alpha1.GreptimeDBClusterList + if err := s.List(c, &clusters, listOptions...); err != nil { + klog.Errorf("failed to list GreptimeDBCluster: %v", err) + c.JSON(http.StatusInternalServerError, Response{ + Success: false, + Message: "failed to get all clusters", + }) + return + } + + if len(clusters.Items) == 0 { + c.JSON(http.StatusNotFound, Response{ + Success: true, + Message: "no cluster found", + }) + return + } + + var data []GreptimeDBCluster + for _, cluster := range clusters.Items { + topology, err := s.getTopology(c, cluster.Namespace, cluster.Name) + if err != nil { + klog.Errorf("failed to get topology for cluster %s/%s: %v", cluster.Namespace, cluster.Name, err) + c.JSON(http.StatusInternalServerError, Response{ + Success: false, + Message: fmt.Sprintf("failed to get topology for cluster '%s' from namespace '%s'", cluster.Name, cluster.Namespace), + }) + return + } + + data = append(data, GreptimeDBCluster{ + Name: cluster.Name, + Namespace: cluster.Namespace, + Spec: &cluster.Spec, + Status: &cluster.Status, + Topology: topology, + }) + } + + c.JSON(http.StatusOK, Response{ + Success: true, + Data: data, + }) +} + +func (s *Server) getCluster(c *gin.Context) { + namespace := c.Param("namespace") + name := c.Param("name") + + if namespace == "" { + c.JSON(http.StatusBadRequest, Response{ + Success: false, + Message: "namespace is required", + }) + return + } + + if name == "" { + c.JSON(http.StatusBadRequest, Response{ + Success: false, + Message: "name is required", + }) + return + } + + var cluster greptimev1alpha1.GreptimeDBCluster + err := s.Get(c, client.ObjectKey{Namespace: namespace, Name: name}, &cluster) + + if errors.IsNotFound(err) { + c.JSON(http.StatusNotFound, Response{ + Success: true, + Message: fmt.Sprintf("cluster '%s' is not found in namespace '%s'", name, namespace), + }) + return + } + + if err != nil { + klog.Errorf("failed to get GreptimeDBCluster %s/%s: %v", namespace, name, err) + c.JSON(http.StatusInternalServerError, Response{ + Success: false, + Message: fmt.Sprintf("failed to get cluster '%s' from namespace '%s'", name, namespace), + }) + return + } + + topology, err := s.getTopology(c, namespace, name) + if err != nil { + klog.Errorf("failed to get topology for cluster %s/%s: %v", namespace, name, err) + c.JSON(http.StatusInternalServerError, Response{ + Success: false, + Message: fmt.Sprintf("failed to get topology for cluster '%s' from namespace '%s'", name, namespace), + }) + return + } + + c.JSON(http.StatusOK, Response{ + Success: true, + Data: GreptimeDBCluster{ + Name: cluster.Name, + Namespace: cluster.Namespace, + Spec: &cluster.Spec, + Status: &cluster.Status, + Topology: topology, + }, + }) +} + +func (s *Server) getTopology(ctx context.Context, namespace, name string) (*GreptimeDBClusterTopology, error) { + topology := new(GreptimeDBClusterTopology) + + for _, kind := range []greptimev1alpha1.ComponentKind{ + greptimev1alpha1.MetaComponentKind, + greptimev1alpha1.DatanodeComponentKind, + greptimev1alpha1.FrontendComponentKind, + greptimev1alpha1.FlownodeComponentKind, + } { + if err := s.getPods(ctx, namespace, name, kind, topology); err != nil { + return nil, err + } + } + + return topology, nil +} + +func (s *Server) getPods(ctx context.Context, namespace, name string, kind greptimev1alpha1.ComponentKind, topology *GreptimeDBClusterTopology) error { + var pods corev1.PodList + if err := s.List(ctx, &pods, client.InNamespace(namespace), + client.MatchingLabels{constant.GreptimeDBComponentName: common.ResourceName(name, kind)}); err != nil { + return err + } + + switch kind { + case greptimev1alpha1.MetaComponentKind: + for _, pod := range pods.Items { + topology.Meta = append(topology.Meta, s.covertToPod(&pod)) + } + case greptimev1alpha1.DatanodeComponentKind: + for _, pod := range pods.Items { + topology.Datanode = append(topology.Datanode, s.covertToPod(&pod)) + } + case greptimev1alpha1.FrontendComponentKind: + for _, pod := range pods.Items { + topology.Frontend = append(topology.Frontend, s.covertToPod(&pod)) + } + case greptimev1alpha1.FlownodeComponentKind: + for _, pod := range pods.Items { + topology.Flownode = append(topology.Flownode, s.covertToPod(&pod)) + } + } + + return nil +} + +func (s *Server) covertToPod(pod *corev1.Pod) *Pod { + return &Pod{ + Name: pod.Name, + Namespace: pod.Namespace, + IP: pod.Status.PodIP, + Status: string(pod.Status.Phase), + Node: pod.Spec.NodeName, + Resource: pod.Spec.Containers[constant.MainContainerIndex].Resources, + StartTime: pod.Status.StartTime, + } +} diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go new file mode 100644 index 00000000..b6864900 --- /dev/null +++ b/pkg/apiserver/apiserver_test.go @@ -0,0 +1,214 @@ +// Copyright 2024 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + + greptimev1alpha1 "github.com/GreptimeTeam/greptimedb-operator/apis/v1alpha1" +) + +const ( + TestPort = 18081 + TestNamespace = "default" + TestCluster = "testcluster" +) + +func TestHTTPGetService(t *testing.T) { + // Start the HTTP service. + svc := NewServer(&FakeClient{}, &Options{Port: TestPort}) + go func() { + if err := svc.Run(); err != nil { + t.Errorf("failed to start HTTP service: %v", err) + } + }() + + // Wait for the HTTP service to start. + time.Sleep(1 * time.Second) + + var ( + testAPI = fmt.Sprintf("http://localhost:%d%s", TestPort, APIGroup) + httpClient = &http.Client{} + ) + + tests := []struct { + url string + httpCode int + success bool + }{ + { + url: fmt.Sprintf("%s/namespaces/all/clusters", testAPI), + httpCode: http.StatusOK, + success: true, + }, + { + url: fmt.Sprintf("%s/namespaces/foo/clusters/bar", testAPI), + httpCode: http.StatusNotFound, + success: true, + }, + { + url: fmt.Sprintf("%s/namespaces/%s/clusters/%s", testAPI, TestNamespace, TestCluster), + httpCode: http.StatusOK, + success: true, + }, + { + url: fmt.Sprintf("%s/namespaces/default/clusters", testAPI), + httpCode: http.StatusOK, + success: true, + }, + { + url: fmt.Sprintf("%s/namespaces/foo/clusters/", testAPI), + httpCode: http.StatusNotFound, + success: true, + }, + } + + for i, tt := range tests { + req, err := http.NewRequest(http.MethodGet, tt.url, nil) + if err != nil { + t.Errorf("[%d] failed to create a GET request for url '%s': '%v'", i, tt.url, err) + } + + resp, err := httpClient.Do(req) + if err != nil { + t.Errorf("[%d] failed to send a GET request to url '%s': '%v'", i, tt.url, err) + } + resp.Body.Close() + + if resp.StatusCode != tt.httpCode { + t.Errorf("[%d] expected status code %d, got %d, url: '%s'", i, tt.httpCode, resp.StatusCode, tt.url) + } + } +} + +var _ client.Client = &FakeClient{} + +// FakeClient is a fake implementation of the client.Client interface. +type FakeClient struct{} + +func (f *FakeClient) Get(_ context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if key.String() == fmt.Sprintf("%s/%s", TestNamespace, TestCluster) { + cluster := obj.(*greptimev1alpha1.GreptimeDBCluster) + cluster.Name = TestCluster + cluster.Namespace = TestNamespace + _ = cluster.SetDefaults() + return nil + } else { + return errors.NewNotFound(schema.GroupResource{ + Group: "greptime.io", + Resource: "GreptimeDBCluster", + }, key.Name) + } +} + +func (f *FakeClient) SubResource(_ string) client.SubResourceClient { + return nil +} + +func (f *FakeClient) GroupVersionKindFor(_ runtime.Object) (schema.GroupVersionKind, error) { + return schema.GroupVersionKind{}, nil +} + +func (f *FakeClient) IsObjectNamespaced(_ runtime.Object) (bool, error) { + return true, nil +} + +func (f *FakeClient) List(_ context.Context, list client.ObjectList, opts ...client.ListOption) error { + listOpts := &client.ListOptions{} + for _, opt := range opts { + opt.ApplyToList(listOpts) + } + + if listOpts.Namespace != "" && listOpts.Namespace != TestNamespace { + return nil + } + + switch v := list.(type) { + case *greptimev1alpha1.GreptimeDBClusterList: + cluster := new(greptimev1alpha1.GreptimeDBCluster) + cluster.Name = TestCluster + cluster.Namespace = TestNamespace + _ = cluster.SetDefaults() + v.Items = append(v.Items, *cluster) + case *corev1.PodList: + pod := new(corev1.Pod) + pod.Name = "testpod" + pod.Namespace = TestNamespace + pod.Spec.Containers = []corev1.Container{ + { + Name: "testcontainer", + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + } + v.Items = append(v.Items, *pod) + default: + return errors.NewBadRequest("unknown list type") + } + + return nil +} + +func (f *FakeClient) Create(_ context.Context, _ client.Object, _ ...client.CreateOption) error { + return nil +} + +func (f *FakeClient) Delete(_ context.Context, _ client.Object, _ ...client.DeleteOption) error { + return nil +} + +func (f *FakeClient) Update(_ context.Context, _ client.Object, _ ...client.UpdateOption) error { + return nil +} + +func (f *FakeClient) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.PatchOption) error { + return nil +} + +func (f *FakeClient) DeleteAllOf(_ context.Context, _ client.Object, _ ...client.DeleteAllOfOption) error { + return nil +} + +func (f *FakeClient) Status() client.StatusWriter { + return nil +} + +func (f *FakeClient) Scheme() *runtime.Scheme { + return nil +} + +func (f *FakeClient) RESTMapper() meta.RESTMapper { + return nil +}