Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aeon: connect implementation #1065

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/prepare-ce-test-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,5 @@ runs:
- name: Install test requirements
run: |
sudo apt -y install gdb
pip3 install -r test/requirements.txt
shell: bash
1 change: 1 addition & 0 deletions .github/actions/prepare-ee-test-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ runs:
- name: Install test requirements
run: |
sudo apt -y install gdb
pip3 install -r test/requirements.txt
shell: bash
11 changes: 0 additions & 11 deletions .github/actions/static-code-check/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ description: "Performs static code checks."
runs:
using: "composite"
steps:
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install tests requirements
run: |
pip3 install -r test/requirements.txt
shell: bash

- name: Log versions
run: |
go version
Expand Down Expand Up @@ -42,4 +32,3 @@ runs:
- name: Python Linter
run: python3 -m flake8 test
shell: bash

2 changes: 1 addition & 1 deletion .github/workflows/full-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

env:
# Note: Use exactly match version of tool, to avoid unexpected issues with test on CI.
GO_VERSION: '1.21.13'
GO_VERSION: '1.22.10'
PYTHON_VERSION: '3.10'

jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

env:
# Note: Use exactly match version of tool, to avoid unexpected issues with test on CI.
GO_VERSION: '1.21.13'
GO_VERSION: '1.22.10'

jobs:
create-packages-linux:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

env:
# Note: Use exactly match version of tool, to avoid unexpected issues with test on CI.
GO_VERSION: '1.21.13'
GO_VERSION: '1.22.10'
PYTHON_VERSION: '3.10'

jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

env:
# Note: Use exactly match version of tool, to avoid unexpected issues with test on CI.
GO_VERSION: '1.21.13'
GO_VERSION: '1.22.10'

jobs:
update:
Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
path = cli/cartridge/third_party/cartridge-cli
url = https://github.com/tarantool/cartridge-cli.git
ignore = dirty
[submodule "cli/aeon/protos"]
path = cli/aeon/protos
url = git@github.com:tarantool/aeon-api-protos.git
branch = master
oleg-jukovec marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* `sslcertfile` - path to an SSL certificate file,
* `sslcafile` - path to a trusted certificate authorities (CA) file,
* `sslciphers` - colon-separated list of SSL cipher suites the connection.
- `tt aeon connect`: add support to connect Aeon database.

### Changed

Expand Down
30 changes: 30 additions & 0 deletions cli/aeon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Update Aaon proto submodule
In case of updating the communication protocol with the Aeon server, the following manual actions are required:
1. Update `proto` files and re-generate new `.go` sources.
2. Make appropriate corrections to the `tt aeon connect` application code.

## Requirements
Ensure that the required utilities are installed on the developer's system.
Installation of `protoc` compiler may depend on your OS distribution.
```sh
apt-get -y install protobuf-compiler
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```

## Update submodule from repository
Get fresh files from the `master` branch.
```sh
cd tt/cli/aeon/protos
git co master
git pull
cd ..
git add protos
```

## Regenerate `pb` modules
After that, you need to regenerate the files and add them to the `git` repository.
```sh
tt/cli/aeon/generate-pb.sh
git add tt/cli/aeon/pb
```
199 changes: 199 additions & 0 deletions cli/aeon/client.go
oleg-jukovec marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package aeon

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
"strings"
"time"

"github.com/apex/log"

"github.com/tarantool/go-prompt"
"github.com/tarantool/tt/cli/aeon/cmd"
"github.com/tarantool/tt/cli/aeon/pb"
"github.com/tarantool/tt/cli/connector"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)

// Client structure with parameters for gRPC connection to Aeon.
type Client struct {
title string
conn *grpc.ClientConn
client pb.SQLServiceClient
}

func makeAddress(ctx cmd.ConnectCtx) string {
if ctx.Network == connector.UnixNetwork {
if strings.HasPrefix(ctx.Address, "@") {
return "unix-abstract:" + (ctx.Address)[1:]
}
return "unix:" + ctx.Address
}
return ctx.Address
}

func getCertificate(args cmd.Ssl) (tls.Certificate, error) {
if args.CertFile == "" && args.KeyFile == "" {
return tls.Certificate{}, nil
}
tls_cert, err := tls.LoadX509KeyPair(args.CertFile, args.KeyFile)
if err != nil {
return tls_cert, fmt.Errorf("could not load client key pair: %w", err)
}
return tls_cert, nil
}

