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

implement grid-compose #1130

Open
wants to merge 22 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7503ba9
init grid-compose
Omarabdul3ziz Jun 25, 2024
84cbd10
add network support
Omarabdul3ziz Jun 25, 2024
7f0d28b
use yaml.v3 to fix the custom unmarshal
Omarabdul3ziz Jun 26, 2024
9e70865
feat: implement init logic for ps command
eyad-hussein Jul 24, 2024
7d3cff0
enhance: implement logic to parse env vars and disks for up command
eyad-hussein Aug 3, 2024
905394b
refactor: refactor ps command implementation
eyad-hussein Aug 3, 2024
40e67a2
refactor: refactor up and ps commands
eyad-hussein Aug 4, 2024
9fcedd2
feat: implement deployment of two or more services on same network
eyad-hussein Aug 4, 2024
09d55e2
exmple: add more example files to cover test cases
eyad-hussein Aug 6, 2024
3b095a7
refactor: refactor logic for all commands to comply with cases and co…
eyad-hussein Aug 13, 2024
9e227e1
docs: edit readme file
eyad-hussein Aug 13, 2024
b0f3b22
docs: add supported cases for deployment, update readme
eyad-hussein Aug 13, 2024
4bf7aaf
feat: add support for depends_on optoin(dependency resolution)
eyad-hussein Aug 17, 2024
352ef37
feat: add support for health check option
eyad-hussein Aug 18, 2024
de9f63a
docs: add future work file
eyad-hussein Aug 18, 2024
07c5da9
refactor: refactor project structure and code
eyad-hussein Aug 21, 2024
124fe56
fix: add check for app type assertion, refactor: remove deploy packag…
eyad-hussein Aug 22, 2024
bd2aa64
Merge pull request #1155 from threefoldtech/development_compose_init_…
Omarabdul3ziz Aug 22, 2024
8ddab7b
Merge branch 'development' of github.com:threefoldtech/tfgrid-sdk-go …
eyad-hussein Aug 22, 2024
deb6275
feat: add support for different storage units for service resources
eyad-hussein Aug 24, 2024
f21cf97
fix: fix storage parser
eyad-hussein Aug 26, 2024
bf550b0
fix: change project name so type becomes Micro Virtual Machine in das…
eyad-hussein Aug 31, 2024
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
3 changes: 2 additions & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
go 1.21
go 1.21.0

