diff --git a/docs/modules/nebulagraph.md b/docs/modules/nebulagraph.md new file mode 100644 index 0000000000..99d6886d3a --- /dev/null +++ b/docs/modules/nebulagraph.md @@ -0,0 +1,111 @@ +# NebulaGraph + +Not available until the next release :material-tag: main + +## Introduction + +The Testcontainers module for [NebulaGraph](https://nebula-graph.io/), a distributed, scalable, and lightning-fast graph database. This module manages a complete NebulaGraph cluster including Meta Service, Storage Service, and Graph Service components. + +## Adding this module to your project dependencies + +Add the NebulaGraph module to your Go dependencies: + +```go +go get github.com/testcontainers/testcontainers-go/modules/nebulagraph +``` + +## Usage example + + +[Creating a NebulaGraph container](../../modules/nebulagraph/nebulagraph_test.go) inside_block:TestNebulaGraphContainer + + +## Module Reference + +### RunCluster function + +- Not available until the next release :material-tag: main + +The NebulaGraph module provides a function to create a complete NebulaGraph cluster within a Docker network: + +```golang +func RunCluster(ctx context.Context, + graphdImg string, graphdCustomizers []testcontainers.ContainerCustomizer, + storagedImg string, storagedCustomizers []testcontainers.ContainerCustomizer, + metadImg string, metadCustomizers []testcontainers.ContainerCustomizer, +) (*Cluster, error) +``` + +This function creates a complete NebulaGraph cluster with customizable settings. It returns a `Cluster` struct that contains references to all four components: +- Meta Service (metad) +- Storage Service (storaged) +- Graph Service (graphd) + +### Default Configuration + +The module uses the following default configurations: + +#### Default Images + - Graph Service: `vesoft/nebula-graphd:v3.8.0` + - Meta Service: `vesoft/nebula-metad:v3.8.0` + - Storage Service: `vesoft/nebula-storaged:v3.8.0` + +#### Exposed Ports + - Graph Service: 9669 (TCP), 19669 (HTTP) + - Meta Service: 9559 (TCP), 19559 (HTTP) + - Storage Service: 9779 (TCP), 19779 (HTTP) + +#### Health Checks + +The module implements health checks for all services: + +- Meta Service: HTTP health check on `/status` endpoint (port 19559) +- Graph Service: HTTP health check on `/status` endpoint (port 19669) +- Storage Service: Log-based health check for initialization +- Activator Service: Log-based health check and exit status for storage registration + +A cluster is considered ready when: + +1. Meta service is healthy and accessible +2. Graph service is healthy and accessible +3. Storage service is initialized and running +4. Storage service is successfully registered with the meta service via the activator + +### Container Options + +When starting the NebulaGraph container, you can pass options in a variadic way to configure it. + +The module supports customization for each service container (Meta, Storage, Graph, and Activator) through ContainerCustomizer options. Common customizations include: + +- Custom images for each service +- Environment variables +- Resource limits +- Network settings +- Volume mounts +- Wait strategies + +{% include "../features/common_functional_options_list.md" %} + +### Container Methods + +The `Cluster` struct provides the following methods: + +#### ConnectionString + +- Not available until the next release :material-tag: main + +Returns the host:port string for connecting to the NebulaGraph graph service (graphd). + +```golang +func (c *Cluster) ConnectionString(ctx context.Context) (string, error) +``` + +#### Terminate + +- Not available until the next release :material-tag: main + +Stops and removes all containers in the NebulaGraph cluster (Meta, Storage, Graph, and Activator services) and cleans up the associated Docker network. + +```golang +func (c *Cluster) Terminate(ctx context.Context) error +``` diff --git a/mkdocs.yml b/mkdocs.yml index 625acbf673..1d6f55baea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -104,6 +104,7 @@ nav: - modules/mssql.md - modules/mysql.md - modules/nats.md + - modules/nebulagraph.md - modules/neo4j.md - modules/ollama.md - modules/openfga.md diff --git a/modules/nebulagraph/Makefile b/modules/nebulagraph/Makefile new file mode 100644 index 0000000000..8185444293 --- /dev/null +++ b/modules/nebulagraph/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-nebulagraph diff --git a/modules/nebulagraph/activator.sh b/modules/nebulagraph/activator.sh new file mode 100644 index 0000000000..e4dd096315 --- /dev/null +++ b/modules/nebulagraph/activator.sh @@ -0,0 +1,24 @@ +for i in $(seq 1 ${ACTIVATOR_RETRY}); do + echo "nebula" | nebula-console -addr graphd0 -port 9669 -u root -e 'ADD HOSTS "storaged0":9779' 1>/dev/null 2>/dev/null + if [ $? -eq 0 ]; then + echo "✔️ Storage activated successfully." + exit 0 + else + output=$(echo "nebula" | nebula-console -addr graphd0 -port 9669 -u root -e 'ADD HOSTS "storaged0":9779' 2>&1) + if echo "$output" | grep -q "Existed"; then + echo "✔️ Storage activated already , Exiting..." + exit 0 + fi + fi + if [ $i -lt ${ACTIVATOR_RETRY} ]; then + echo "⏳ Attempting to activate storaged, attempt $i/${ACTIVATOR_RETRY}... It's normal to take some attempts before storaged is ready. Please wait." + else + echo "❌ Failed to activate storaged after ${ACTIVATOR_RETRY} attempts. Please check MetaD, StorageD logs." + echo "ℹ️ Error during storage activation:" + echo "==============================================================" + echo "$output" + echo "==============================================================" + exit 1 + fi + sleep 5 + done && tail -f /dev/null \ No newline at end of file diff --git a/modules/nebulagraph/go.mod b/modules/nebulagraph/go.mod new file mode 100644 index 0000000000..8ba2104ec2 --- /dev/null +++ b/modules/nebulagraph/go.mod @@ -0,0 +1,70 @@ +module github.com/testcontainers/testcontainers-go/modules/nebulagraph + +go 1.23.6 + +require ( + github.com/jolestar/go-commons-pool v2.0.0+incompatible + github.com/nebula-contrib/nebula-sirius v1.0.0-rc2 + github.com/stretchr/testify v1.10.0 + github.com/testcontainers/testcontainers-go v0.38.0 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/apache/thrift v0.21.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.2.2+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v4 v4.25.5 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/modules/nebulagraph/go.sum b/modules/nebulagraph/go.sum new file mode 100644 index 0000000000..77dd158160 --- /dev/null +++ b/modules/nebulagraph/go.sum @@ -0,0 +1,200 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/jolestar/go-commons-pool v2.0.0+incompatible h1:uHn5uRKsLLQSf9f1J5QPY2xREWx/YH+e4bIIXcAuAaE= +github.com/jolestar/go-commons-pool v2.0.0+incompatible/go.mod h1:ChJYIbIch0DMCSU6VU0t0xhPoWDR2mMFIQek3XWU0s8= +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/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/nebula-contrib/nebula-sirius v1.0.0-rc2 h1:fHoW6ELbJJOaNRGcYkjzGA9gJYmECGXAXjHMXNTrWQQ= +github.com/nebula-contrib/nebula-sirius v1.0.0-rc2/go.mod h1:ScJ/CkDeN5LhdvUfooNVB2/xBtr88tBxCHswW00m5Ew= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= +github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw= +github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +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= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8= +google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/modules/nebulagraph/nebulagraph.go b/modules/nebulagraph/nebulagraph.go new file mode 100644 index 0000000000..04e713406c --- /dev/null +++ b/modules/nebulagraph/nebulagraph.go @@ -0,0 +1,121 @@ +package nebulagraph + +import ( + "context" + "errors" + "fmt" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" +) + +// Cluster represents a running NebulaGraph cluster for testing +type Cluster struct { + graphd testcontainers.Container + metad testcontainers.Container + storaged testcontainers.Container + network *testcontainers.DockerNetwork +} + +// RunCluster starts a NebulaGraph cluster (metad, storaged, graphd and activator) containers within a Docker network +func RunCluster(ctx context.Context, + graphdImg string, graphdCustomizers []testcontainers.ContainerCustomizer, + storagedImg string, storagedCustomizers []testcontainers.ContainerCustomizer, + metadImg string, metadCustomizers []testcontainers.ContainerCustomizer, +) (*Cluster, error) { + // 1. Create a custom network + netRes, err := network.New(ctx) + if err != nil { + return nil, fmt.Errorf("new nebulagraph network: %w", err) + } + + // 2. Start metad + aggMetadCustomizers := append(defaultMetadContainerCustomizers(netRes), metadCustomizers...) + metad, err := testcontainers.Run(ctx, metadImg, aggMetadCustomizers...) + if err != nil { + errs := []error{fmt.Errorf("run metad container: %w", err)} + errs2 := terminateContainersAndRemoveNetwork(ctx, netRes) + errs = append(errs, errs2...) + return nil, errors.Join(errs...) + } + + // 3. Start graphd (needed for storage registration) + aggGraphdCustomizers := append(defaultGraphdContainerCustomizers(netRes), graphdCustomizers...) + graphd, err := testcontainers.Run(ctx, graphdImg, aggGraphdCustomizers...) + if err != nil { + errs := []error{fmt.Errorf("run graphd container: %w", err)} + errs2 := terminateContainersAndRemoveNetwork(ctx, netRes, metad) + errs = append(errs, errs2...) + return nil, errors.Join(errs...) + } + + // 4. Start storaged + aggStoragedCustomizers := append(defaultStoragedContainerCustomizers(netRes), storagedCustomizers...) + storaged, err := testcontainers.Run(ctx, storagedImg, aggStoragedCustomizers...) + if err != nil { + errs := []error{fmt.Errorf("run storaged container: %w", err)} + fmt.Println("error starting storaged: ", err) + errs2 := terminateContainersAndRemoveNetwork(ctx, netRes, graphd, metad) + errs = append(errs, errs2...) + return nil, errors.Join(errs...) + } + + // 5. Run storage registration command with retry logic + activator, err := testcontainers.Run(ctx, defaultNebulaConsoleImage, defaultActivatorContainerCustomizers(netRes)...) + if err != nil { + errs := []error{fmt.Errorf("run activator container: %w", err)} + errs2 := terminateContainersAndRemoveNetwork(ctx, netRes, storaged, graphd, metad) + errs = append(errs, errs2...) + return nil, errors.Join(errs...) + } + + activatorState, err := activator.State(ctx) + if err != nil { + errs := []error{fmt.Errorf("get activator container state: %w", err)} + errs2 := terminateContainersAndRemoveNetwork(ctx, netRes, storaged, graphd, metad, activator) + errs = append(errs, errs2...) + return nil, errors.Join(errs...) + } + + if !activatorState.Running && activatorState.ExitCode != 0 { + errs := []error{fmt.Errorf("activator container not running or exited with code %d", activatorState.ExitCode)} + errs2 := terminateContainersAndRemoveNetwork(ctx, netRes, storaged, graphd, metad) + errs = append(errs, errs2...) + return nil, errors.Join(errs...) + } + + return &Cluster{ + graphd: graphd, + metad: metad, + storaged: storaged, + network: netRes, + }, nil +} + +// ConnectionString returns the host:port for connecting to NebulaGraph graphd +func (c *Cluster) ConnectionString(ctx context.Context) (string, error) { + return c.graphd.PortEndpoint(ctx, graphdPort, "") +} + +// Terminate stops all NebulaGraph containers +func (c *Cluster) Terminate(ctx context.Context) error { + errs := terminateContainersAndRemoveNetwork(ctx, c.network, c.graphd, c.metad, c.storaged) + return errors.Join(errs...) +} + +func terminateContainersAndRemoveNetwork(ctx context.Context, netRes *testcontainers.DockerNetwork, containers ...testcontainers.Container) []error { + var errs []error + for _, ctr := range containers { + if ctr != nil { + if err := ctr.Terminate(ctx); err != nil { + errs = append(errs, fmt.Errorf("terminate container: %w", err)) + } + } + } + + if err := netRes.Remove(ctx); err != nil { + errs = append(errs, fmt.Errorf("network remove: %w", err)) + } + + return errs +} diff --git a/modules/nebulagraph/nebulagraph_test.go b/modules/nebulagraph/nebulagraph_test.go new file mode 100644 index 0000000000..89186a5acc --- /dev/null +++ b/modules/nebulagraph/nebulagraph_test.go @@ -0,0 +1,112 @@ +package nebulagraph_test + +import ( + "context" + "net" + "strconv" + "testing" + "time" + + pool "github.com/jolestar/go-commons-pool" + nebula_sirius "github.com/nebula-contrib/nebula-sirius" + "github.com/nebula-contrib/nebula-sirius/nebula" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/nebulagraph" +) + +const ( + defaultGraphdImage = "vesoft/nebula-graphd:v3.8.0" + defaultMetadImage = "vesoft/nebula-metad:v3.8.0" + defaultStoragedImage = "vesoft/nebula-storaged:v3.8.0" +) + +func TestNebulaGraphContainer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + + container, err := nebulagraph.RunCluster(ctx, + defaultGraphdImage, []testcontainers.ContainerCustomizer{}, + defaultStoragedImage, []testcontainers.ContainerCustomizer{}, + defaultMetadImage, []testcontainers.ContainerCustomizer{}, + ) + require.NoError(t, err) + t.Cleanup(func() { _ = container.Terminate(ctx) }) + + conn, err := container.ConnectionString(ctx) + require.NoError(t, err) + require.NotEmpty(t, conn) + + // Parse the connection string to get host and port + host, portt, err := net.SplitHostPort(conn) + require.NoError(t, err) + + portInt, err := strconv.Atoi(portt) + require.NoError(t, err) + + // Create client factory + clientFactory := nebula_sirius.NewNebulaClientFactory( + &nebula_sirius.NebulaClientConfig{ + HostAddress: nebula_sirius.HostAddress{ + Host: host, + Port: portInt, + }, + }, + nebula_sirius.DefaultLogger{}, + nebula_sirius.DefaultClientNameGenerator, + ) + + // Create client pool + nebulaClientPool := pool.NewObjectPool( + ctx, + clientFactory, + &pool.ObjectPoolConfig{ + MaxIdle: 5, + MaxTotal: 10, + }, + ) + + // Test client connection and basic queries + t.Run("basic-operations", func(t *testing.T) { + // Get a client from the pool + clientObj, err := nebulaClientPool.BorrowObject(ctx) + require.NoError(t, err) + defer func() { + err := nebulaClientPool.ReturnObject(ctx, clientObj) + require.NoError(t, err) + }() + + client := clientObj.(*nebula_sirius.WrappedNebulaClient) + require.NotNil(t, client) + + // Get graph client + g, err := client.GraphClient() + require.NoError(t, err) + + // Authenticate + auth, err := g.Authenticate(ctx, []byte("root"), []byte("nebula")) + require.NoError(t, err) + require.Equal(t, nebula.ErrorCode_SUCCEEDED, auth.GetErrorCode(), "Auth error: %s", auth.GetErrorMsg()) + + // Test YIELD query + result, err := g.Execute(ctx, *auth.SessionID, []byte("YIELD 1;")) + require.NoError(t, err) + require.Equal(t, nebula.ErrorCode_SUCCEEDED, result.GetErrorCode(), "Query error: %s", result.GetErrorMsg()) + + // Validate result contains our storage node + resultSet, err := nebula_sirius.GenResultSet(result) + require.NoError(t, err) + + // Convert result to string for validation + rows := resultSet.GetRows() + require.NotEmpty(t, rows, "Expected at least one row in YIELD output") + + row := rows[0] + require.NotNil(t, row, "Row should not be nil") + + vals := row.GetValues() + require.NotEmpty(t, vals, "Row values should not be empty") + require.Equal(t, int64(1), vals[0].GetIVal()) + }) +} diff --git a/modules/nebulagraph/options.go b/modules/nebulagraph/options.go new file mode 100644 index 0000000000..f04e3a8376 --- /dev/null +++ b/modules/nebulagraph/options.go @@ -0,0 +1,134 @@ +package nebulagraph + +import ( + _ "embed" + "fmt" + "net/http" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" +) + +//go:embed activator.sh +var activatorScript string + +const user = "root" + +const ( + defaultNebulaConsoleImage = "vesoft/nebula-console:v3.8.0" +) + +const ( + metadNetworkAlias = "metad0" + graphdNetworkAlias = "graphd0" + storagedNetworkAlias = "storaged0" +) + +const ( + graphdPort = "9669" + metadPort = "9559" + storagedPort = "9779" + + graphdPortHTTP = "19669" + metadPortHTTP = "19559" + storagedPortHTTP = "19779" +) + +func defaultGraphdContainerCustomizers(nw *testcontainers.DockerNetwork) []testcontainers.ContainerCustomizer { + customizers := []testcontainers.ContainerCustomizer{ + testcontainers.WithExposedPorts(graphdPort+"/tcp", graphdPortHTTP+"/tcp"), + testcontainers.WithCmdArgs([]string{ + "--meta_server_addrs=" + metadNetworkAlias + ":" + metadPort, + "--port=" + graphdPort, + "--local_ip=" + graphdNetworkAlias, + "--ws_ip=" + graphdNetworkAlias, + "--ws_http_port=" + graphdPortHTTP, + "--logtostderr=true", + "--redirect_stdout=false", + "--v=0", + "--minloglevel=0", + }...), + testcontainers.WithEnv(map[string]string{"USER": user}), + testcontainers.WithWaitStrategy(wait.ForHTTP("/status").WithPort(graphdPortHTTP + "/tcp"). + WithStatusCodeMatcher( + func(status int) bool { + return status == http.StatusOK + }, + )), + network.WithNetwork([]string{graphdNetworkAlias}, nw), + } + return customizers +} + +func defaultMetadContainerCustomizers(nw *testcontainers.DockerNetwork) []testcontainers.ContainerCustomizer { + customizers := []testcontainers.ContainerCustomizer{ + testcontainers.WithExposedPorts(metadPort+"/tcp", metadPortHTTP+"/tcp"), + testcontainers.WithCmdArgs([]string{ + "--meta_server_addrs=" + metadNetworkAlias + ":" + metadPort, + "--local_ip=" + metadNetworkAlias, + "--ws_ip=" + metadNetworkAlias, + "--port=" + metadPort, + "--ws_http_port=" + metadPortHTTP, + "--data_path=/data/meta", + "--logtostderr=true", + "--redirect_stdout=false", + "--v=0", + "--minloglevel=0", + }...), + testcontainers.WithEnv(map[string]string{"USER": user}), + testcontainers.WithWaitStrategy(wait.ForHTTP("/status").WithPort(metadPortHTTP + "/tcp"). + WithStatusCodeMatcher( + func(status int) bool { + return status == http.StatusOK + }, + )), + network.WithNetwork([]string{metadNetworkAlias}, nw), + } + return customizers +} + +func defaultStoragedContainerCustomizers(nw *testcontainers.DockerNetwork) []testcontainers.ContainerCustomizer { + customizers := []testcontainers.ContainerCustomizer{ + testcontainers.WithExposedPorts(storagedPort+"/tcp", storagedPortHTTP+"/tcp"), + testcontainers.WithCmdArgs([]string{ + "--meta_server_addrs=" + metadNetworkAlias + ":" + metadPort, + "--local_ip=" + storagedNetworkAlias, + "--ws_ip=" + storagedNetworkAlias, + "--port=" + storagedPort, + "--ws_http_port=" + storagedPortHTTP, + "--data_path=/data/storage", + "--logtostderr=true", + "--redirect_stdout=false", + "--v=0", + "--minloglevel=0", + }...), + testcontainers.WithEnv(map[string]string{"USER": user}), + testcontainers.WithWaitStrategy( + wait.ForLog(fmt.Sprintf(`localhost = "%s":%s`, storagedNetworkAlias, storagedPort)).WithStartupTimeout(30 * time.Second), + ), + network.WithNetwork([]string{storagedNetworkAlias}, nw), + } + return customizers +} + +func defaultActivatorContainerCustomizers(nw *testcontainers.DockerNetwork) []testcontainers.ContainerCustomizer { + customizers := []testcontainers.ContainerCustomizer{ + testcontainers.WithEntrypoint([]string{}...), + testcontainers.WithCmd([]string{ + "sh", "-c", + activatorScript, + }...), + testcontainers.WithExposedPorts(), + testcontainers.WithEnv(map[string]string{ + "USER": user, + "ACTIVATOR_RETRY": "30", + }), + testcontainers.WithWaitStrategy( + wait.ForLog(`✔️ Storage activated`).WithStartupTimeout(60 * time.Second), + ), + network.WithNetwork([]string{}, nw), + } + return customizers +}