func getTlsConfig(args cmd.Ssl) (*tls.Config, error) {
if args.CaFile == "" {
return &tls.Config{
ClientAuth: tls.NoClientCert,
}, nil
}

ca, err := os.ReadFile(args.CaFile)
if err != nil {
return nil, fmt.Errorf("failed to read CA file: %w", err)
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(ca) {
return nil, errors.New("failed to append CA data")
}
cert, err := getCertificate(args)
if err != nil {
return nil, fmt.Errorf("failed get certificate: %w", err)
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
RootCAs: certPool,
}, nil
}

func getDialOpts(ctx cmd.ConnectCtx) (grpc.DialOption, error) {
var creds credentials.TransportCredentials
if ctx.Transport == cmd.TransportSsl {
config, err := getTlsConfig(ctx.Ssl)
if err != nil {
return nil, fmt.Errorf("not tls config: %w", err)
}
creds = credentials.NewTLS(config)
} else {
creds = insecure.NewCredentials()
}
return grpc.WithTransportCredentials(creds), nil
}

// NewAeonHandler create new grpc connection to Aeon server.
func NewAeonHandler(ctx cmd.ConnectCtx) (*Client, error) {
c := Client{title: ctx.Address}
target := makeAddress(ctx)
// var err error
opt, err := getDialOpts(ctx)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
c.conn, err = grpc.NewClient(target, opt)
if err != nil {
return nil, fmt.Errorf("fail to dial: %w", err)
}
if err := c.ping(); err == nil {
log.Infof("Aeon responses at %q", target)
} else {
return nil, fmt.Errorf("can't ping to Aeon at %q: %w", target, err)
}

c.client = pb.NewSQLServiceClient(c.conn)
return &c, nil
}

func (c *Client) ping() error {
log.Infof("Start ping aeon server")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

diag := pb.NewDiagServiceClient(c.conn)
_, err := diag.Ping(ctx, &pb.PingRequest{})
if err != nil {
log.Warnf("Aeon ping %s", err)
}
return err
}

// Title implements console.Handler interface.
func (c *Client) Title() string {
return c.title
}

// Validate implements console.Handler interface.
func (c *Client) Validate(input string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

check, err := c.client.SQLCheck(ctx, &pb.SQLRequest{Query: input})
if err != nil {
log.Warnf("Aeon validate %s\nFor request: %q", err, input)
return false
}

return check.Status == pb.SQLCheckStatus_SQL_QUERY_VALID
}

// Execute implements console.Handler interface.
func (c *Client) Execute(input string) any {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

resp, err := c.client.SQL(ctx, &pb.SQLRequest{Query: input})
if err != nil {
return err
}
return parseSQLResponse(resp)
}

// Stop implements console.Handler interface.
func (c *Client) Close() {
c.conn.Close()
}

// Complete implements console.Handler interface.
func (c *Client) Complete(input prompt.Document) []prompt.Suggest {
// TODO: waiting until there is support from Aeon side.
oleg-jukovec marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

// parseSQLResponse returns result as table in map.
// Where keys is name of columns. And body is array of values.
// On any issue return an error.
func parseSQLResponse(resp *pb.SQLResponse) any {
if resp.Error != nil {
return resultError{resp.Error}
}
if resp.TupleFormat == nil {
return resultType{}
}
res := resultType{
names: make([]string, len(resp.TupleFormat.Names)),
rows: make([]resultRow, len(resp.Tuples)),
}
for i, n := range resp.TupleFormat.Names {
res.names[i] = n
res.rows[i] = make([]any, 0, len(resp.TupleFormat.Names))
}

for r, row := range resp.Tuples {
for _, v := range row.Fields {
val, err := decodeValue(v)
if err != nil {
return fmt.Errorf("tuple %d can't decode %s: %w", r, v.String(), err)
}
res.rows[r] = append(res.rows[r], val)
}
}
return res
}
4 changes: 4 additions & 0 deletions cli/aeon/cmd/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ type ConnectCtx struct {
Ssl Ssl
// Transport is a connection mode.
Transport Transport
// Network is kind of transport layer.
Network string
// Address is a connection URL, unix socket address and etc.
Address string
}
Loading