use (
./activation-service
Expand All @@ -12,4 +12,5 @@ use (
./tfrobot
./tools/relay-cache-warmer
./user-contracts-mon
./grid-compose
)
2 changes: 2 additions & 0 deletions grid-compose/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/*
.pre-commit-config.yaml
6 changes: 6 additions & 0 deletions grid-compose/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BIN_PATH = bin/main

.PHONY: build

build:
@go build -o $(BIN_PATH) ./main.go
22 changes: 22 additions & 0 deletions grid-compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Grid-Compose

is a tool for running multi-vm applications on TFGrid defined using a Yaml formatted file.

## Usage

`REQUIRED` EnvVars:

- `MNEMONIC`: your secret words
- `NETWORK`: one of (dev, qa, test, main)

```bash
grid-compose [OPTIONS] [COMMAND]

OPTIONS:
-f path to yaml file, default is ./grid-compose.yaml

COMMANDS:
- version: shows the project version
- up: deploy the app
- down: cancel all deployments
```
28 changes: 28 additions & 0 deletions grid-compose/cmd/down.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/internal"
)

var downCmd = &cobra.Command{
Use: "down",
Short: "cancel your project on the grid",
Run: func(cmd *cobra.Command, args []string) {
if err := down(); err != nil {
log.Fatal().Err(err).Send()
}
},
}

func down() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to a method on App struct in internal/app.go

for key := range app.Specs.Services {
projectName := internal.GetProjectName(key, app.Client.TwinID)
log.Info().Str("projectName", projectName).Msg("canceling deployments")
if err := app.Client.CancelByProjectName(projectName); err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap the error here was e.g fmt.Errorf("failed to cancel project %s: %w", projectName, err)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to to a method on App struct in app.go

}
}
return nil
}
123 changes: 123 additions & 0 deletions grid-compose/cmd/ps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/deployer"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/workloads"
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/internal"
"github.com/threefoldtech/tfgrid-sdk-go/grid-proxy/pkg/types"
)

var psCmd = &cobra.Command{
Use: "ps",
Short: "list containers",
Run: func(cmd *cobra.Command, args []string) {
if err := ps(cmd.Context()); err != nil {
log.Fatal().Err(err).Send()
}
},
}

func ps(ctx context.Context) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to a method on the app

twindId := uint64(app.Client.TwinID)
filters := types.ContractFilter{
TwinID: &twindId,
}
limits := types.Limit{
Size: 100,
}
cache := make(map[string]bool, 0)

contracts, _, err := app.Client.GridProxyClient.Contracts(ctx, filters, limits)

if err != nil {
return err
}

for _, contract := range contracts {
if contract.Type != "node" || contract.State == "Deleted" {
continue
}

details, err := workloads.ParseDeploymentData(contract.Details.(types.NodeContractDetails).DeploymentData)
if err != nil {
return err
}

if strings.Split(details.ProjectName, "/")[0] != "compose" || cache[details.ProjectName] {
continue
}

res, err := GetVM(ctx, app.Client, details.Name)
if err != nil {
return err
}

s, err := json.MarshalIndent(res, "", "\t")
if err != nil {
log.Fatal().Err(err).Send()
}

log.Info().Msg("vm:\n" + string(s))
cache[details.ProjectName] = true
}

return nil
}

// GetVM gets a vm with its project name
func GetVM(ctx context.Context, t deployer.TFPluginClient, name string) (workloads.Deployment, error) {
name, _ = strings.CutSuffix(name, "net")
projectName := internal.GetProjectName(name, app.Client.TwinID)

// try to get contracts with the old project name format "<name>"
contracts, err := t.ContractsGetter.ListContractsOfProjectName(projectName, true)
if err != nil {
return workloads.Deployment{}, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider returning a wrapper error with more context

}

if len(contracts.NodeContracts) == 0 {
// if could not find any contracts try to get contracts with the new project name format "vm/<name>"
projectName = fmt.Sprintf("vm/%s", name)
contracts, err = t.ContractsGetter.ListContractsOfProjectName(projectName, true)
if err != nil {
return workloads.Deployment{}, err
}

if len(contracts.NodeContracts) == 0 {
return workloads.Deployment{}, fmt.Errorf("couldn't find any contracts with name %s", name)
}
}

var nodeID uint32

for _, contract := range contracts.NodeContracts {
contractID, err := strconv.ParseUint(contract.ContractID, 10, 64)
if err != nil {
return workloads.Deployment{}, err
}

nodeID = contract.NodeID
checkIfExistAndAppend(t, nodeID, contractID)

}

return t.State.LoadDeploymentFromGrid(ctx, nodeID, name)
}

func checkIfExistAndAppend(t deployer.TFPluginClient, node uint32, contractID uint64) {
for _, n := range t.State.CurrentNodeDeployments[node] {
if n == contractID {
return
}
}

t.State.CurrentNodeDeployments[node] = append(t.State.CurrentNodeDeployments[node], contractID)
}
94 changes: 94 additions & 0 deletions grid-compose/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cmd

import (
"fmt"
"io"
"os"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/deployer"
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/pkg/types"
"gopkg.in/yaml.v3"
)

var (
app App
configFile string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configPath

network string
mnemonic string
)

var rootCmd = &cobra.Command{
Use: "grid-compose",
Short: "Grid-Compose is a tool for running multi-vm applications on TFGrid defined using a Yaml formatted file.",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
var err error
app, err = NewApp(network, mnemonic, configFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if app, err := ... ; err != nil {
log.Fatal ...
}

if err != nil {
log.Fatal().Err(err).Send()
}
},
}

func init() {
network = os.Getenv("NETWORK")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move getting the env to the Run in rootCmd

mnemonic = os.Getenv("MNEMONIC")
rootCmd.PersistentFlags().StringVarP(&configFile, "file", "f", "./grid-compose.yaml", "the grid-compose configuration file")

log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})

rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(upCmd)
rootCmd.AddCommand(psCmd)
rootCmd.AddCommand(downCmd)
}

type App struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the right place, should be in internal/app.go or similar

Client deployer.TFPluginClient
Specs types.Specs
}

func NewApp(net, mne, filePath string) (App, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factory function should be in internal/app.go and use better names eg mnemonic instead of mne

specs, err := LoadSpecsFromFile(filePath)
if err != nil {
return App{}, fmt.Errorf("failed to load specs from file: %w", err)
}

client, err := deployer.NewTFPluginClient(mne, deployer.WithNetwork(net))
if err != nil {
return App{}, fmt.Errorf("failed to load grid client: %w", err)
}

return App{
Specs: specs,
Client: client,
}, nil
}

func LoadSpecsFromFile(filePath string) (types.Specs, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a loadSpecsFromReader with io.Reader and don't bother about paths, opening/closing the files.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and of course, not the right place, internal/app.go

file, err := os.Open(filePath)
if err != nil {
return types.Specs{}, fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()

content, err := io.ReadAll(file)
if err != nil {
return types.Specs{}, fmt.Errorf("failed to read file: %w", err)
}

var specs types.Specs
if err := yaml.Unmarshal(content, &specs); err != nil {
return types.Specs{}, fmt.Errorf("failed to parse file: %w", err)
}

return specs, nil
}

func Execute() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to the beginning of the file

if err := rootCmd.Execute(); err != nil {
log.Fatal().Err(err).Send()
}
}
95 changes: 95 additions & 0 deletions grid-compose/cmd/up.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"context"
"fmt"
"net"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/workloads"
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/internal"
"github.com/threefoldtech/zos/pkg/gridtypes"
)

var upCmd = &cobra.Command{
Use: "up",
Short: "deploy application on the grid",
Run: func(cmd *cobra.Command, args []string) {
if err := up(cmd.Context()); err != nil {
log.Fatal().Err(err).Send()
}
},
}

func up(ctx context.Context) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move that into internal/app.go and make it a method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and same for the rest..

results := make(map[string]workloads.VM)
for key, val := range app.Specs.Services {
projectName := internal.GetProjectName(key, app.Client.TwinID)

networkName := key + "net"
log.Info().Str("projectName", projectName).Str("workloadName", networkName).Msg("deploying network")
net := workloads.ZNet{
Name: networkName,
Nodes: []uint32{uint32(val.NodeID)},
IPRange: gridtypes.NewIPNet(net.IPNet{
IP: net.IPv4(10, 20, 0, 0),
Mask: net.CIDRMask(16, 32),
}),
SolutionType: projectName,
}
if err := app.Client.NetworkDeployer.Deploy(ctx, &net); err != nil {
return err
}

log.Info().Str("projectName", projectName).Str("workloadName", key).Msg("deploying vm")
vm := workloads.VM{
Name: key,
Flist: val.Flist,
Entrypoint: val.Entrypoint,
EnvVars: val.Environment,
CPU: int(val.Resources.CPU),
Memory: int(val.Resources.Memory),
NetworkName: net.Name,
}

for _, net := range val.Networks {
switch net {
case "publicIp4":
vm.PublicIP = true
case "publicIp6":
vm.PublicIP6 = true
case "yggdrasil":
vm.Planetary = true
case "mycelium":
seed, err := internal.GetRandomMyceliumIPSeed()
if err != nil {
return fmt.Errorf("failed to get mycelium seed: %w", err)
}
vm.MyceliumIPSeed = seed
}
}

dl := workloads.NewDeployment(vm.Name, uint32(val.NodeID), projectName, nil, net.Name, nil, nil, []workloads.VM{vm}, nil)
if err := app.Client.DeploymentDeployer.Deploy(ctx, &dl); err != nil {
log.Error().Err(err).Msg("reverting deployed network")
if err := app.Client.NetworkDeployer.Cancel(ctx, &net); err != nil {
return err
}
return err
}

res, err := app.Client.State.LoadVMFromGrid(ctx, uint32(val.NodeID), vm.Name, dl.Name)
if err != nil {
return err
}

results[vm.Name] = res
}

for key, val := range results {
fmt.Printf("%s vm addresses:\n", key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why mixing logging with printing?

fmt.Println(internal.GetVmAddresses(val))
}
return nil
}
Loading
Loading