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

Devnet wiz: one command for devnet + subnet flow #1205

Merged
merged 44 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6dde00b
add source code repo prompts to custom vm create
felipemadero Nov 9, 2023
33b5597
fix e2e
felipemadero Nov 9, 2023
90031f4
Merge branch 'main' into add-source-code-prompts-to-custom-vm-create
felipemadero Nov 9, 2023
8d34d6a
partial work
felipemadero Nov 9, 2023
29796f3
add skel
felipemadero Nov 14, 2023
dd481bb
add create node to wiz
felipemadero Nov 14, 2023
a341d08
trying to add deploy
felipemadero Nov 14, 2023
e931d03
create devnet + subnet deploy working
felipemadero Nov 15, 2023
f754643
add check for subnet status
felipemadero Nov 15, 2023
5f99236
add list of nodes to health check
felipemadero Nov 15, 2023
887d34d
deploy + sync + validate working
felipemadero Nov 15, 2023
a09e92e
nit
felipemadero Nov 15, 2023
08510ac
add create to the flow
felipemadero Nov 15, 2023
3118149
fix cli compilation point
felipemadero Nov 15, 2023
3bf91db
Merge branch 'main' into add-source-code-prompts-to-custom-vm-create
felipemadero Nov 15, 2023
f44af24
address PR comments
felipemadero Nov 15, 2023
1b5a3b2
Merge branch 'add-source-code-prompts-to-custom-vm-create' into devne…
felipemadero Nov 15, 2023
ce81555
added subnet create
felipemadero Nov 15, 2023
1ceacca
add subnet create + configure
felipemadero Nov 15, 2023
407cd8d
wip
arturrez Nov 15, 2023
4a68a42
wip
arturrez Nov 15, 2023
814231c
ready for testing
arturrez Nov 16, 2023
8204c7c
fix lint
arturrez Nov 16, 2023
360416b
fix typo
arturrez Nov 16, 2023
e5afddb
rm unused ansible files
arturrez Nov 16, 2023
858b417
fix timeouts
arturrez Nov 16, 2023
56dadfd
bug fixes and better error display
arturrez Nov 16, 2023
b27d363
make sure we are connected
arturrez Nov 17, 2023
b7cb19a
reuse connection if it's exists when host connects
arturrez Nov 17, 2023
6f0fcbf
lint
arturrez Nov 17, 2023
2eb98ba
fix script issue, cleanup
arturrez Nov 17, 2023
b726439
Merge branch 'main' into devnet-wiz
felipemadero Nov 17, 2023
96f6b95
GetErroHostMap -> GetErrorHostMap
arturrez Nov 17, 2023
45a139f
fix subnet-evm branch
felipemadero Nov 17, 2023
f311b25
Merge branch 'ansible_ssh_number_2' into devnet-wiz-ssh
felipemadero Nov 17, 2023
8131588
merge ssh pr into this one
felipemadero Nov 17, 2023
8528ada
fix branch
felipemadero Nov 17, 2023
a472e2a
fix
felipemadero Nov 17, 2023
d72c6d6
fix remaining issues
felipemadero Nov 17, 2023
7f7f213
Merge branch 'main' into devnet-wiz
felipemadero Nov 17, 2023
7331756
nits
felipemadero Nov 17, 2023
2084b42
Merge branch 'main' into devnet-wiz
felipemadero Nov 18, 2023
0a5ed4a
Merge branch 'main' into devnet-wiz
felipemadero Nov 22, 2023
34ccf23
nit
felipemadero Nov 22, 2023
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
7 changes: 7 additions & 0 deletions cmd/nodecmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ func createNodes(_ *cobra.Command, args []string) error {
if err != nil {
return err
}
if numNodes <= 0 {
var err error
numNodes, err = app.Prompt.CaptureInt("How many nodes do you want to set up?")
if err != nil {
return err
}
}
usr, err := user.Current()
if err != nil {
return err
Expand Down
7 changes: 0 additions & 7 deletions cmd/nodecmd/create_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,6 @@ func createEC2Instances(rootBody *hclwrite.Body,
if err := terraformaws.SetCloudCredentials(rootBody, awsProfile, region); err != nil {
return nil, nil, "", "", err
}
if numNodes <= 0 {
var err error
numNodes, err = app.Prompt.CaptureInt("How many nodes do you want to set up on AWS?")
if err != nil {
return nil, nil, "", "", err
}
}
ux.Logger.PrintToUser("Creating new EC2 instance(s) on AWS...")
var useExistingKeyPair bool
keyPairExists, err := awsAPI.CheckKeyPairExists(ec2Svc, keyPairName)
Expand Down
6 changes: 0 additions & 6 deletions cmd/nodecmd/create_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,6 @@ func createGCEInstances(rootBody *hclwrite.Body,
if err := terraformgcp.SetCloudCredentials(rootBody, zone, credentialsPath, projectName); err != nil {
return nil, nil, "", "", err
}
if numNodes <= 0 {
numNodes, err = app.Prompt.CaptureInt("How many nodes do you want to set up on GCP?")
if err != nil {
return nil, nil, "", "", err
}
}
ux.Logger.PrintToUser("Creating new VM instance(s) on Google Compute Engine...")
certInSSHDir, err := app.CheckCertInSSHDir(fmt.Sprintf("%s-keypair.pub", cliDefaultName))
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions cmd/nodecmd/devnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ You can check the updated status by calling avalanche node status <clusterName>`
}
// node devnet deploy
cmd.AddCommand(newDeployCmd())
// node devnet wiz
cmd.AddCommand(newWizCmd())
return cmd
}
13 changes: 13 additions & 0 deletions cmd/nodecmd/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,16 @@ func getClusterNodes(clusterName string) ([]string, error) {
}
return clusterNodes, nil
}

func clusterExists(clusterName string) (bool, error) {
clustersConfig := models.ClustersConfig{}
if app.ClustersConfigExists() {
var err error
clustersConfig, err = app.LoadClustersConfig()
if err != nil {
return false, err
}
}
_, ok := clustersConfig.Clusters[clusterName]
return ok, nil
}
276 changes: 276 additions & 0 deletions cmd/nodecmd/wiz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// Copyright (C) 2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nodecmd

import (
"fmt"
"strings"
"sync"
"time"

"github.com/ava-labs/avalanche-cli/cmd/subnetcmd"
"github.com/ava-labs/avalanche-cli/pkg/ansible"
"github.com/ava-labs/avalanche-cli/pkg/constants"
"github.com/ava-labs/avalanche-cli/pkg/models"
"github.com/ava-labs/avalanche-cli/pkg/ssh"
"github.com/ava-labs/avalanche-cli/pkg/ux"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/vms/platformvm/status"
"github.com/spf13/cobra"
)

const (
healthCheckPoolTime = 10 * time.Second
healthCheckTimeout = 1 * time.Minute
syncCheckPoolTime = 10 * time.Second
syncCheckTimeout = 1 * time.Minute
validateCheckPoolTime = 10 * time.Second
validateCheckTimeout = 1 * time.Minute
)

var (
forceSubnetCreate bool
subnetGenesisFile string
useEvmSubnet bool
useCustomSubnet bool
evmVersion string
useLatestEvmVersion bool
customVMRepoURL string
customVMBranch string
customVMBuildScript string
nodeConf string
subnetConf string
chainConf string
)

func newWizCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "wiz [clusterName] [subnetName]",
felipemadero marked this conversation as resolved.
Show resolved Hide resolved
Short: "(ALPHA Warning) Creates a devnet together with a fully validated subnet into it.",
Long: `(ALPHA Warning) This command is currently in experimental mode.

The node wiz command creates a devnet and deploys, sync and validate a subnet into it. It creates the subnet if so needed.
`,
SilenceUsage: true,
Args: cobra.ExactArgs(2),
RunE: wiz,
}
cmd.Flags().BoolVar(&useStaticIP, "use-static-ip", true, "attach static Public IP on cloud servers")
cmd.Flags().BoolVar(&useAWS, "aws", false, "create node/s in AWS cloud")
cmd.Flags().BoolVar(&useGCP, "gcp", false, "create node/s in GCP cloud")
cmd.Flags().StringVar(&cmdLineRegion, "region", "", "create node/s in given region")
cmd.Flags().BoolVar(&authorizeAccess, "authorize-access", false, "authorize CLI to create cloud resources")
cmd.Flags().IntVar(&numNodes, "num-nodes", 0, "number of nodes to create")
cmd.Flags().StringVar(&cmdLineGCPCredentialsPath, "gcp-credentials", "", "use given GCP credentials")
cmd.Flags().StringVar(&cmdLineGCPProjectName, "gcp-project", "", "use given GCP project")
cmd.Flags().StringVar(&cmdLineAlternativeKeyPairName, "alternative-key-pair-name", "", "key pair name to use if default one generates conflicts")
cmd.Flags().StringVar(&awsProfile, "aws-profile", constants.AWSDefaultCredential, "aws profile to use")
cmd.Flags().BoolVar(&defaultValidatorParams, "default-validator-params", false, "use default weight/start/duration params for subnet validator")

cmd.Flags().BoolVar(&forceSubnetCreate, "force-subnet-create", false, "overwrite the existing subnet configuration if one exists")
cmd.Flags().StringVar(&subnetGenesisFile, "subnet-genesis", "", "file path of the subnet genesis")
cmd.Flags().BoolVar(&useEvmSubnet, "evm-subnet", false, "use Subnet-EVM as the subnet virtual machine")
cmd.Flags().BoolVar(&useCustomSubnet, "custom-subnet", false, "use a custom VM as the subnet virtual machine")
cmd.Flags().StringVar(&evmVersion, "evm-version", "", "version of Subnet-Evm to use")
cmd.Flags().BoolVar(&useLatestEvmVersion, "latest-evm-version", false, "use latest Subnet-Evm version")
cmd.Flags().StringVar(&customVMRepoURL, "custom-vm-repo-url", "", "custom vm repository url")
cmd.Flags().StringVar(&customVMBranch, "custom-vm-branch", "", "custom vm branch")
cmd.Flags().StringVar(&customVMBuildScript, "custom-vm-build-script", "", "custom vm build-script")
cmd.Flags().StringVar(&nodeConf, "node-config", "", "path to avalanchego node configuration for subnet")
cmd.Flags().StringVar(&subnetConf, "subnet-config", "", "path to the subnet configuration for subnet")
cmd.Flags().StringVar(&chainConf, "chain-config", "", "path to the chain configuration for subnet")
return cmd
}

func wiz(cmd *cobra.Command, args []string) error {
clusterName := args[0]
subnetName := args[1]
exists, err := clusterExists(clusterName)
if err != nil {
return err
}
if exists {
return fmt.Errorf("cluster %s already exists", clusterName)
}
if !app.SidecarExists(subnetName) || forceSubnetCreate {
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Creating the subnet"))
ux.Logger.PrintToUser("")
if err := subnetcmd.CallCreate(
cmd,
subnetName,
forceSubnetCreate,
subnetGenesisFile,
useEvmSubnet,
useCustomSubnet,
evmVersion,
useLatestEvmVersion,
customVMRepoURL,
customVMBranch,
customVMBuildScript,
); err != nil {
return err
}
if chainConf != "" || subnetConf != "" || nodeConf != "" {
if err := subnetcmd.CallConfigure(
cmd,
subnetName,
chainConf,
subnetConf,
nodeConf,
); err != nil {
return err
}
}
}
createDevnet = true
useAvalanchegoVersionFromSubnet = subnetName
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Creating the devnet"))
ux.Logger.PrintToUser("")
err = createNodes(cmd, []string{clusterName})
if err != nil {
return err
}
if err := waitForHealthyCluster(clusterName, healthCheckTimeout, healthCheckPoolTime); err != nil {
return err
}
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Deploying the subnet"))
ux.Logger.PrintToUser("")
if err := deploySubnet(cmd, []string{clusterName, subnetName}); err != nil {
return err
}

ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Setting the nodes as subnet trackers"))
ux.Logger.PrintToUser("")
if err := syncSubnet(cmd, []string{clusterName, subnetName}); err != nil {
return err
}
if err := waitForHealthyCluster(clusterName, healthCheckTimeout, healthCheckPoolTime); err != nil {
return err
}
sc, err := app.LoadSidecar(subnetName)
if err != nil {
return err
}
blockchainID := sc.Networks[models.Devnet.String()].BlockchainID
if blockchainID == ids.Empty {
return ErrNoBlockchainID
}
if err := waitForClusterSubnetStatus(clusterName, subnetName, blockchainID, status.Syncing, syncCheckTimeout, syncCheckPoolTime); err != nil {
return err
}
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Adding nodes as subnet validators"))
ux.Logger.PrintToUser("")
if err := validateSubnet(cmd, []string{clusterName, subnetName}); err != nil {
return err
}
if err := waitForClusterSubnetStatus(clusterName, subnetName, blockchainID, status.Validating, validateCheckTimeout, validateCheckPoolTime); err != nil {
return err
}
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Devnet %s has been created and is validating subnet %s!"), clusterName, subnetName)
return nil
}

func waitForHealthyCluster(
clusterName string,
timeout time.Duration,
poolTime time.Duration,
) error {
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser("Waiting for node(s) in cluster %s to be healthy...", clusterName)
hosts, err := ansible.GetInventoryFromAnsibleInventoryFile(app.GetAnsibleInventoryDirPath(clusterName))
if err != nil {
return err
}
defer disconnectHosts(hosts)
startTime := time.Now()
for {
notHealthyNodes, err := checkHostsAreHealthy(hosts)
if err != nil {
return err
}
if len(notHealthyNodes) == 0 {
ux.Logger.PrintToUser("Nodes healthy after %d seconds", uint32(time.Since(startTime).Seconds()))
return nil
}
if time.Since(startTime) > timeout {
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser("Unhealthy Nodes")
for _, failedNode := range notHealthyNodes {
ux.Logger.PrintToUser(" " + failedNode)
}
ux.Logger.PrintToUser("")
return fmt.Errorf("cluster not healthy after %d seconds", uint32(timeout.Seconds()))
}
time.Sleep(poolTime)
}
}

func waitForClusterSubnetStatus(
clusterName string,
subnetName string,
blockchainID ids.ID,
targetStatus status.BlockchainStatus,
timeout time.Duration,
poolTime time.Duration,
) error {
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser("Waiting for node(s) in cluster %s to be %s subnet %s...", clusterName, strings.ToLower(targetStatus.String()), subnetName)
hosts, err := ansible.GetInventoryFromAnsibleInventoryFile(app.GetAnsibleInventoryDirPath(clusterName))
if err != nil {
return err
}
defer disconnectHosts(hosts)
startTime := time.Now()
for {
wg := sync.WaitGroup{}
wgResults := models.NodeResults{}
for _, host := range hosts {
wg.Add(1)
go func(nodeResults *models.NodeResults, host *models.Host) {
defer wg.Done()
if syncstatus, err := ssh.RunSSHSubnetSyncStatus(host, blockchainID.String()); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
return
} else {
if subnetSyncStatus, err := parseSubnetSyncOutput(syncstatus); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
return
} else {
nodeResults.AddResult(host.NodeID, subnetSyncStatus, err)
}
}
}(&wgResults, host)
}
wg.Wait()
if wgResults.HasErrors() {
return fmt.Errorf("failed to check sync status for node(s) %s", wgResults.GetErrorHostMap())
}
failedNodes := []string{}
for host, subnetSyncStatus := range wgResults.GetResultMap() {
if subnetSyncStatus != targetStatus.String() {
failedNodes = append(failedNodes, host)
}
}
if len(failedNodes) == 0 {
ux.Logger.PrintToUser("Nodes %s %s after %d seconds", targetStatus.String(), subnetName, uint32(time.Since(startTime).Seconds()))
return nil
}
if time.Since(startTime) > timeout {
ux.Logger.PrintToUser("Nodes not %s %s", targetStatus.String(), subnetName)
for _, failedNode := range failedNodes {
ux.Logger.PrintToUser(" " + failedNode)
}
ux.Logger.PrintToUser("")
return fmt.Errorf("cluster not %s subnet %s after %d seconds", strings.ToLower(targetStatus.String()), subnetName, uint32(timeout.Seconds()))
}
time.Sleep(poolTime)
}
}
13 changes: 13 additions & 0 deletions cmd/subnetcmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ configuration itself. This command allows you to set all those files.`,
return cmd
}

func CallConfigure(
cmd *cobra.Command,
subnetName string,
chainConfParam string,
subnetConfParam string,
nodeConfParam string,
) error {
chainConf = chainConfParam
subnetConf = subnetConfParam
nodeConf = nodeConfParam
return configure(cmd, []string{subnetName})
}

func configure(_ *cobra.Command, args []string) error {
chains, err := ValidateSubnetNameAndGetChains(args)
if err != nil {
Expand Down
Loading
Loading