From 5d38f1737433a7927927ab953aaf449153bd63a4 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 3 Mar 2017 16:18:27 +0100 Subject: [PATCH 01/38] refactor(g5k): Moving all Grid5000 related code to libdockerg5k --- command/commands.go | 6 +-- command/create_cluster.go | 74 +++++----------------------------- libdockerg5k/g5k/deployment.go | 51 +++++++++++++++++++++++ libdockerg5k/g5k/g5k.go | 36 +++++++++++++++++ libdockerg5k/g5k/job.go | 46 +++++++++++++++++++++ 5 files changed, 145 insertions(+), 68 deletions(-) create mode 100644 libdockerg5k/g5k/deployment.go create mode 100644 libdockerg5k/g5k/g5k.go create mode 100644 libdockerg5k/g5k/job.go diff --git a/command/commands.go b/command/commands.go index a1afd16..fce500d 100644 --- a/command/commands.go +++ b/command/commands.go @@ -1,14 +1,14 @@ package command import ( - "github.com/Spirals-Team/docker-machine-driver-g5k/api" + "github.com/Spirals-Team/docker-g5k/libdockerg5k/g5k" "github.com/codegangsta/cli" ) // Command struct contain common informations used between commands type Command struct { - cli *cli.Context - api *api.Api + cli *cli.Context + g5kAPI *g5k.G5K g5kJobID int g5kDeploymentID string diff --git a/command/create_cluster.go b/command/create_cluster.go index 3bff23a..49771bc 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -3,7 +3,6 @@ package command import ( "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "sync" @@ -14,68 +13,12 @@ import ( "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/swarm" - "github.com/Spirals-Team/docker-machine-driver-g5k/api" "github.com/Spirals-Team/docker-machine-driver-g5k/driver" g5kswarm "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" "github.com/Spirals-Team/docker-g5k/libdockerg5k/weave" ) -// AllocateNodes allocate a new job with multiple nodes -func (c *Command) AllocateNodes() error { - // convert walltime to seconds - seconds, err := api.ConvertDuration(c.cli.String("g5k-walltime")) - if err != nil { - return err - } - - // create a new job request - jobReq := api.JobRequest{ - Resources: fmt.Sprintf("nodes=%v,walltime=%s", c.cli.Int("g5k-nb-nodes"), c.cli.String("g5k-walltime")), - Command: fmt.Sprintf("sleep %v", seconds), - Properties: c.cli.String("g5k-resource-properties"), - Types: []string{"deploy"}, - } - - // submit job request - c.g5kJobID, err = c.api.SubmitJob(jobReq) - if err != nil { - return err - } - - return nil -} - -// DeployNodes submit a deployment request -func (c *Command) DeployNodes() error { - // reading ssh public key file - pubkey, err := ioutil.ReadFile(c.cli.String("g5k-ssh-public-key")) - if err != nil { - return err - } - - // get job informations - job, err := c.api.GetJob(c.g5kJobID) - if err != nil { - return err - } - - // creating a new deployment request - deploymentReq := api.DeploymentRequest{ - Nodes: job.Nodes, - Environment: c.cli.String("g5k-image"), - Key: string(pubkey), - } - - // deploy environment - c.g5kDeploymentID, err = c.api.SubmitDeployment(deploymentReq) - if err != nil { - return err - } - - return nil -} - // createHostAuthOptions returns a configured AuthOptions for HostOptions struct func (c *Command) createHostAuthOptions(machineName string) *auth.Options { return &auth.Options{ @@ -295,24 +238,25 @@ func (c *Command) CreateCluster() error { return err } - // create new Grid5000 API client - c.api = api.NewApi(c.cli.String("g5k-username"), c.cli.String("g5k-password"), c.cli.String("g5k-site")) + // TODO: Multi-sites reservation/deployment - // submit new job - if err := c.AllocateNodes(); err != nil { + // reserve nodes via the Grid5000 API + g5kJobID, err := c.g5kAPI.ReserveNodes(c.cli.String("g5k-site"), c.cli.Int("g5k-nb-nodes"), c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) + if err != nil { return err } - // wait until job is running - c.api.WaitUntilJobIsReady(c.g5kJobID) + // wait until job is ready + c.g5kAPI.WaitUntilJobIsReady(c.cli.String("g5k-site"), g5kJobID) // submit new deployment - if err := c.DeployNodes(); err != nil { + g5kDeploymentID, err := c.g5kAPI.DeployNodes(c.cli.String("g5k-site"), c.cli.String("g5k-ssh-public-key"), g5kJobID, c.cli.String("g5k-image")) + if err != nil { return err } // wait until deployment is finished - c.api.WaitUntilDeploymentIsFinished(c.g5kDeploymentID) + c.g5kAPI.WaitUntilDeploymentIsFinished(c.cli.String("g5k-site"), g5kDeploymentID) // provision nodes if err := c.ProvisionNodes(); err != nil { diff --git a/libdockerg5k/g5k/deployment.go b/libdockerg5k/g5k/deployment.go new file mode 100644 index 0000000..82878e4 --- /dev/null +++ b/libdockerg5k/g5k/deployment.go @@ -0,0 +1,51 @@ +package g5k + +import ( + "io/ioutil" + + "github.com/Spirals-Team/docker-machine-driver-g5k/api" +) + +// DeployNodes submit a deployment request and returns the Deployment ID +func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image string) (string, error) { + // reading ssh public key file + pubkey, err := ioutil.ReadFile(sshPublicKeyPath) + if err != nil { + return "", err + } + + // get required site API client + siteAPI := g.getSiteAPI(site) + + // get job informations + job, err := siteAPI.GetJob(jobID) + if err != nil { + return "", err + } + + // creating a new deployment request + deploymentReq := api.DeploymentRequest{ + Nodes: job.Nodes, + Environment: image, + Key: string(pubkey), + } + + // deploy environment + g5kDeploymentID, err := siteAPI.SubmitDeployment(deploymentReq) + if err != nil { + return "", err + } + + return g5kDeploymentID, nil +} + +// WaitUntilDeploymentIsFinished wait until deployment finish +func (g *G5K) WaitUntilDeploymentIsFinished(site string, deploymentID string) error { + siteAPI := g.getSiteAPI(site) + + if err := siteAPI.WaitUntilDeploymentIsFinished(deploymentID); err != nil { + return err + } + + return nil +} diff --git a/libdockerg5k/g5k/g5k.go b/libdockerg5k/g5k/g5k.go new file mode 100644 index 0000000..8ce1a94 --- /dev/null +++ b/libdockerg5k/g5k/g5k.go @@ -0,0 +1,36 @@ +package g5k + +import ( + "github.com/Spirals-Team/docker-machine-driver-g5k/api" +) + +// G5K stores all informations needed to use the Grid5000 API +type G5K struct { + username string + password string + sitesAPI map[string]*api.Api +} + +// Init initialize a new G5K struct with the given parameters +func Init(username string, password string) *G5K { + return &G5K{ + username: username, + password: password, + sitesAPI: map[string]*api.Api{}, + } +} + +// createSiteAPI create a new Grid5000 API client for the given site +func (g *G5K) createSiteAPI(site string) { + g.sitesAPI[site] = api.NewApi(g.username, g.password, site) +} + +// getSiteAPI returns the API client for the given site (create it if not exist) +func (g *G5K) getSiteAPI(site string) *api.Api { + // create API client for the site if it does not exist + if _, ok := g.sitesAPI[site]; !ok { + g.createSiteAPI(site) + } + + return g.sitesAPI[site] +} diff --git a/libdockerg5k/g5k/job.go b/libdockerg5k/g5k/job.go new file mode 100644 index 0000000..2f00a85 --- /dev/null +++ b/libdockerg5k/g5k/job.go @@ -0,0 +1,46 @@ +package g5k + +import ( + "fmt" + + "github.com/Spirals-Team/docker-machine-driver-g5k/api" +) + +// ReserveNodes allocate a new job with the required number of nodes on the given site, and returns the Job ID +func (g *G5K) ReserveNodes(site string, nbNodes int, resourceProperties string, walltime string) (int, error) { + // convert walltime to seconds + seconds, err := api.ConvertDuration(walltime) + if err != nil { + return -1, err + } + + // create a new job request with given parameters + jobReq := api.JobRequest{ + Resources: fmt.Sprintf("nodes=%v,walltime=%s", nbNodes, walltime), + Command: fmt.Sprintf("sleep %v", seconds), + Properties: resourceProperties, + Types: []string{"deploy"}, + } + + // get site API client + siteAPI := g.getSiteAPI(site) + + // submit job request + g5kJobID, err := siteAPI.SubmitJob(jobReq) + if err != nil { + return -1, err + } + + return g5kJobID, nil +} + +// WaitUntilJobIsReady wait until job reach 'ready' state +func (g *G5K) WaitUntilJobIsReady(site string, jobID int) error { + siteAPI := g.getSiteAPI(site) + + if err := siteAPI.WaitUntilJobIsReady(jobID); err != nil { + return err + } + + return nil +} From ea6b85ba2dfe9aece301055719c6249896f5349f Mon Sep 17 00:00:00 2001 From: gfieni Date: Mon, 6 Mar 2017 11:16:08 +0100 Subject: [PATCH 02/38] refactor(create_cluster): Moving wait for Job/Deployment parts in g5k module from libdockerg5k. BREAKING CHANGES: DeployNodes does not give the deployment ID anymore because its useless. --- command/create_cluster.go | 22 +++++----------------- libdockerg5k/g5k/deployment.go | 32 ++++++++++++++++---------------- libdockerg5k/g5k/job.go | 14 ++++---------- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 49771bc..f8c2224 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -125,16 +125,10 @@ func (c *Command) provisionNode(nodeName string, isSwarmMaster bool) error { } // ProvisionNodes provision the nodes -func (c *Command) ProvisionNodes() error { - // get deployment informations - deployment, err := c.api.GetDeployment(c.g5kDeploymentID) - if err != nil { - return err - } - +func (c *Command) ProvisionNodes(nodes []string) error { // provision all deployed nodes var wg sync.WaitGroup - for i, v := range deployment.Nodes { + for i, v := range nodes { wg.Add(1) go func(nodeID int, nodeName string) { defer wg.Done() @@ -241,25 +235,19 @@ func (c *Command) CreateCluster() error { // TODO: Multi-sites reservation/deployment // reserve nodes via the Grid5000 API - g5kJobID, err := c.g5kAPI.ReserveNodes(c.cli.String("g5k-site"), c.cli.Int("g5k-nb-nodes"), c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) + jobID, err := c.g5kAPI.ReserveNodes(c.cli.String("g5k-site"), c.cli.Int("g5k-nb-nodes"), c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) if err != nil { return err } - // wait until job is ready - c.g5kAPI.WaitUntilJobIsReady(c.cli.String("g5k-site"), g5kJobID) - // submit new deployment - g5kDeploymentID, err := c.g5kAPI.DeployNodes(c.cli.String("g5k-site"), c.cli.String("g5k-ssh-public-key"), g5kJobID, c.cli.String("g5k-image")) + deployedNodes, err := c.g5kAPI.DeployNodes(c.cli.String("g5k-site"), c.cli.String("g5k-ssh-public-key"), jobID, c.cli.String("g5k-image")) if err != nil { return err } - // wait until deployment is finished - c.g5kAPI.WaitUntilDeploymentIsFinished(c.cli.String("g5k-site"), g5kDeploymentID) - // provision nodes - if err := c.ProvisionNodes(); err != nil { + if err := c.ProvisionNodes(deployedNodes); err != nil { return err } diff --git a/libdockerg5k/g5k/deployment.go b/libdockerg5k/g5k/deployment.go index 82878e4..6b6219c 100644 --- a/libdockerg5k/g5k/deployment.go +++ b/libdockerg5k/g5k/deployment.go @@ -6,12 +6,12 @@ import ( "github.com/Spirals-Team/docker-machine-driver-g5k/api" ) -// DeployNodes submit a deployment request and returns the Deployment ID -func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image string) (string, error) { +// DeployNodes submit a deployment request and returns the deployed nodes hostname +func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image string) ([]string, error) { // reading ssh public key file pubkey, err := ioutil.ReadFile(sshPublicKeyPath) if err != nil { - return "", err + return nil, err } // get required site API client @@ -20,10 +20,10 @@ func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image // get job informations job, err := siteAPI.GetJob(jobID) if err != nil { - return "", err + return nil, err } - // creating a new deployment request + // create a new deployment request deploymentReq := api.DeploymentRequest{ Nodes: job.Nodes, Environment: image, @@ -31,21 +31,21 @@ func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image } // deploy environment - g5kDeploymentID, err := siteAPI.SubmitDeployment(deploymentReq) + deploymentID, err := siteAPI.SubmitDeployment(deploymentReq) if err != nil { - return "", err + return nil, err } - return g5kDeploymentID, nil -} - -// WaitUntilDeploymentIsFinished wait until deployment finish -func (g *G5K) WaitUntilDeploymentIsFinished(site string, deploymentID string) error { - siteAPI := g.getSiteAPI(site) - + // wait until deployment finish if err := siteAPI.WaitUntilDeploymentIsFinished(deploymentID); err != nil { - return err + return nil, err + } + + // get deployment informations + deployment, err := siteAPI.GetDeployment(deploymentID) + if err != nil { + return nil, err } - return nil + return deployment.Nodes, nil } diff --git a/libdockerg5k/g5k/job.go b/libdockerg5k/g5k/job.go index 2f00a85..8f0006d 100644 --- a/libdockerg5k/g5k/job.go +++ b/libdockerg5k/g5k/job.go @@ -26,21 +26,15 @@ func (g *G5K) ReserveNodes(site string, nbNodes int, resourceProperties string, siteAPI := g.getSiteAPI(site) // submit job request - g5kJobID, err := siteAPI.SubmitJob(jobReq) + jobID, err := siteAPI.SubmitJob(jobReq) if err != nil { return -1, err } - return g5kJobID, nil -} - -// WaitUntilJobIsReady wait until job reach 'ready' state -func (g *G5K) WaitUntilJobIsReady(site string, jobID int) error { - siteAPI := g.getSiteAPI(site) - + // wait until job reach 'ready' state if err := siteAPI.WaitUntilJobIsReady(jobID); err != nil { - return err + return 0, err } - return nil + return jobID, nil } From 88473e5272681bdb4bd41803bcd1436487d927d2 Mon Sep 17 00:00:00 2001 From: gfieni Date: Mon, 6 Mar 2017 13:04:37 +0100 Subject: [PATCH 03/38] refactor(create-cluster): Moving JobID to nodes provisionning function. --- command/create_cluster.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index f8c2224..61f7d65 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -15,6 +15,7 @@ import ( "github.com/Spirals-Team/docker-machine-driver-g5k/driver" + "github.com/Spirals-Team/docker-g5k/libdockerg5k/g5k" g5kswarm "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" "github.com/Spirals-Team/docker-g5k/libdockerg5k/weave" ) @@ -57,7 +58,7 @@ func (c *Command) createHostSwarmOptions(machineName string, isMaster bool) *swa } } -func (c *Command) provisionNode(nodeName string, isSwarmMaster bool) error { +func (c *Command) provisionNode(nodeName string, jobID int, isSwarmMaster bool) error { // create a new libmachine client client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) defer client.Close() @@ -76,7 +77,7 @@ func (c *Command) provisionNode(nodeName string, isSwarmMaster bool) error { driver.G5kSSHPublicKeyPath = c.cli.String("g5k-ssh-public-key") driver.G5kHostToProvision = nodeName - driver.G5kJobID = c.g5kJobID + driver.G5kJobID = jobID // set base driver parameters driver.BaseDriver.MachineName = nodeName @@ -125,7 +126,7 @@ func (c *Command) provisionNode(nodeName string, isSwarmMaster bool) error { } // ProvisionNodes provision the nodes -func (c *Command) ProvisionNodes(nodes []string) error { +func (c *Command) ProvisionNodes(nodes []string, jobID int) error { // provision all deployed nodes var wg sync.WaitGroup for i, v := range nodes { @@ -135,9 +136,9 @@ func (c *Command) ProvisionNodes(nodes []string) error { // first node will be the swarm master if nodeID == 0 { - c.provisionNode(nodeName, true) + c.provisionNode(nodeName, jobID, true) } else { - c.provisionNode(nodeName, false) + c.provisionNode(nodeName, jobID, false) } }(i, v) @@ -232,22 +233,23 @@ func (c *Command) CreateCluster() error { return err } - // TODO: Multi-sites reservation/deployment + // Create Grid5000 API client + c.g5kAPI = g5k.Init(c.cli.String("g5k-username"), c.cli.String("g5k-password")) - // reserve nodes via the Grid5000 API + // reserve nodes jobID, err := c.g5kAPI.ReserveNodes(c.cli.String("g5k-site"), c.cli.Int("g5k-nb-nodes"), c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) if err != nil { return err } - // submit new deployment + // deploy the nodes deployedNodes, err := c.g5kAPI.DeployNodes(c.cli.String("g5k-site"), c.cli.String("g5k-ssh-public-key"), jobID, c.cli.String("g5k-image")) if err != nil { return err } // provision nodes - if err := c.ProvisionNodes(deployedNodes); err != nil { + if err := c.ProvisionNodes(deployedNodes, jobID); err != nil { return err } From e6a936e0f6bb32d76b032bb0e1104ac0b96ce498 Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 7 Mar 2017 14:26:04 +0100 Subject: [PATCH 04/38] feat(create-cluster): Making machines name predictable : {site}-{id} --- command/create_cluster.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 61f7d65..3712c82 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -36,7 +36,7 @@ func (c *Command) createHostAuthOptions(machineName string) *auth.Options { } // createHostSwarmOptions returns a configured SwarmOptions for HostOptions struct -func (c *Command) createHostSwarmOptions(machineName string, isMaster bool) *swarm.Options { +func (c *Command) createHostSwarmOptions(nodeName string, isMaster bool) *swarm.Options { runAgent := true // By default, exclude master node from Swarm pool, but can be overrided by swarm-master-join flag if isMaster && !c.cli.Bool("swarm-master-join") { @@ -49,7 +49,7 @@ func (c *Command) createHostSwarmOptions(machineName string, isMaster bool) *swa Agent: runAgent, Master: isMaster, Discovery: c.cli.String("swarm-discovery"), - Address: machineName, + Address: nodeName, Host: "tcp://0.0.0.0:3376", Strategy: c.cli.String("swarm-strategy"), ArbitraryFlags: c.cli.StringSlice("swarm-opt"), @@ -58,7 +58,7 @@ func (c *Command) createHostSwarmOptions(machineName string, isMaster bool) *swa } } -func (c *Command) provisionNode(nodeName string, jobID int, isSwarmMaster bool) error { +func (c *Command) provisionNode(nodeName string, machineName string, jobID int, isSwarmMaster bool) error { // create a new libmachine client client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) defer client.Close() @@ -80,7 +80,7 @@ func (c *Command) provisionNode(nodeName string, jobID int, isSwarmMaster bool) driver.G5kJobID = jobID // set base driver parameters - driver.BaseDriver.MachineName = nodeName + driver.BaseDriver.MachineName = machineName driver.BaseDriver.StorePath = mcndirs.GetBaseDir() driver.BaseDriver.SSHKeyPath = driver.GetSSHKeyPath() @@ -97,7 +97,7 @@ func (c *Command) provisionNode(nodeName string, jobID int, isSwarmMaster bool) } // mandatory, or driver will use bad paths - h.HostOptions.AuthOptions = c.createHostAuthOptions(nodeName) + h.HostOptions.AuthOptions = c.createHostAuthOptions(machineName) // set swarm options h.HostOptions.SwarmOptions = c.createHostSwarmOptions(nodeName, isSwarmMaster) @@ -126,7 +126,7 @@ func (c *Command) provisionNode(nodeName string, jobID int, isSwarmMaster bool) } // ProvisionNodes provision the nodes -func (c *Command) ProvisionNodes(nodes []string, jobID int) error { +func (c *Command) ProvisionNodes(site string, nodes []string, jobID int) error { // provision all deployed nodes var wg sync.WaitGroup for i, v := range nodes { @@ -134,11 +134,14 @@ func (c *Command) ProvisionNodes(nodes []string, jobID int) error { go func(nodeID int, nodeName string) { defer wg.Done() + // compute Machine name + machineName := fmt.Sprintf("%s-%d", site, nodeID) + // first node will be the swarm master if nodeID == 0 { - c.provisionNode(nodeName, jobID, true) + c.provisionNode(nodeName, machineName, jobID, true) } else { - c.provisionNode(nodeName, jobID, false) + c.provisionNode(nodeName, machineName, jobID, false) } }(i, v) @@ -224,7 +227,7 @@ func (c *Command) checkCliParameters() error { // CreateCluster create nodes in docker-machine func (c *Command) CreateCluster() error { - // create a new libmachine client + // create libmachine client client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) defer client.Close() @@ -233,7 +236,7 @@ func (c *Command) CreateCluster() error { return err } - // Create Grid5000 API client + // create Grid5000 API client c.g5kAPI = g5k.Init(c.cli.String("g5k-username"), c.cli.String("g5k-password")) // reserve nodes @@ -242,14 +245,14 @@ func (c *Command) CreateCluster() error { return err } - // deploy the nodes + // deploy nodes deployedNodes, err := c.g5kAPI.DeployNodes(c.cli.String("g5k-site"), c.cli.String("g5k-ssh-public-key"), jobID, c.cli.String("g5k-image")) if err != nil { return err } // provision nodes - if err := c.ProvisionNodes(deployedNodes, jobID); err != nil { + if err := c.ProvisionNodes(c.cli.String("g5k-site"), deployedNodes, jobID); err != nil { return err } From 6ad6c8263aff411d8bac1e760b0511d39109ffef Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 7 Mar 2017 16:29:47 +0100 Subject: [PATCH 05/38] feat(create-cluster): Adding support for multi-site cluster creation. Removing CLI flags "g5k-site" and "g5k-nb-nodes". Adding new CLI flag "g5k-reserve-nodes" to replace the removed flags. BREAKING CHANGES: flags "g5k-site" and "g5k-nb-nodes" are removed. --- command/create_cluster.go | 57 +++++++++++++++++++++++---------------- main.go | 17 ++++-------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 3712c82..f75d319 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -15,6 +15,10 @@ import ( "github.com/Spirals-Team/docker-machine-driver-g5k/driver" + "strings" + + "strconv" + "github.com/Spirals-Team/docker-g5k/libdockerg5k/g5k" g5kswarm "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" "github.com/Spirals-Team/docker-g5k/libdockerg5k/weave" @@ -58,7 +62,7 @@ func (c *Command) createHostSwarmOptions(nodeName string, isMaster bool) *swarm. } } -func (c *Command) provisionNode(nodeName string, machineName string, jobID int, isSwarmMaster bool) error { +func (c *Command) provisionNode(site string, nodeName string, machineName string, jobID int, isSwarmMaster bool) error { // create a new libmachine client client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) defer client.Close() @@ -69,7 +73,7 @@ func (c *Command) provisionNode(nodeName string, machineName string, jobID int, // set g5k driver parameters driver.G5kUsername = c.cli.String("g5k-username") driver.G5kPassword = c.cli.String("g5k-password") - driver.G5kSite = c.cli.String("g5k-site") + driver.G5kSite = site driver.G5kImage = c.cli.String("g5k-image") driver.G5kWalltime = c.cli.String("g5k-walltime") @@ -139,9 +143,9 @@ func (c *Command) ProvisionNodes(site string, nodes []string, jobID int) error { // first node will be the swarm master if nodeID == 0 { - c.provisionNode(nodeName, machineName, jobID, true) + c.provisionNode(site, nodeName, machineName, jobID, true) } else { - c.provisionNode(nodeName, machineName, jobID, false) + c.provisionNode(site, nodeName, machineName, jobID, false) } }(i, v) @@ -167,12 +171,6 @@ func (c *Command) checkCliParameters() error { return fmt.Errorf("You must provide your Grid5000 account password") } - // check site - g5kSite := c.cli.String("g5k-site") - if g5kSite == "" { - return fmt.Errorf("You must provide a site to reserve the ressources on") - } - // check ssh private key sshPrivKey := c.cli.String("g5k-ssh-private-key") if sshPrivKey == "" { @@ -239,21 +237,34 @@ func (c *Command) CreateCluster() error { // create Grid5000 API client c.g5kAPI = g5k.Init(c.cli.String("g5k-username"), c.cli.String("g5k-password")) - // reserve nodes - jobID, err := c.g5kAPI.ReserveNodes(c.cli.String("g5k-site"), c.cli.Int("g5k-nb-nodes"), c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) - if err != nil { - return err - } + // process nodes reservations + for _, r := range c.cli.StringSlice("g5k-reserve-nodes") { + // extract site name and number of nodes to reserve + v := strings.Split(r, ":") + site := v[0] + nbNodes, err := strconv.Atoi(v[1]) + if err != nil { + return err + } - // deploy nodes - deployedNodes, err := c.g5kAPI.DeployNodes(c.cli.String("g5k-site"), c.cli.String("g5k-ssh-public-key"), jobID, c.cli.String("g5k-image")) - if err != nil { - return err - } + log.Infof("Reserving %d nodes on '%s' site...", nbNodes, site) - // provision nodes - if err := c.ProvisionNodes(c.cli.String("g5k-site"), deployedNodes, jobID); err != nil { - return err + // reserve nodes + jobID, err := c.g5kAPI.ReserveNodes(site, nbNodes, c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) + if err != nil { + return err + } + + // deploy nodes + deployedNodes, err := c.g5kAPI.DeployNodes(site, c.cli.String("g5k-ssh-public-key"), jobID, c.cli.String("g5k-image")) + if err != nil { + return err + } + + // provision nodes + if err := c.ProvisionNodes(site, deployedNodes, jobID); err != nil { + return err + } } return nil diff --git a/main.go b/main.go index bbc491e..9f602a5 100644 --- a/main.go +++ b/main.go @@ -32,12 +32,6 @@ var ( Value: "", }, - cli.StringFlag{ - Name: "g5k-site", - Usage: "Site to reserve the resources on", - Value: "", - }, - cli.StringFlag{ Name: "g5k-walltime", Usage: "Machine's lifetime (HH:MM:SS)", @@ -68,12 +62,6 @@ var ( Value: "", }, - cli.IntFlag{ - Name: "g5k-nb-nodes", - Usage: "Number of nodes to allocate", - Value: 3, - }, - cli.StringFlag{ Name: "swarm-discovery", Usage: "Discovery service to use with Swarm", @@ -113,6 +101,11 @@ var ( Name: "weave-networking", Usage: "Use Weave for networking", }, + + cli.StringSliceFlag{ + Name: "g5k-reserve-nodes", + Usage: "Reserve nodes on a site (ex: lille:24)", + }, }, }, { From ca997b29a36e0f9ec6b5c3d3a3aae29d194249f8 Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 7 Mar 2017 16:45:17 +0100 Subject: [PATCH 06/38] docs(README): Updating README for multi-site cluster creation --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ff39fe9..a8a1777 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,7 @@ Please follow the instructions from the [Grid5000 Wiki](https://www.grid5000.fr/ |------------------------------|---------------------------------------------------------|-----------------------|------------| | `--g5k-username` | Your Grid5000 account username | | Yes | | `--g5k-password` | Your Grid5000 account password | | Yes | -| `--g5k-site` | Site to reserve the resources on | | Yes | -| `--g5k-nb-nodes` | Number of nodes to allocate | 3 | No | +| `--g5k-reserve-nodes` | Reserve nodes on a site (ex: lille:24) | | Yes | | `--g5k-walltime` | Timelife of the machine | "1:00:00" | No | | `--g5k-ssh-private-key` | Path of your ssh private key | "~/.ssh/id_rsa" | No | | `--g5k-ssh-public-key` | Path of your ssh public key | "< private-key >.pub" | No | @@ -70,18 +69,18 @@ Please follow the instructions from the [Grid5000 Wiki](https://www.grid5000.fr/ An example of a 3 nodes Docker Swarm cluster creation: ```bash docker-g5k create-cluster \ ---g5k-username user \ ---g5k-password ******** \ ---g5k-site lille \ +--g5k-username "user" \ +--g5k-password "********" \ +--g5k-reserve-nodes "lille:3" --g5k-ssh-private-key ~/.ssh/g5k-key ``` An example where 3 nodes join an existing Docker Swarm cluster using a discovery token: ```bash docker-g5k create-cluster \ ---g5k-username user \ ---g5k-password ******** \ ---g5k-site lille \ +--g5k-username "user" \ +--g5k-password "********" \ +--g5k-reserve-nodes "lille:3" --swarm-discovery "token://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ --g5k-ssh-private-key ~/.ssh/g5k-key ``` @@ -89,11 +88,10 @@ docker-g5k create-cluster \ An example of a 16 nodes Docker Swarm cluster creation with resource properties (nodes in cluster `chimint` with more thant 8GB of RAM and at least 4 CPU cores): ```bash docker-g5k create-cluster \ ---g5k-username user \ ---g5k-password ******** \ ---g5k-site lille \ +--g5k-username "user" \ +--g5k-password "********" \ --g5k-ssh-private-key ~/.ssh/g5k-key \ ---g5k-nb-nodes 16 \ +--g5k-reserve-nodes "lille:16" --g5k-resource-properties "cluster = 'chimint' and memnode > 8192 and cpucore >= 4" ``` @@ -128,7 +126,7 @@ eval $(docker-machine env --swarm swarm-master-node-name) Then run a container using Weave networking: ```bash -docker run --net=weave -h foo.weave.local --name foo $(~/.docker/machine/weave-net dns-args) -td your-image:version +docker run --net=weave -h foo.weave.local --name foo --dns=172.17.0.1 --dns-search=weave.local. -td your-image:version ``` Your containers can now communicate with each other using theirs short ('foo') or long ('foo.weave.local') name. The name used NEED to be the one given in parameter '-h'. The name of the container (parameter '--name') is not used by Weave. \ No newline at end of file From 3e640aab28cc194c73753cfe5e750331511fe0a1 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 8 Mar 2017 10:32:08 +0100 Subject: [PATCH 07/38] chrore(Travis CI): Adding x86 builds for all OS. --- .travis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index aac0830..bfd7ca0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,15 @@ language: go go_import_path: github.com/Spirals-Team/docker-g5k env: + # Linux - GIMME_OS=linux GIMME_ARCH=amd64 + - GIMME_OS=linux GIMME_ARCH=386 + # MacOS - GIMME_OS=darwin GIMME_ARCH=amd64 + - GIMME_OS=darwin GIMME_ARCH=386 + # Windows - GIMME_OS=windows GIMME_ARCH=amd64 + - GIMME_OS=windows GIMME_ARCH=386 install: - go get -d -v ./... @@ -18,9 +24,7 @@ deploy: provider: releases skip_cleanup: true file: "docker-g5k-$GIMME_OS-$GIMME_ARCH" - on: tags: true - api_key: - secure: AmCrDfWm2XV6tNawIpcq/eLdHl83ns5foq3+CBE/RjF2ncqhLProvbZf8LeYivtA+Q/fbWV6LK3I2cQKFWj3h47llwL0goeq2GW3VpQBx9vEnDrZbR0T3XydWAD7BgHVKXDs6ppk3c1F+QtVWuKxyc/4whN+uRDwckMmZ+ZJQacCf6mutMA1xxQYZFbZ/QCI+hrbRaQNgPuN7LAOoWq9SMpnE5EcMNj2vdEWsS6wcFRmO6ZDweTXt2aLQqrilBVfQfdG58U2PmAQNvYGjN/uJeliIOph4wwhkDR5qMQguex/dPvytps+tEShKYpPJD0lGNhTk/fM0J6Gyw1VxTTbN+jDw3MvUccFMl+yUM2puTRwBTdSBb5wcABjeQIk+C5TS9Hx8woiV+OasK9a/0Ar5DogpDWFrM99VyXoYA9ilUG22JzNlAoIHHpWAIuIrG0637tlV66We4fWWUMbj1Smh0ZJ1AqzuB8f23PkYBf0pDETO77Y1Mrb6WrchKmyK/IhIf0JlBAEBBBEfCAnaa8PeGTSlEIJoBeEZf4dg6daOUtC1azXIL7GNw0wpJ7ah7ziWNZhH1L1F1ZhZCWUHCqwsZkRUtSfaG9C/wZuHZiz7mcfDnUQ8leBXbWJIVQ9+fyEMPeqlq7vNbSSikrV+icTbLw8k1zCwfy9BJGJShPsT8Q= \ No newline at end of file + secure: AmCrDfWm2XV6tNawIpcq/eLdHl83ns5foq3+CBE/RjF2ncqhLProvbZf8LeYivtA+Q/fbWV6LK3I2cQKFWj3h47llwL0goeq2GW3VpQBx9vEnDrZbR0T3XydWAD7BgHVKXDs6ppk3c1F+QtVWuKxyc/4whN+uRDwckMmZ+ZJQacCf6mutMA1xxQYZFbZ/QCI+hrbRaQNgPuN7LAOoWq9SMpnE5EcMNj2vdEWsS6wcFRmO6ZDweTXt2aLQqrilBVfQfdG58U2PmAQNvYGjN/uJeliIOph4wwhkDR5qMQguex/dPvytps+tEShKYpPJD0lGNhTk/fM0J6Gyw1VxTTbN+jDw3MvUccFMl+yUM2puTRwBTdSBb5wcABjeQIk+C5TS9Hx8woiV+OasK9a/0Ar5DogpDWFrM99VyXoYA9ilUG22JzNlAoIHHpWAIuIrG0637tlV66We4fWWUMbj1Smh0ZJ1AqzuB8f23PkYBf0pDETO77Y1Mrb6WrchKmyK/IhIf0JlBAEBBBEfCAnaa8PeGTSlEIJoBeEZf4dg6daOUtC1azXIL7GNw0wpJ7ah7ziWNZhH1L1F1ZhZCWUHCqwsZkRUtSfaG9C/wZuHZiz7mcfDnUQ8leBXbWJIVQ9+fyEMPeqlq7vNbSSikrV+icTbLw8k1zCwfy9BJGJShPsT8Q= From d4b53cc66e3bfe62009002dcafcfedb4f9387aa0 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 8 Mar 2017 10:33:21 +0100 Subject: [PATCH 08/38] docs(README): Adding "Installation from GitHub Releases" informations. --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a8a1777..0d60a51 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,23 @@ It only support creating and deleting nodes in Docker Machine. * [Docker](https://www.docker.com/products/overview#/install_the_platform) * [Docker Machine](https://docs.docker.com/machine/install-machine) * [Docker Machine Driver for Grid5000](https://github.com/Spirals-Team/docker-machine-driver-g5k) -* [Go tools](https://golang.org/doc/install) +* [Go tools (Only for installation from sources)](https://golang.org/doc/install) You need a Grid5000 account to use this tool. See [this page](https://www.grid5000.fr/mediawiki/index.php/Grid5000:Get_an_account) to create an account. ## Installation +## Installation from GitHub releases +Binary releases are available for Linux, MacOS and Windows using x86/x86_64 CPU architectures from the [releases page](https://github.com/Spirals-Team/docker-g5k/releases). +You can use the following commands to install or upgrade the driver: +```bash +# download the binary for your OS and CPU architecture +sudo curl -L -o /usr/local/bin/docker-g5k "" + +# grant execution rigths to the driver for everyone : +sudo chmod 755 /usr/local/bin/docker-g5k +``` + ## Installation from sources *This procedure was only tested on Ubuntu 16.04.* From 88c5eca8060023862289e6f0237e8a3db93d5eb9 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 8 Mar 2017 14:45:34 +0100 Subject: [PATCH 09/38] feat(create-cluster): Adding check to CLI flag "g5k-reserve-nodes". --- command/create_cluster.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/command/create_cluster.go b/command/create_cluster.go index f75d319..1a7b3ce 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -171,6 +171,11 @@ func (c *Command) checkCliParameters() error { return fmt.Errorf("You must provide your Grid5000 account password") } + // check nodes reservation + if len(c.cli.StringSlice("g5k-reserve-nodes")) < 1 { + return fmt.Errorf("You must provide a site and the number of nodes to reserve on it") + } + // check ssh private key sshPrivKey := c.cli.String("g5k-ssh-private-key") if sshPrivKey == "" { From 4ae262951fe7b92e9f6eb3ce6b1739876dbeebd7 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 8 Mar 2017 15:05:10 +0100 Subject: [PATCH 10/38] docs(README): Updating informations about docker-g5k --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0d60a51..8a54623 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # docker-g5k -A tool to create a Docker Swarm cluster for Docker Machine on Grid5000 testbed infrastructure. -It only support creating and deleting nodes in Docker Machine. +A tool to create a Docker Swarm cluster for Docker Machine on the Grid5000 testbed infrastructure. ## Requirements * [Docker](https://www.docker.com/products/overview#/install_the_platform) @@ -13,21 +12,20 @@ You need a Grid5000 account to use this tool. See [this page](https://www.grid50 ## Installation ## Installation from GitHub releases -Binary releases are available for Linux, MacOS and Windows using x86/x86_64 CPU architectures from the [releases page](https://github.com/Spirals-Team/docker-g5k/releases). -You can use the following commands to install or upgrade the driver: +Binary releases for Linux, MacOS and Windows using x86/x86_64 CPU architectures are available in the [releases page](https://github.com/Spirals-Team/docker-g5k/releases). +You can use the following commands to install or upgrade the tool: ```bash -# download the binary for your OS and CPU architecture +# download the binary for your OS and CPU architecture : sudo curl -L -o /usr/local/bin/docker-g5k "" -# grant execution rigths to the driver for everyone : +# grant execution rigths for everyone : sudo chmod 755 /usr/local/bin/docker-g5k ``` ## Installation from sources *This procedure was only tested on Ubuntu 16.04.* -To use the Go tools, you need to set your [GOPATH](https://golang.org/doc/code.html#GOPATH) variable environment. - +To use the Go tools, you need to set your [GOPATH](https://golang.org/doc/code.html#GOPATH) variable environment. To get the code and compile the binary, run: ```bash go get -u github.com/Spirals-Team/docker-g5k From d3597f057d3237bb06f48d1a73531d1d44c29028 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 8 Mar 2017 17:08:02 +0100 Subject: [PATCH 11/38] feat(create-cluster): Disable Swarm configuration by default, add CLI flag to enable it. BREAKING CHANGES: Swarm is not configured by default, you need to use the flag "--swarm-enable" --- command/create_cluster.go | 10 ++++++---- main.go | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 1a7b3ce..7e42849 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -103,16 +103,18 @@ func (c *Command) provisionNode(site string, nodeName string, machineName string // mandatory, or driver will use bad paths h.HostOptions.AuthOptions = c.createHostAuthOptions(machineName) - // set swarm options - h.HostOptions.SwarmOptions = c.createHostSwarmOptions(nodeName, isSwarmMaster) + // set swarm options if Swarm (standalone) is enabled + if c.cli.Bool("swarm-enable") { + h.HostOptions.SwarmOptions = c.createHostSwarmOptions(nodeName, isSwarmMaster) + } // provision the new machine if err := client.Create(h); err != nil { return err } - // install and run Weave Net / Discovery if Weave networking mode is enabled - if c.cli.Bool("weave-networking") { + // install and run Weave Net / Discovery if Weave networking mode and Swarm are enabled + if c.cli.Bool("weave-networking") && c.cli.Bool("swarm-enable") { // run Weave Net log.Info("Running Weave Net...") if err := weave.RunWeaveNet(h); err != nil { diff --git a/main.go b/main.go index 9f602a5..c5c42dc 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,11 @@ var ( Value: "", }, + cli.BoolFlag{ + Name: "swarm-enable", + Usage: "Enable Swarm (standalone) on the cluster", + }, + cli.StringFlag{ Name: "swarm-discovery", Usage: "Discovery service to use with Swarm", @@ -99,7 +104,7 @@ var ( cli.BoolFlag{ Name: "weave-networking", - Usage: "Use Weave for networking", + Usage: "Use Weave for networking (Only if Swarm is enabled)", }, cli.StringSliceFlag{ From 0baa3bb5fe25f29a3655b50ca01b617beb4fa104 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 8 Mar 2017 17:09:11 +0100 Subject: [PATCH 12/38] docs(README): Minor corrections, update informations about Swarm enable flag. --- README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8a54623..3a1bb61 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,14 @@ Please follow the instructions from the [Grid5000 Wiki](https://www.grid5000.fr/ | `--g5k-ssh-public-key` | Path of your ssh public key | "< private-key >.pub" | No | | `--g5k-image` | Name of the image to deploy | "jessie-x64-min" | No | | `--g5k-resource-properties` | Resource selection with OAR properties (SQL format) | | No | +| `--swarm-enable` | Enable Swarm (standalone) on the cluster | False | No | | `--swarm-discovery` | Discovery service to use with Swarm | Generate a new token | No | | `--swarm-image` | Specify Docker image to use for Swarm | "swarm:latest" | No | | `--swarm-strategy` | Define a default scheduling strategy for Swarm | "spread" | No | | `--swarm-opt` | Define arbitrary flags for Swarm master | | No | | `--swarm-join-opt` | Define arbitrary flags for Swarm join | | No | | `--swarm-master-join` | Make Swarm master join the Swarm pool | False | No | -| `--weave-networking` | Use Weave for networking | False | No | +| `--weave-networking` | Use Weave for networking (Only if Swarm is enabled) | False | No | #### Cluster deletion flags @@ -75,23 +76,34 @@ Please follow the instructions from the [Grid5000 Wiki](https://www.grid5000.fr/ #### Cluster creation -An example of a 3 nodes Docker Swarm cluster creation: +An example of a 6 nodes Docker reservation. There is only Docker installed, and Swarm is not configured. ```bash docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ ---g5k-reserve-nodes "lille:3" +--g5k-reserve-nodes "lille:6" \ --g5k-ssh-private-key ~/.ssh/g5k-key ``` +An example of a 3 nodes Docker Swarm cluster creation: +```bash +docker-g5k create-cluster \ +--g5k-username "user" \ +--g5k-password "********" \ +--g5k-reserve-nodes "lille:3" \ +--g5k-ssh-private-key ~/.ssh/g5k-key \ +--swarm-enable +``` + An example where 3 nodes join an existing Docker Swarm cluster using a discovery token: ```bash docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ ---g5k-reserve-nodes "lille:3" +--g5k-reserve-nodes "lille:3" \ --swarm-discovery "token://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ ---g5k-ssh-private-key ~/.ssh/g5k-key +--g5k-ssh-private-key ~/.ssh/g5k-key \ +--swarm-enable ``` An example of a 16 nodes Docker Swarm cluster creation with resource properties (nodes in cluster `chimint` with more thant 8GB of RAM and at least 4 CPU cores): @@ -99,8 +111,9 @@ An example of a 16 nodes Docker Swarm cluster creation with resource properties docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ +--g5k-reserve-nodes "lille:16" \ --g5k-ssh-private-key ~/.ssh/g5k-key \ ---g5k-reserve-nodes "lille:16" +--swarm-enable \ --g5k-resource-properties "cluster = 'chimint' and memnode > 8192 and cpucore >= 4" ``` From 2d6746adaeb78618c17dded44775c5b6a8eaf4c0 Mon Sep 17 00:00:00 2001 From: gfieni Date: Thu, 9 Mar 2017 15:59:31 +0100 Subject: [PATCH 13/38] feat(create-cluster): Add base for Swarm mode support. --- libdockerg5k/swarm/mode.go | 28 +++++++++++++++++++ .../swarm/{discovery.go => standalone.go} | 0 2 files changed, 28 insertions(+) create mode 100644 libdockerg5k/swarm/mode.go rename libdockerg5k/swarm/{discovery.go => standalone.go} (100%) diff --git a/libdockerg5k/swarm/mode.go b/libdockerg5k/swarm/mode.go new file mode 100644 index 0000000..511f4e2 --- /dev/null +++ b/libdockerg5k/swarm/mode.go @@ -0,0 +1,28 @@ +package swarm + +import "github.com/docker/machine/libmachine/host" + +// JoinMode defined join modes for Swarm +type JoinMode int + +const ( + // Manager makes the node join the cluster as Swarm Manager + Manager JoinMode = iota + // Worker makes the node join the cluster as Swarm Worker + Worker +) + +// InitSwarmModeCluster initialize a new Swarm mode cluster on the given host and returns the Manager/Worker join tokens +func InitSwarmModeCluster(h *host.Host) (string, string, error) { + // Launch Weave Net Router + if _, err := h.RunSSHCommand("docker swarm init"); err != nil { + return "", "", err + } + + return "", "", nil +} + +// JoinSwarmModecluster makes the host join a Swarm mode cluster as Manager or Worker +func JoinSwarmModecluster(h *host.Host, j JoinMode, t string) error { + return nil +} diff --git a/libdockerg5k/swarm/discovery.go b/libdockerg5k/swarm/standalone.go similarity index 100% rename from libdockerg5k/swarm/discovery.go rename to libdockerg5k/swarm/standalone.go From 50ccf3c23272a502736770de2cd73907e4b1b3d6 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 10 Mar 2017 11:29:08 +0100 Subject: [PATCH 14/38] refactor(create-cluster): Renaming Swarm standalone CLI flags, moving libmachine client declaration. --- command/commands.go | 3 --- command/create_cluster.go | 53 +++++++++++++++++++------------------- libdockerg5k/swarm/mode.go | 12 ++++----- main.go | 33 ++++++++++++++---------- 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/command/commands.go b/command/commands.go index fce500d..e06c2b0 100644 --- a/command/commands.go +++ b/command/commands.go @@ -9,9 +9,6 @@ import ( type Command struct { cli *cli.Context g5kAPI *g5k.G5K - - g5kJobID int - g5kDeploymentID string } // NewCommandContext verify mandatory parameters and returns a new CommandContext diff --git a/command/create_cluster.go b/command/create_cluster.go index 7e42849..cddbd73 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -42,31 +42,27 @@ func (c *Command) createHostAuthOptions(machineName string) *auth.Options { // createHostSwarmOptions returns a configured SwarmOptions for HostOptions struct func (c *Command) createHostSwarmOptions(nodeName string, isMaster bool) *swarm.Options { runAgent := true - // By default, exclude master node from Swarm pool, but can be overrided by swarm-master-join flag - if isMaster && !c.cli.Bool("swarm-master-join") { + // By default, exclude master node from Swarm pool, but can be overrided by swarm-standalone-master-join flag + if isMaster && !c.cli.Bool("swarm-standalone-master-join") { runAgent = false } return &swarm.Options{ IsSwarm: true, - Image: c.cli.String("swarm-image"), + Image: c.cli.String("swarm-standalone-image"), Agent: runAgent, Master: isMaster, - Discovery: c.cli.String("swarm-discovery"), + Discovery: c.cli.String("swarm-standalone-discovery"), Address: nodeName, Host: "tcp://0.0.0.0:3376", - Strategy: c.cli.String("swarm-strategy"), - ArbitraryFlags: c.cli.StringSlice("swarm-opt"), - ArbitraryJoinFlags: c.cli.StringSlice("swarm-join-opt"), + Strategy: c.cli.String("swarm-standalone-strategy"), + ArbitraryFlags: c.cli.StringSlice("swarm-standalone-opt"), + ArbitraryJoinFlags: c.cli.StringSlice("swarm-standalone-join-opt"), IsExperimental: false, } } -func (c *Command) provisionNode(site string, nodeName string, machineName string, jobID int, isSwarmMaster bool) error { - // create a new libmachine client - client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) - defer client.Close() - +func (c *Command) provisionNode(libMachineClient *libmachine.Client, site string, nodeName string, machineName string, jobID int, isSwarmMaster bool) error { // create driver instance for libmachine driver := driver.NewDriver() @@ -95,7 +91,7 @@ func (c *Command) provisionNode(site string, nodeName string, machineName string } // create a new host config - h, err := client.NewHost("g5k", data) + h, err := libMachineClient.NewHost("g5k", data) if err != nil { return err } @@ -103,18 +99,18 @@ func (c *Command) provisionNode(site string, nodeName string, machineName string // mandatory, or driver will use bad paths h.HostOptions.AuthOptions = c.createHostAuthOptions(machineName) - // set swarm options if Swarm (standalone) is enabled - if c.cli.Bool("swarm-enable") { + // set swarm options if Swarm standalone is enabled + if c.cli.Bool("swarm-standalone-enable") { h.HostOptions.SwarmOptions = c.createHostSwarmOptions(nodeName, isSwarmMaster) } // provision the new machine - if err := client.Create(h); err != nil { + if err := libMachineClient.Create(h); err != nil { return err } // install and run Weave Net / Discovery if Weave networking mode and Swarm are enabled - if c.cli.Bool("weave-networking") && c.cli.Bool("swarm-enable") { + if c.cli.Bool("weave-networking") { // run Weave Net log.Info("Running Weave Net...") if err := weave.RunWeaveNet(h); err != nil { @@ -123,7 +119,7 @@ func (c *Command) provisionNode(site string, nodeName string, machineName string // run Weave Discovery log.Info("Running Weave Discovery...") - if err := weave.RunWeaveDiscovery(h, c.cli.String("swarm-discovery")); err != nil { + if err := weave.RunWeaveDiscovery(h, c.cli.String("swarm-standalone-discovery")); err != nil { return err } } @@ -132,7 +128,7 @@ func (c *Command) provisionNode(site string, nodeName string, machineName string } // ProvisionNodes provision the nodes -func (c *Command) ProvisionNodes(site string, nodes []string, jobID int) error { +func (c *Command) ProvisionNodes(libMachineClient *libmachine.Client, site string, nodes []string, jobID int) error { // provision all deployed nodes var wg sync.WaitGroup for i, v := range nodes { @@ -145,9 +141,9 @@ func (c *Command) ProvisionNodes(site string, nodes []string, jobID int) error { // first node will be the swarm master if nodeID == 0 { - c.provisionNode(site, nodeName, machineName, jobID, true) + c.provisionNode(libMachineClient, site, nodeName, machineName, jobID, true) } else { - c.provisionNode(site, nodeName, machineName, jobID, false) + c.provisionNode(libMachineClient, site, nodeName, machineName, jobID, false) } }(i, v) @@ -202,7 +198,7 @@ func (c *Command) checkCliParameters() error { } // check Docker Swarm discovery - swarmDiscovery := c.cli.String("swarm-discovery") + swarmDiscovery := c.cli.String("swarm-standalone-discovery") if swarmDiscovery == "" { swarmDiscoveryToken, err := g5kswarm.GetNewSwarmDiscoveryToken() if err != nil { @@ -210,23 +206,28 @@ func (c *Command) checkCliParameters() error { } // set discovery token in CLI context - c.cli.Set("swarm-discovery", fmt.Sprintf("token://%s", swarmDiscoveryToken)) + c.cli.Set("swarm-standalone-discovery", fmt.Sprintf("token://%s", swarmDiscoveryToken)) log.Infof("New Swarm discovery token generated : '%s'", swarmDiscoveryToken) } // check Docker Swarm image - swarmImage := c.cli.String("swarm-image") + swarmImage := c.cli.String("swarm-standalone-image") if swarmImage == "" { return fmt.Errorf("You must provide a Swarm image") } // check Docker Swarm strategy - swarmStrategy := c.cli.String("swarm-strategy") + swarmStrategy := c.cli.String("swarm-standalone-strategy") if swarmStrategy == "" { return fmt.Errorf("You must provide a Swarm strategy") } + // block enabling Swarm mode and Swarm standalone at the same time + if c.cli.Bool("swarm-standalone-enable") && c.cli.Bool("swarm-mode-enable") { + return fmt.Errorf("You can't enable both swarm modes at the same time") + } + return nil } @@ -269,7 +270,7 @@ func (c *Command) CreateCluster() error { } // provision nodes - if err := c.ProvisionNodes(site, deployedNodes, jobID); err != nil { + if err := c.ProvisionNodes(client, site, deployedNodes, jobID); err != nil { return err } } diff --git a/libdockerg5k/swarm/mode.go b/libdockerg5k/swarm/mode.go index 511f4e2..180d887 100644 --- a/libdockerg5k/swarm/mode.go +++ b/libdockerg5k/swarm/mode.go @@ -2,14 +2,14 @@ package swarm import "github.com/docker/machine/libmachine/host" -// JoinMode defined join modes for Swarm +// JoinMode define the join modes for Swarm type JoinMode int const ( - // Manager makes the node join the cluster as Swarm Manager - Manager JoinMode = iota - // Worker makes the node join the cluster as Swarm Worker - Worker + // JoinAsManager makes the node join the cluster as Swarm Manager + JoinAsManager JoinMode = iota + // JoinAsWorker makes the node join the cluster as Swarm Worker + JoinAsWorker ) // InitSwarmModeCluster initialize a new Swarm mode cluster on the given host and returns the Manager/Worker join tokens @@ -23,6 +23,6 @@ func InitSwarmModeCluster(h *host.Host) (string, string, error) { } // JoinSwarmModecluster makes the host join a Swarm mode cluster as Manager or Worker -func JoinSwarmModecluster(h *host.Host, j JoinMode, t string) error { +func JoinSwarmModecluster(host *host.Host, joinMode JoinMode, joinToken string) error { return nil } diff --git a/main.go b/main.go index c5c42dc..9eaabc4 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,11 @@ var ( Value: "", }, + cli.StringSliceFlag{ + Name: "g5k-reserve-nodes", + Usage: "Reserve nodes on a site (ex: lille:24)", + }, + cli.StringFlag{ Name: "g5k-walltime", Usage: "Machine's lifetime (HH:MM:SS)", @@ -63,53 +68,53 @@ var ( }, cli.BoolFlag{ - Name: "swarm-enable", - Usage: "Enable Swarm (standalone) on the cluster", + Name: "swarm-mode-enable", + Usage: "Create a Swarm mode cluster", + }, + + cli.BoolFlag{ + Name: "swarm-standalone-enable", + Usage: "Create a Swarm standalone cluster", }, cli.StringFlag{ - Name: "swarm-discovery", + Name: "swarm-standalone-discovery", Usage: "Discovery service to use with Swarm", Value: "", }, cli.StringFlag{ - Name: "swarm-image", + Name: "swarm-standalone-image", Usage: "Specify Docker image to use for Swarm", Value: "swarm:latest", }, cli.StringFlag{ - Name: "swarm-strategy", + Name: "swarm-standalone-strategy", Usage: "Define a default scheduling strategy for Swarm", Value: "spread", }, cli.StringSliceFlag{ - Name: "swarm-opt", + Name: "swarm-standalone-opt", Usage: "Define arbitrary flags for Swarm master (can be provided multiple times)", Value: nil, }, cli.StringSliceFlag{ - Name: "swarm-join-opt", + Name: "swarm-standalone-join-opt", Usage: "Define arbitrary flags for Swarm join (can be provided multiple times)", Value: nil, }, cli.BoolFlag{ - Name: "swarm-master-join", + Name: "swarm-standalone-master-join", Usage: "Make Swarm master join the Swarm pool", }, cli.BoolFlag{ Name: "weave-networking", - Usage: "Use Weave for networking (Only if Swarm is enabled)", - }, - - cli.StringSliceFlag{ - Name: "g5k-reserve-nodes", - Usage: "Reserve nodes on a site (ex: lille:24)", + Usage: "Use Weave for networking (Only if Swarm standalone is enabled)", }, }, }, From e1ce835a606b6b840371ec1d7db893c5d7cd3b33 Mon Sep 17 00:00:00 2001 From: gfieni Date: Mon, 13 Mar 2017 16:48:14 +0100 Subject: [PATCH 15/38] feat(create-cluster): Adding support for jobs walltime extension. --- libdockerg5k/g5k/job.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libdockerg5k/g5k/job.go b/libdockerg5k/g5k/job.go index 8f0006d..850f65d 100644 --- a/libdockerg5k/g5k/job.go +++ b/libdockerg5k/g5k/job.go @@ -8,16 +8,10 @@ import ( // ReserveNodes allocate a new job with the required number of nodes on the given site, and returns the Job ID func (g *G5K) ReserveNodes(site string, nbNodes int, resourceProperties string, walltime string) (int, error) { - // convert walltime to seconds - seconds, err := api.ConvertDuration(walltime) - if err != nil { - return -1, err - } - // create a new job request with given parameters jobReq := api.JobRequest{ Resources: fmt.Sprintf("nodes=%v,walltime=%s", nbNodes, walltime), - Command: fmt.Sprintf("sleep %v", seconds), + Command: "sleep 365d", Properties: resourceProperties, Types: []string{"deploy"}, } From 0c7d53fa0e21c03f47e9ac07e77f20a323cab0de Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 14 Mar 2017 16:43:36 +0100 Subject: [PATCH 16/38] refactor(libdockerg5k/swarm): Moving configuration to structures instead of function arguments --- libdockerg5k/swarm/mode.go | 69 ++++++++++++++++++++++++-------- libdockerg5k/swarm/standalone.go | 36 +++++++++++++++-- 2 files changed, 86 insertions(+), 19 deletions(-) diff --git a/libdockerg5k/swarm/mode.go b/libdockerg5k/swarm/mode.go index 180d887..84c68b4 100644 --- a/libdockerg5k/swarm/mode.go +++ b/libdockerg5k/swarm/mode.go @@ -1,28 +1,65 @@ package swarm -import "github.com/docker/machine/libmachine/host" +import ( + "fmt" -// JoinMode define the join modes for Swarm -type JoinMode int - -const ( - // JoinAsManager makes the node join the cluster as Swarm Manager - JoinAsManager JoinMode = iota - // JoinAsWorker makes the node join the cluster as Swarm Worker - JoinAsWorker + "github.com/docker/machine/libmachine/host" ) +// SwarmModeGlobalConfig contain Swarm Mode global configuration +type SwarmModeGlobalConfig struct { + ManagerToken string + BootstrapManagerURL string + WorkerToken string +} + +// IsSwarmModeClusterInitialized returns true if Swarm mode cluster is initialized (Manager/Worker tokens set), and false otherwise +func (gc *SwarmModeGlobalConfig) IsSwarmModeClusterInitialized() bool { + return (gc.ManagerToken != "") || (gc.WorkerToken != "") +} + // InitSwarmModeCluster initialize a new Swarm mode cluster on the given host and returns the Manager/Worker join tokens -func InitSwarmModeCluster(h *host.Host) (string, string, error) { - // Launch Weave Net Router - if _, err := h.RunSSHCommand("docker swarm init"); err != nil { - return "", "", err +func (gc *SwarmModeGlobalConfig) InitSwarmModeCluster(h *host.Host) error { + // check if Swarm mode cluster is already initialized + if gc.IsSwarmModeClusterInitialized() { + return fmt.Errorf("The Swarm Mode cluster is already initialized") + } + + // init Swarm mode cluster + _, err := h.RunSSHCommand("docker swarm init") + if err != nil { + return err + } + + // get Manager join token + gc.ManagerToken, err = h.RunSSHCommand("docker swarm join-token -q manager") + if err != nil { + return err } - return "", "", nil + // get Worker join token + gc.WorkerToken, err = h.RunSSHCommand("docker swarm join-token -q worker") + if err != nil { + return err + } + + return nil } -// JoinSwarmModecluster makes the host join a Swarm mode cluster as Manager or Worker -func JoinSwarmModecluster(host *host.Host, joinMode JoinMode, joinToken string) error { +// JoinSwarmModeCluster makes the host join a Swarm mode cluster as Manager or Worker +func (gc *SwarmModeGlobalConfig) JoinSwarmModeCluster(host *host.Host, isManager bool) error { + // by default, join as Worker + token := gc.WorkerToken + + // if node is a manager, change the join token to Manager token + if isManager { + token = gc.ManagerToken + } + + // run swarm join command + if _, err := host.RunSSHCommand(fmt.Sprintf("docker swarm join --token %s %s", token, gc.BootstrapManagerURL)); err != nil { + return err + } + return nil } diff --git a/libdockerg5k/swarm/standalone.go b/libdockerg5k/swarm/standalone.go index f91b068..2ed60c8 100644 --- a/libdockerg5k/swarm/standalone.go +++ b/libdockerg5k/swarm/standalone.go @@ -1,9 +1,39 @@ package swarm -import "github.com/docker/swarm/discovery/token" +import ( + "github.com/docker/machine/libmachine/swarm" + "github.com/docker/swarm/discovery/token" +) -// GetNewSwarmDiscoveryToken get a new Docker Swarm discovery token from Docker Hub -func GetNewSwarmDiscoveryToken() (string, error) { +// SwarmStandaloneGlobalConfig contain Swarm standalone global configuration +type SwarmStandaloneGlobalConfig struct { + Image string + Discovery string + Strategy string + MasterFlags []string + JoinFlags []string + IsExperimental bool +} + +// CreateSwarmStandaloneNodeConfig returns a configured SwarmOptions for HostOptions struct +func (gc *SwarmStandaloneGlobalConfig) CreateSwarmStandaloneNodeConfig(nodeName string, isMaster bool, isWorker bool) *swarm.Options { + return &swarm.Options{ + IsSwarm: true, + Image: gc.Image, + Agent: isWorker, + Master: isMaster, + Discovery: gc.Discovery, + Address: nodeName, + Host: "tcp://0.0.0.0:3376", + Strategy: gc.Strategy, + ArbitraryFlags: gc.MasterFlags, + ArbitraryJoinFlags: gc.JoinFlags, + IsExperimental: gc.IsExperimental, + } +} + +// GetNewSwarmStandaloneDiscoveryToken get a new Docker Swarm discovery token from Docker Hub +func GetNewSwarmStandaloneDiscoveryToken() (string, error) { // init Discovery structure discovery := token.Discovery{} discovery.Initialize("token", 0, 0, nil) From 50679eaed6aa1e1697713620e579508bc59948e6 Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 14 Mar 2017 17:08:01 +0100 Subject: [PATCH 17/38] refactor(node): Moving all node provisionning parts to libdockerg5k. --- command/create_cluster.go | 2 +- libdockerg5k/node/privisionning.go | 115 +++++++++++++++++++++++++++++ libdockerg5k/swarm/mode.go | 2 +- 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 libdockerg5k/node/privisionning.go diff --git a/command/create_cluster.go b/command/create_cluster.go index cddbd73..7c85855 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -200,7 +200,7 @@ func (c *Command) checkCliParameters() error { // check Docker Swarm discovery swarmDiscovery := c.cli.String("swarm-standalone-discovery") if swarmDiscovery == "" { - swarmDiscoveryToken, err := g5kswarm.GetNewSwarmDiscoveryToken() + swarmDiscoveryToken, err := g5kswarm.GetNewSwarmStandaloneDiscoveryToken() if err != nil { return err } diff --git a/libdockerg5k/node/privisionning.go b/libdockerg5k/node/privisionning.go new file mode 100644 index 0000000..49b979b --- /dev/null +++ b/libdockerg5k/node/privisionning.go @@ -0,0 +1,115 @@ +package node + +import ( + "encoding/json" + "path/filepath" + + "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" + "github.com/Spirals-Team/docker-g5k/libdockerg5k/weave" + "github.com/Spirals-Team/docker-machine-driver-g5k/driver" + "github.com/docker/machine/commands/mcndirs" + "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/log" +) + +// NodeGlobalConfig contain needed information to provision a node +type NodeGlobalConfig struct { + // libMachine client + libMachineClient *libmachine.Client + + // g5k driver config (needed or Docker Machine will not work afterwards) + g5kUsername string + g5kPassword string + g5kSite string + g5kImage string + g5kWalltime string + g5kSSHPrivateKeyPath string + g5kSSHPublicKeyPath string + + // Swarm configuration + swarmStandaloneGlobalConfig *swarm.SwarmStandaloneGlobalConfig + swarmModeGlobalConfig *swarm.SwarmModeGlobalConfig + + // Weave networking + weaveNetworkingEnabled bool +} + +// createHostAuthOptions returns a configured AuthOptions for HostOptions struct +func createHostAuthOptions(machineName string) *auth.Options { + return &auth.Options{ + CertDir: mcndirs.GetMachineCertDir(), + CaCertPath: filepath.Join(mcndirs.GetMachineCertDir(), "ca.pem"), + CaPrivateKeyPath: filepath.Join(mcndirs.GetMachineCertDir(), "ca-key.pem"), + ClientCertPath: filepath.Join(mcndirs.GetMachineCertDir(), "cert.pem"), + ClientKeyPath: filepath.Join(mcndirs.GetMachineCertDir(), "key.pem"), + ServerCertPath: filepath.Join(mcndirs.GetMachineDir(), machineName, "server.pem"), + ServerKeyPath: filepath.Join(mcndirs.GetMachineDir(), machineName, "server-key.pem"), + StorePath: filepath.Join(mcndirs.GetMachineDir(), machineName), + ServerCertSANs: nil, + } +} + +// ProvisionNode will install Docker and configure Swarm (if enabled) on the node +func (gc *NodeGlobalConfig) ProvisionNode(g5kJobID int, nodeName string, machineName string, isMaster bool) error { + // create driver instance for libmachine + driver := driver.NewDriver() + + // set g5k driver parameters + driver.G5kUsername = gc.g5kUsername + driver.G5kPassword = gc.g5kPassword + driver.G5kSite = gc.g5kSite + driver.G5kImage = gc.g5kImage + driver.G5kWalltime = gc.g5kWalltime + driver.G5kSSHPrivateKeyPath = gc.g5kSSHPrivateKeyPath + driver.G5kSSHPublicKeyPath = gc.g5kSSHPublicKeyPath + driver.G5kJobID = g5kJobID + driver.G5kHostToProvision = nodeName + + // set base driver parameters + driver.BaseDriver.MachineName = machineName + driver.BaseDriver.StorePath = mcndirs.GetBaseDir() + driver.BaseDriver.SSHKeyPath = driver.GetSSHKeyPath() + + // marshal configured driver + data, err := json.Marshal(driver) + if err != nil { + return err + } + + // create a new host config + h, err := gc.libMachineClient.NewHost("g5k", data) + if err != nil { + return err + } + + // mandatory, or driver will use bad paths for certificates + h.HostOptions.AuthOptions = createHostAuthOptions(machineName) + + // set swarm options if Swarm standalone is enabled + if gc.swarmStandaloneGlobalConfig != nil { + h.HostOptions.SwarmOptions = gc.swarmStandaloneGlobalConfig.CreateSwarmStandaloneNodeConfig(nodeName, isMaster, true) + } + + // provision the new machine + if err := gc.libMachineClient.Create(h); err != nil { + return err + } + + // install and run Weave Net / Discovery if Weave networking mode and Swarm standalone are enabled + if gc.weaveNetworkingEnabled && (gc.swarmStandaloneGlobalConfig != nil) { + // run Weave Net + log.Info("Running Weave Net...") + if err := weave.RunWeaveNet(h); err != nil { + return err + } + + // run Weave Discovery + log.Info("Running Weave Discovery...") + if err := weave.RunWeaveDiscovery(h, gc.swarmStandaloneGlobalConfig.Discovery); err != nil { + return err + } + } + + return nil +} diff --git a/libdockerg5k/swarm/mode.go b/libdockerg5k/swarm/mode.go index 84c68b4..2b7f1f3 100644 --- a/libdockerg5k/swarm/mode.go +++ b/libdockerg5k/swarm/mode.go @@ -15,7 +15,7 @@ type SwarmModeGlobalConfig struct { // IsSwarmModeClusterInitialized returns true if Swarm mode cluster is initialized (Manager/Worker tokens set), and false otherwise func (gc *SwarmModeGlobalConfig) IsSwarmModeClusterInitialized() bool { - return (gc.ManagerToken != "") || (gc.WorkerToken != "") + return (gc.ManagerToken != "") && (gc.WorkerToken != "") } // InitSwarmModeCluster initialize a new Swarm mode cluster on the given host and returns the Manager/Worker join tokens From 091eb5f3fcb527e76b157a5e6f97cacdac2d168b Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 22 Mar 2017 13:36:37 +0100 Subject: [PATCH 18/38] refactor(create-cluster): Moving nodes reservations CLI flag parser in function --- command/create_cluster.go | 45 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 7c85855..4dd7b6e 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -231,6 +231,31 @@ func (c *Command) checkCliParameters() error { return nil } +func (c *Command) parseReserveNodesFlag() (map[string]int, error) { + reservations := make(map[string]int) + + for _, r := range c.cli.StringSlice("g5k-reserve-nodes") { + // extract site name and number of nodes to reserve + v := strings.Split(r, ":") + + // we only need 2 parameters : site and number of nodes + if len(v) != 2 { + return nil, fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", r) + } + + // convert nodes number to int + nb, err := strconv.Atoi(v[1]) + if err != nil { + return nil, err + } + + // store nodes to reserve for site + reservations[v[0]] = nb + } + + return reservations, nil +} + // CreateCluster create nodes in docker-machine func (c *Command) CreateCluster() error { // create libmachine client @@ -245,20 +270,18 @@ func (c *Command) CreateCluster() error { // create Grid5000 API client c.g5kAPI = g5k.Init(c.cli.String("g5k-username"), c.cli.String("g5k-password")) - // process nodes reservations - for _, r := range c.cli.StringSlice("g5k-reserve-nodes") { - // extract site name and number of nodes to reserve - v := strings.Split(r, ":") - site := v[0] - nbNodes, err := strconv.Atoi(v[1]) - if err != nil { - return err - } + // parse nodes reservations from CLI flag + reservations, err := c.parseReserveNodesFlag() + if err != nil { + return err + } - log.Infof("Reserving %d nodes on '%s' site...", nbNodes, site) + // process nodes reservations by sites + for site, nb := range reservations { + log.Infof("Reserving %d nodes on '%s' site...", nb, site) // reserve nodes - jobID, err := c.g5kAPI.ReserveNodes(site, nbNodes, c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) + jobID, err := c.g5kAPI.ReserveNodes(site, nb, c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) if err != nil { return err } From 97b4f6bdbdb515468c32a637dd8f587aa4eeb70f Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 22 Mar 2017 15:53:08 +0100 Subject: [PATCH 19/38] refactor(swarm/standalone): Moving Discovery token to a method. --- libdockerg5k/swarm/standalone.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libdockerg5k/swarm/standalone.go b/libdockerg5k/swarm/standalone.go index 2ed60c8..c08a2f0 100644 --- a/libdockerg5k/swarm/standalone.go +++ b/libdockerg5k/swarm/standalone.go @@ -1,6 +1,8 @@ package swarm import ( + "fmt" + "github.com/docker/machine/libmachine/swarm" "github.com/docker/swarm/discovery/token" ) @@ -15,8 +17,8 @@ type SwarmStandaloneGlobalConfig struct { IsExperimental bool } -// CreateSwarmStandaloneNodeConfig returns a configured SwarmOptions for HostOptions struct -func (gc *SwarmStandaloneGlobalConfig) CreateSwarmStandaloneNodeConfig(nodeName string, isMaster bool, isWorker bool) *swarm.Options { +// CreateNodeConfig returns a configured SwarmOptions for HostOptions struct +func (gc *SwarmStandaloneGlobalConfig) CreateNodeConfig(nodeName string, isMaster bool, isWorker bool) *swarm.Options { return &swarm.Options{ IsSwarm: true, Image: gc.Image, @@ -32,8 +34,8 @@ func (gc *SwarmStandaloneGlobalConfig) CreateSwarmStandaloneNodeConfig(nodeName } } -// GetNewSwarmStandaloneDiscoveryToken get a new Docker Swarm discovery token from Docker Hub -func GetNewSwarmStandaloneDiscoveryToken() (string, error) { +// GenerateDiscoveryToken generate a new Docker Swarm discovery token from Docker Hub +func (gc *SwarmStandaloneGlobalConfig) GenerateDiscoveryToken() error { // init Discovery structure discovery := token.Discovery{} discovery.Initialize("token", 0, 0, nil) @@ -41,8 +43,9 @@ func GetNewSwarmStandaloneDiscoveryToken() (string, error) { // get a new discovery token from Docker Hub swarmToken, err := discovery.CreateCluster() if err != nil { - return "", err + return fmt.Errorf("Error when generating new discovery token: %s", err.Error()) } - return swarmToken, nil + gc.Discovery = fmt.Sprintf("token://%s", swarmToken) + return nil } From bb7b7d397cb2bb4af6c6d58dbd5bdf7bea8ba6ef Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 13:15:44 +0100 Subject: [PATCH 20/38] refactor(CLI): Moving CLI commands/flags inside the related command file. --- command/commands.go | 47 ---------------- main.go | 127 ++------------------------------------------ 2 files changed, 4 insertions(+), 170 deletions(-) delete mode 100644 command/commands.go diff --git a/command/commands.go b/command/commands.go deleted file mode 100644 index e06c2b0..0000000 --- a/command/commands.go +++ /dev/null @@ -1,47 +0,0 @@ -package command - -import ( - "github.com/Spirals-Team/docker-g5k/libdockerg5k/g5k" - "github.com/codegangsta/cli" -) - -// Command struct contain common informations used between commands -type Command struct { - cli *cli.Context - g5kAPI *g5k.G5K -} - -// NewCommandContext verify mandatory parameters and returns a new CommandContext -func NewCommandContext(cmd *cli.Context) (*Command, error) { - return &Command{ - cli: cmd, - }, nil -} - -// RunCreateClusterCommand create a new cluster using parameters given in cli -func RunCreateClusterCommand(c *cli.Context) error { - cmd, err := NewCommandContext(c) - if err != nil { - return err - } - - if err := cmd.CreateCluster(); err != nil { - return err - } - - return nil -} - -// RunRemoveClusterCommand remove an existing cluster using parameters given in cli -func RunRemoveClusterCommand(c *cli.Context) error { - cmd, err := NewCommandContext(c) - if err != nil { - return err - } - - if err := cmd.RemoveCluster(); err != nil { - return err - } - - return nil -} diff --git a/main.go b/main.go index 9eaabc4..420241a 100644 --- a/main.go +++ b/main.go @@ -6,138 +6,19 @@ import ( "github.com/Spirals-Team/docker-g5k/command" "github.com/codegangsta/cli" "github.com/docker/machine/libmachine/log" - "github.com/docker/machine/libmachine/mcnutils" ) var ( // AppFlags stores cli flags for common parameters - AppFlags = []cli.Flag{} - - // CliCommands stores cli commands and their flags - CliCommands = []cli.Command{ - { - Name: "create-cluster", - Usage: "Create a new Docker Swarm cluster on Grid5000", - Action: command.RunCreateClusterCommand, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "g5k-username", - Usage: "Your Grid5000 account username", - Value: "", - }, - - cli.StringFlag{ - Name: "g5k-password", - Usage: "Your Grid5000 account password", - Value: "", - }, - - cli.StringSliceFlag{ - Name: "g5k-reserve-nodes", - Usage: "Reserve nodes on a site (ex: lille:24)", - }, - - cli.StringFlag{ - Name: "g5k-walltime", - Usage: "Machine's lifetime (HH:MM:SS)", - Value: "1:00:00", - }, - - cli.StringFlag{ - Name: "g5k-ssh-private-key", - Usage: "Path of your ssh private key", - Value: mcnutils.GetHomeDir() + "/.ssh/id_rsa", - }, - - cli.StringFlag{ - Name: "g5k-ssh-public-key", - Usage: "Path of your ssh public key (default: \".pub\")", - Value: "", - }, - - cli.StringFlag{ - Name: "g5k-image", - Usage: "Name of the image to deploy", - Value: "jessie-x64-min", - }, - - cli.StringFlag{ - Name: "g5k-resource-properties", - Usage: "Resource selection with OAR properties (SQL format)", - Value: "", - }, - - cli.BoolFlag{ - Name: "swarm-mode-enable", - Usage: "Create a Swarm mode cluster", - }, - - cli.BoolFlag{ - Name: "swarm-standalone-enable", - Usage: "Create a Swarm standalone cluster", - }, - - cli.StringFlag{ - Name: "swarm-standalone-discovery", - Usage: "Discovery service to use with Swarm", - Value: "", - }, - - cli.StringFlag{ - Name: "swarm-standalone-image", - Usage: "Specify Docker image to use for Swarm", - Value: "swarm:latest", - }, - - cli.StringFlag{ - Name: "swarm-standalone-strategy", - Usage: "Define a default scheduling strategy for Swarm", - Value: "spread", - }, - - cli.StringSliceFlag{ - Name: "swarm-standalone-opt", - Usage: "Define arbitrary flags for Swarm master (can be provided multiple times)", - Value: nil, - }, - - cli.StringSliceFlag{ - Name: "swarm-standalone-join-opt", - Usage: "Define arbitrary flags for Swarm join (can be provided multiple times)", - Value: nil, - }, - - cli.BoolFlag{ - Name: "swarm-standalone-master-join", - Usage: "Make Swarm master join the Swarm pool", - }, - - cli.BoolFlag{ - Name: "weave-networking", - Usage: "Use Weave for networking (Only if Swarm standalone is enabled)", - }, - }, - }, - { - Name: "remove-cluster", - Usage: "Remove a Docker Swarm cluster from Grid5000", - Action: command.RunRemoveClusterCommand, - Flags: []cli.Flag{ - cli.IntFlag{ - Name: "g5k-job-id", - Usage: "Only remove nodes related to the provided job ID (By default ALL nodes from ALL jobs will be removed)", - Value: -1, - }, - }, - }, - } + appFlags = []cli.Flag{} + cliCommands = []cli.Command{command.CreateClusterCliCommand, command.RemoveClusterCliCommand} ) func main() { app := cli.NewApp() - app.Flags = AppFlags - app.Commands = CliCommands + app.Flags = appFlags + app.Commands = cliCommands if err := app.Run(os.Args); err != nil { log.Error(err) From 2c121f7d941f25d86b131822a3761e69ae745253 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 13:18:26 +0100 Subject: [PATCH 21/38] feat(libdockerg5k): Deployment now take the public key in argument (in Authorized key format) --- libdockerg5k/g5k/deployment.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libdockerg5k/g5k/deployment.go b/libdockerg5k/g5k/deployment.go index 6b6219c..b8b64a1 100644 --- a/libdockerg5k/g5k/deployment.go +++ b/libdockerg5k/g5k/deployment.go @@ -1,19 +1,11 @@ package g5k import ( - "io/ioutil" - "github.com/Spirals-Team/docker-machine-driver-g5k/api" ) // DeployNodes submit a deployment request and returns the deployed nodes hostname -func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image string) ([]string, error) { - // reading ssh public key file - pubkey, err := ioutil.ReadFile(sshPublicKeyPath) - if err != nil { - return nil, err - } - +func (g *G5K) DeployNodes(site string, sshPublicKey string, jobID int, image string) ([]string, error) { // get required site API client siteAPI := g.getSiteAPI(site) @@ -27,7 +19,7 @@ func (g *G5K) DeployNodes(site string, sshPublicKeyPath string, jobID int, image deploymentReq := api.DeploymentRequest{ Nodes: job.Nodes, Environment: image, - Key: string(pubkey), + Key: sshPublicKey, } // deploy environment From 83bb9be6ff61e0b1cf2301bb0e72e977cdaa95a5 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 13:19:46 +0100 Subject: [PATCH 22/38] fix(libdockerg5k): Remove Swarm Standalone experimental flag support (possible undefined behavior) --- libdockerg5k/swarm/standalone.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libdockerg5k/swarm/standalone.go b/libdockerg5k/swarm/standalone.go index c08a2f0..fa99df8 100644 --- a/libdockerg5k/swarm/standalone.go +++ b/libdockerg5k/swarm/standalone.go @@ -9,12 +9,11 @@ import ( // SwarmStandaloneGlobalConfig contain Swarm standalone global configuration type SwarmStandaloneGlobalConfig struct { - Image string - Discovery string - Strategy string - MasterFlags []string - JoinFlags []string - IsExperimental bool + Image string + Discovery string + Strategy string + MasterFlags []string + JoinFlags []string } // CreateNodeConfig returns a configured SwarmOptions for HostOptions struct @@ -30,7 +29,7 @@ func (gc *SwarmStandaloneGlobalConfig) CreateNodeConfig(nodeName string, isMaste Strategy: gc.Strategy, ArbitraryFlags: gc.MasterFlags, ArbitraryJoinFlags: gc.JoinFlags, - IsExperimental: gc.IsExperimental, + IsExperimental: false, } } From 66987088ecac81107c770f0a411800610bdebec3 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 13:21:40 +0100 Subject: [PATCH 23/38] feat,refactor(libdockerg5k): Moving all node provisionning parts to libdockerg5k, Add support for Swarm Mode --- libdockerg5k/node/node.go | 65 ++++++++++++++++++++++++ libdockerg5k/node/privisionning.go | 79 +++++++++++++----------------- 2 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 libdockerg5k/node/node.go diff --git a/libdockerg5k/node/node.go b/libdockerg5k/node/node.go new file mode 100644 index 0000000..0d7e9be --- /dev/null +++ b/libdockerg5k/node/node.go @@ -0,0 +1,65 @@ +package node + +import ( + "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" + "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/ssh" +) + +// GlobalConfig contain global nodes configuration +type GlobalConfig struct { + // libMachine client + LibMachineClient *libmachine.Client + + // g5k driver config (needed or Docker Machine will not work afterwards) + G5kUsername string + G5kPassword string + G5kImage string + G5kWalltime string + SSHKeyPair *ssh.KeyPair + + // Swarm configuration + SwarmStandaloneGlobalConfig *swarm.SwarmStandaloneGlobalConfig + SwarmModeGlobalConfig *swarm.SwarmModeGlobalConfig + SwarmMasterNode map[string]bool + + // Weave networking + WeaveNetworkingEnabled bool +} + +// GenerateSSHKeyPair generate a new global SSH key pair +func (gc *GlobalConfig) GenerateSSHKeyPair() error { + sshKeyPair, err := ssh.NewKeyPair() + if err != nil { + return err + } + + gc.SSHKeyPair = sshKeyPair + return nil +} + +// Node contain node specific informations +type Node struct { + *GlobalConfig + + NodeName string // Grid'5000 node hostname + MachineName string // Docker Machine name + + // g5k driver + G5kSite string + G5kJobID int + + // Docker Engine + engineOpt []string + engineLabel []string +} + +// AddEngineOpt add an option to Docker Engine +func (n *Node) AddEngineOpt(opt string) { + n.engineOpt = append(n.engineOpt, opt) +} + +// AddEngineLabel add a label to Docker Engine +func (n *Node) AddEngineLabel(label string) { + n.engineLabel = append(n.engineLabel, label) +} diff --git a/libdockerg5k/node/privisionning.go b/libdockerg5k/node/privisionning.go index 49b979b..ea68215 100644 --- a/libdockerg5k/node/privisionning.go +++ b/libdockerg5k/node/privisionning.go @@ -4,37 +4,13 @@ import ( "encoding/json" "path/filepath" - "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" "github.com/Spirals-Team/docker-g5k/libdockerg5k/weave" - "github.com/Spirals-Team/docker-machine-driver-g5k/driver" + g5kdriver "github.com/Spirals-Team/docker-machine-driver-g5k/driver" "github.com/docker/machine/commands/mcndirs" - "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/log" ) -// NodeGlobalConfig contain needed information to provision a node -type NodeGlobalConfig struct { - // libMachine client - libMachineClient *libmachine.Client - - // g5k driver config (needed or Docker Machine will not work afterwards) - g5kUsername string - g5kPassword string - g5kSite string - g5kImage string - g5kWalltime string - g5kSSHPrivateKeyPath string - g5kSSHPublicKeyPath string - - // Swarm configuration - swarmStandaloneGlobalConfig *swarm.SwarmStandaloneGlobalConfig - swarmModeGlobalConfig *swarm.SwarmModeGlobalConfig - - // Weave networking - weaveNetworkingEnabled bool -} - // createHostAuthOptions returns a configured AuthOptions for HostOptions struct func createHostAuthOptions(machineName string) *auth.Options { return &auth.Options{ @@ -51,23 +27,22 @@ func createHostAuthOptions(machineName string) *auth.Options { } // ProvisionNode will install Docker and configure Swarm (if enabled) on the node -func (gc *NodeGlobalConfig) ProvisionNode(g5kJobID int, nodeName string, machineName string, isMaster bool) error { +func (n *Node) ProvisionNode() error { // create driver instance for libmachine - driver := driver.NewDriver() + driver := g5kdriver.NewDriver() // set g5k driver parameters - driver.G5kUsername = gc.g5kUsername - driver.G5kPassword = gc.g5kPassword - driver.G5kSite = gc.g5kSite - driver.G5kImage = gc.g5kImage - driver.G5kWalltime = gc.g5kWalltime - driver.G5kSSHPrivateKeyPath = gc.g5kSSHPrivateKeyPath - driver.G5kSSHPublicKeyPath = gc.g5kSSHPublicKeyPath - driver.G5kJobID = g5kJobID - driver.G5kHostToProvision = nodeName + driver.G5kUsername = n.GlobalConfig.G5kUsername + driver.G5kPassword = n.GlobalConfig.G5kPassword + driver.G5kSite = n.G5kSite + driver.G5kImage = n.GlobalConfig.G5kImage + driver.G5kWalltime = n.GlobalConfig.G5kWalltime + driver.G5kJobID = n.G5kJobID + driver.G5kHostToProvision = n.NodeName + driver.SSHKeyPair = n.GlobalConfig.SSHKeyPair // set base driver parameters - driver.BaseDriver.MachineName = machineName + driver.BaseDriver.MachineName = n.MachineName driver.BaseDriver.StorePath = mcndirs.GetBaseDir() driver.BaseDriver.SSHKeyPath = driver.GetSSHKeyPath() @@ -78,26 +53,26 @@ func (gc *NodeGlobalConfig) ProvisionNode(g5kJobID int, nodeName string, machine } // create a new host config - h, err := gc.libMachineClient.NewHost("g5k", data) + h, err := n.GlobalConfig.LibMachineClient.NewHost("g5k", data) if err != nil { return err } // mandatory, or driver will use bad paths for certificates - h.HostOptions.AuthOptions = createHostAuthOptions(machineName) + h.HostOptions.AuthOptions = createHostAuthOptions(n.MachineName) // set swarm options if Swarm standalone is enabled - if gc.swarmStandaloneGlobalConfig != nil { - h.HostOptions.SwarmOptions = gc.swarmStandaloneGlobalConfig.CreateSwarmStandaloneNodeConfig(nodeName, isMaster, true) + if n.GlobalConfig.SwarmStandaloneGlobalConfig != nil { + h.HostOptions.SwarmOptions = n.GlobalConfig.SwarmStandaloneGlobalConfig.CreateNodeConfig(n.NodeName, n.GlobalConfig.SwarmMasterNode[n.MachineName], true) } // provision the new machine - if err := gc.libMachineClient.Create(h); err != nil { + if err := n.GlobalConfig.LibMachineClient.Create(h); err != nil { return err } // install and run Weave Net / Discovery if Weave networking mode and Swarm standalone are enabled - if gc.weaveNetworkingEnabled && (gc.swarmStandaloneGlobalConfig != nil) { + if n.GlobalConfig.WeaveNetworkingEnabled && (n.GlobalConfig.SwarmStandaloneGlobalConfig != nil) { // run Weave Net log.Info("Running Weave Net...") if err := weave.RunWeaveNet(h); err != nil { @@ -106,10 +81,26 @@ func (gc *NodeGlobalConfig) ProvisionNode(g5kJobID int, nodeName string, machine // run Weave Discovery log.Info("Running Weave Discovery...") - if err := weave.RunWeaveDiscovery(h, gc.swarmStandaloneGlobalConfig.Discovery); err != nil { + if err := weave.RunWeaveDiscovery(h, n.GlobalConfig.SwarmStandaloneGlobalConfig.Discovery); err != nil { return err } } + // Swarm mode + if n.GlobalConfig.SwarmModeGlobalConfig != nil { + // check if cluster is already initialized + if !n.GlobalConfig.SwarmModeGlobalConfig.IsSwarmModeClusterInitialized() { + // initialize Swarm mode cluster (only for bootstrap node) + if err := n.GlobalConfig.SwarmModeGlobalConfig.InitSwarmModeCluster(h); err != nil { + return err + } + } else { + // join the Swarm mode cluster + if err := n.GlobalConfig.SwarmModeGlobalConfig.JoinSwarmModeCluster(h, n.GlobalConfig.SwarmMasterNode[n.MachineName]); err != nil { + return err + } + } + } + return nil } From 66d597caf4c84330511f781f6d083b0337fb71e7 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 13:22:46 +0100 Subject: [PATCH 24/38] refactor(remove-cluster): Adding related CLI command/flags from main --- command/remove_cluster.go | 52 ++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/command/remove_cluster.go b/command/remove_cluster.go index 7480c0f..e5abd73 100644 --- a/command/remove_cluster.go +++ b/command/remove_cluster.go @@ -3,6 +3,7 @@ package command import ( "encoding/json" + "github.com/codegangsta/cli" "github.com/docker/machine/commands/mcndirs" "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/log" @@ -11,8 +12,40 @@ import ( "github.com/Spirals-Team/docker-machine-driver-g5k/driver" ) +var ( + // RemoveClusterCliCommand represent the CLI command "remove-cluster" with its flags + RemoveClusterCliCommand = cli.Command{ + Name: "remove-cluster", + Usage: "Remove a Docker Swarm cluster from the Grid'5000 infrastructure", + Action: RunRemoveClusterCommand, + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "g5k-job-id", + Usage: "Only remove nodes related to the provided job ID (By default ALL nodes from ALL jobs will be removed)", + Value: -1, + }, + }, + } +) + +// RemoveClusterCommand contain global parameters for the command "rm-cluster" +type RemoveClusterCommand struct { + cli *cli.Context +} + +// getG5kDriverConfig takes a raw driver and return a configured Driver structure +func (c *RemoveClusterCommand) getG5kDriverConfig(rawDriver []byte) (*driver.Driver, error) { + // unmarshal driver configuration + var drv driver.Driver + if err := json.Unmarshal(rawDriver, &drv); err != nil { + return nil, err + } + + return &drv, nil +} + // RemoveCluster remove all nodes -func (c *Command) RemoveCluster() error { +func (c *RemoveClusterCommand) RemoveCluster() error { // create a new libmachine client client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) defer client.Close() @@ -30,7 +63,7 @@ func (c *Command) RemoveCluster() error { for _, h := range lst { // only remove Grid5000 nodes if h.DriverName == "g5k" { - driverConfig, err := getG5kDriverConfig(h.RawDriver) + driverConfig, err := c.getG5kDriverConfig(h.RawDriver) if err != nil { log.Errorf("Cannot remove node '%s' : %s", h.Name, err) } @@ -46,7 +79,7 @@ func (c *Command) RemoveCluster() error { // check the job is already in the list of deleted jobs if _, exist := jobs[driverConfig.G5kJobID]; !exist { // send API call to kill job - driverConfig.KillJob(driverConfig.G5kJobID) + driverConfig.G5kAPI.KillJob(driverConfig.G5kJobID) // add job ID to list of deleted jobs jobs[driverConfig.G5kJobID] = true @@ -64,13 +97,8 @@ func (c *Command) RemoveCluster() error { return nil } -// getG5kDriverConfig takes a raw driver and return a configured Driver structure -func getG5kDriverConfig(rawDriver []byte) (*driver.Driver, error) { - // unmarshal driver configuration - var drv driver.Driver - if err := json.Unmarshal(rawDriver, &drv); err != nil { - return nil, err - } - - return &drv, nil +// RunRemoveClusterCommand remove a cluster +func RunRemoveClusterCommand(cli *cli.Context) error { + c := RemoveClusterCommand{cli: cli} + return c.RemoveCluster() } From 320f258c1f042abea27bd5a7ceca5aa6bb858c5c Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 13:23:49 +0100 Subject: [PATCH 25/38] feat,refactor(create-cluster): Moving all nodes reservation, deployment, configuration and provisionning parts to libdockerg5k, Adding support for multi-sites cluster, Swarm mode, Advanced nodes configuration (flags, Swarm master) --- command/create_cluster.go | 500 +++++++++++++++++++++++--------------- 1 file changed, 302 insertions(+), 198 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 4dd7b6e..aeb33f5 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -1,171 +1,185 @@ package command import ( - "encoding/json" "fmt" - "os" - "path/filepath" + "strconv" + "strings" "sync" + "github.com/codegangsta/cli" "github.com/docker/machine/commands/mcndirs" "github.com/docker/machine/libmachine" - "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/log" - "github.com/docker/machine/libmachine/swarm" - - "github.com/Spirals-Team/docker-machine-driver-g5k/driver" - - "strings" - - "strconv" "github.com/Spirals-Team/docker-g5k/libdockerg5k/g5k" - g5kswarm "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" - "github.com/Spirals-Team/docker-g5k/libdockerg5k/weave" + "github.com/Spirals-Team/docker-g5k/libdockerg5k/node" + "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" ) -// createHostAuthOptions returns a configured AuthOptions for HostOptions struct -func (c *Command) createHostAuthOptions(machineName string) *auth.Options { - return &auth.Options{ - CertDir: mcndirs.GetMachineCertDir(), - CaCertPath: filepath.Join(mcndirs.GetMachineCertDir(), "ca.pem"), - CaPrivateKeyPath: filepath.Join(mcndirs.GetMachineCertDir(), "ca-key.pem"), - ClientCertPath: filepath.Join(mcndirs.GetMachineCertDir(), "cert.pem"), - ClientKeyPath: filepath.Join(mcndirs.GetMachineCertDir(), "key.pem"), - ServerCertPath: filepath.Join(mcndirs.GetMachineDir(), machineName, "server.pem"), - ServerKeyPath: filepath.Join(mcndirs.GetMachineDir(), machineName, "server-key.pem"), - StorePath: filepath.Join(mcndirs.GetMachineDir(), machineName), - ServerCertSANs: nil, - } -} - -// createHostSwarmOptions returns a configured SwarmOptions for HostOptions struct -func (c *Command) createHostSwarmOptions(nodeName string, isMaster bool) *swarm.Options { - runAgent := true - // By default, exclude master node from Swarm pool, but can be overrided by swarm-standalone-master-join flag - if isMaster && !c.cli.Bool("swarm-standalone-master-join") { - runAgent = false +var ( + // CreateClusterCliCommand represent the CLI command "create-cluster" with its flags + CreateClusterCliCommand = cli.Command{ + Name: "create-cluster", + Usage: "Create a new Docker Swarm cluster on the Grid'5000 infrastructure", + Action: RunCreateClusterCommand, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "g5k-username", + Usage: "Your Grid5000 account username", + Value: "", + }, + + cli.StringFlag{ + Name: "g5k-password", + Usage: "Your Grid5000 account password", + Value: "", + }, + + cli.StringSliceFlag{ + Name: "g5k-reserve-nodes", + Usage: "Reserve nodes on a site (ex: lille:24)", + }, + + cli.StringFlag{ + Name: "g5k-walltime", + Usage: "Machine's lifetime (HH:MM:SS)", + Value: "1:00:00", + }, + + cli.StringFlag{ + Name: "g5k-image", + Usage: "Name of the image to deploy", + Value: "jessie-x64-min", + }, + + cli.StringFlag{ + Name: "g5k-resource-properties", + Usage: "Resource selection with OAR properties (SQL format)", + Value: "", + }, + + cli.StringSliceFlag{ + Name: "engine-opt", + Usage: "Specify arbitrary flags to include on the selected node(s) engine (site-id:flag=value)", + }, + + cli.StringSliceFlag{ + Name: "engine-label", + Usage: "Specify labels for the selected node(s) engine (site-id:labelname=labelvalue)", + }, + + cli.StringSliceFlag{ + Name: "swarm-master", + Usage: "Select node(s) to be promoted to Swarm Master(standalone)/Manager(Mode)", + }, + + cli.BoolFlag{ + Name: "swarm-mode-enable", + Usage: "Create a Swarm mode cluster", + }, + + cli.BoolFlag{ + Name: "swarm-standalone-enable", + Usage: "Create a Swarm standalone cluster", + }, + + cli.StringFlag{ + Name: "swarm-standalone-discovery", + Usage: "Discovery service to use with Swarm", + Value: "", + }, + + cli.StringFlag{ + Name: "swarm-standalone-image", + Usage: "Specify Docker image to use for Swarm", + Value: "swarm:latest", + }, + + cli.StringFlag{ + Name: "swarm-standalone-strategy", + Usage: "Define a default scheduling strategy for Swarm", + Value: "spread", + }, + + cli.StringSliceFlag{ + Name: "swarm-standalone-opt", + Usage: "Define arbitrary flags for Swarm master (can be provided multiple times)", + }, + + cli.StringSliceFlag{ + Name: "swarm-standalone-join-opt", + Usage: "Define arbitrary flags for Swarm join (can be provided multiple times)", + }, + + cli.BoolFlag{ + Name: "weave-networking", + Usage: "Use Weave for networking (Only if Swarm standalone is enabled)", + }, + }, } +) - return &swarm.Options{ - IsSwarm: true, - Image: c.cli.String("swarm-standalone-image"), - Agent: runAgent, - Master: isMaster, - Discovery: c.cli.String("swarm-standalone-discovery"), - Address: nodeName, - Host: "tcp://0.0.0.0:3376", - Strategy: c.cli.String("swarm-standalone-strategy"), - ArbitraryFlags: c.cli.StringSlice("swarm-standalone-opt"), - ArbitraryJoinFlags: c.cli.StringSlice("swarm-standalone-join-opt"), - IsExperimental: false, - } +// CreateClusterCommand contain global parameters for the command "create-cluster" +type CreateClusterCommand struct { + cli *cli.Context + nodesReservation map[string]int + swarmMasterNodes map[string]bool + nodesGlobalConfig *node.GlobalConfig } -func (c *Command) provisionNode(libMachineClient *libmachine.Client, site string, nodeName string, machineName string, jobID int, isSwarmMaster bool) error { - // create driver instance for libmachine - driver := driver.NewDriver() - - // set g5k driver parameters - driver.G5kUsername = c.cli.String("g5k-username") - driver.G5kPassword = c.cli.String("g5k-password") - driver.G5kSite = site - - driver.G5kImage = c.cli.String("g5k-image") - driver.G5kWalltime = c.cli.String("g5k-walltime") - driver.G5kSSHPrivateKeyPath = c.cli.String("g5k-ssh-private-key") - driver.G5kSSHPublicKeyPath = c.cli.String("g5k-ssh-public-key") +// parseReserveNodesFlag parse the nodes reservation flag (site):(number of nodes) +func (c *CreateClusterCommand) parseReserveNodesFlag() error { + // initialize nodes reservation map + c.nodesReservation = make(map[string]int) - driver.G5kHostToProvision = nodeName - driver.G5kJobID = jobID + // TODO: Brace expansion - // set base driver parameters - driver.BaseDriver.MachineName = machineName - driver.BaseDriver.StorePath = mcndirs.GetBaseDir() - driver.BaseDriver.SSHKeyPath = driver.GetSSHKeyPath() - - // marshal configured driver - data, err := json.Marshal(driver) - if err != nil { - return err - } - - // create a new host config - h, err := libMachineClient.NewHost("g5k", data) - if err != nil { - return err - } - - // mandatory, or driver will use bad paths - h.HostOptions.AuthOptions = c.createHostAuthOptions(machineName) - - // set swarm options if Swarm standalone is enabled - if c.cli.Bool("swarm-standalone-enable") { - h.HostOptions.SwarmOptions = c.createHostSwarmOptions(nodeName, isSwarmMaster) - } - - // provision the new machine - if err := libMachineClient.Create(h); err != nil { - return err - } + for _, r := range c.cli.StringSlice("g5k-reserve-nodes") { + // extract site name and number of nodes to reserve + v := strings.Split(r, ":") - // install and run Weave Net / Discovery if Weave networking mode and Swarm are enabled - if c.cli.Bool("weave-networking") { - // run Weave Net - log.Info("Running Weave Net...") - if err := weave.RunWeaveNet(h); err != nil { - return err + // we only need 2 parameters : site and number of nodes + if len(v) != 2 { + return fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", r) } - // run Weave Discovery - log.Info("Running Weave Discovery...") - if err := weave.RunWeaveDiscovery(h, c.cli.String("swarm-standalone-discovery")); err != nil { - return err + // convert nodes number to int + nb, err := strconv.Atoi(v[1]) + if err != nil { + return fmt.Errorf("Error while converting number of nodes in reservation parameters: '%s'", r) } + + // store nodes to reserve for site + c.nodesReservation[v[0]] = nb } return nil } -// ProvisionNodes provision the nodes -func (c *Command) ProvisionNodes(libMachineClient *libmachine.Client, site string, nodes []string, jobID int) error { - // provision all deployed nodes - var wg sync.WaitGroup - for i, v := range nodes { - wg.Add(1) - go func(nodeID int, nodeName string) { - defer wg.Done() - - // compute Machine name - machineName := fmt.Sprintf("%s-%d", site, nodeID) - - // first node will be the swarm master - if nodeID == 0 { - c.provisionNode(libMachineClient, site, nodeName, machineName, jobID, true) - } else { - c.provisionNode(libMachineClient, site, nodeName, machineName, jobID, false) - } +// parseSwarmMasterFlag parse the Swarm Master flag (site)-(id) +func (c *CreateClusterCommand) parseSwarmMasterFlag() error { + // initialize Swarm masters map + c.swarmMasterNodes = make(map[string]bool) - }(i, v) - } + // TODO: Brace expansion - // wait nodes provisionning to finish - wg.Wait() + for _, n := range c.cli.StringSlice("swarm-master") { + // TODO: check if node exist (id too low/high) + c.swarmMasterNodes[n] = true + } return nil } // checkCliParameters perform checks on CLI parameters -func (c *Command) checkCliParameters() error { +func (c *CreateClusterCommand) checkCliParameters() error { // check username - g5kUsername := c.cli.String("g5k-username") - if g5kUsername == "" { + if c.cli.String("g5k-username") == "" { return fmt.Errorf("You must provide your Grid5000 account username") } // check password - g5kPassword := c.cli.String("g5k-password") - if g5kPassword == "" { + if c.cli.String("g5k-password") == "" { return fmt.Errorf("You must provide your Grid5000 account password") } @@ -174,128 +188,218 @@ func (c *Command) checkCliParameters() error { return fmt.Errorf("You must provide a site and the number of nodes to reserve on it") } - // check ssh private key - sshPrivKey := c.cli.String("g5k-ssh-private-key") - if sshPrivKey == "" { - return fmt.Errorf("You must provide your SSH private key path") + // parse nodes reservation + if err := c.parseReserveNodesFlag(); err != nil { + return err } - // check if private key file exist - if _, err := os.Stat(sshPrivKey); os.IsNotExist(err) { - return fmt.Errorf("Your ssh private key file does not exist in : '%s'", sshPrivKey) + // check walltime + if c.cli.String("g5k-image") == "" { + return fmt.Errorf("You must provide an image to deploy on the nodes") } - // check ssh public key, set it to '.pub' if not set - sshPubKey := c.cli.String("g5k-ssh-public-key") - if sshPubKey == "" { - c.cli.Set("g5k-ssh-public-key", fmt.Sprintf("%s.pub", sshPrivKey)) - sshPubKey = c.cli.String("g5k-ssh-public-key") + // check walltime + if c.cli.String("g5k-walltime") == "" { + return fmt.Errorf("You must provide a walltime") } - // check if public key file exist - if _, err := os.Stat(sshPubKey); os.IsNotExist(err) { - return fmt.Errorf("Your ssh public key file does not exist in : '%s'", sshPubKey) + // check Swarm Standalone parameters + if c.cli.Bool("swarm-standalone-enable") { + // check Docker Swarm image + if c.cli.String("swarm-standalone-image") == "" { + return fmt.Errorf("You must provide a Swarm image") + } + + // check Docker Swarm strategy + if c.cli.String("swarm-standalone-strategy") == "" { + return fmt.Errorf("You must provide a Swarm strategy") + } } - // check Docker Swarm discovery - swarmDiscovery := c.cli.String("swarm-standalone-discovery") - if swarmDiscovery == "" { - swarmDiscoveryToken, err := g5kswarm.GetNewSwarmStandaloneDiscoveryToken() - if err != nil { + // check Swarm Mode parameters + if c.cli.Bool("swarm-mode-enable") { + // block enabling Swarm mode and Swarm standalone at the same time + if c.cli.Bool("swarm-standalone-enable") { + return fmt.Errorf("You can't enable both swarm modes at the same time") + } + + // block enabling Weave Networking (unsupported with Swarm Mode) + if c.cli.Bool("weave-networking") { + return fmt.Errorf("You can't enable Weave networking with Swarm Mode (Only Swarm Standalone is supported)") + } + } + + // parse Swarm master flag only if Swarm is enabled + if c.cli.Bool("swarm-standalone-enable") || c.cli.Bool("swarm-mode-enable") { + if err := c.parseSwarmMasterFlag(); err != nil { return err } + } - // set discovery token in CLI context - c.cli.Set("swarm-standalone-discovery", fmt.Sprintf("token://%s", swarmDiscoveryToken)) + return nil +} - log.Infof("New Swarm discovery token generated : '%s'", swarmDiscoveryToken) +// generateNodesGlobalConfig generate a Nodes global configuration from CLI flags +func (c *CreateClusterCommand) generateNodesGlobalConfig() error { + // create nodes global configuration + gc := &node.GlobalConfig{ + LibMachineClient: libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()), + G5kUsername: c.cli.String("g5k-username"), + G5kPassword: c.cli.String("g5k-password"), + G5kImage: c.cli.String("g5k-image"), + G5kWalltime: c.cli.String("g5k-walltime"), + WeaveNetworkingEnabled: c.cli.Bool("weave-networking"), + SwarmMasterNode: c.swarmMasterNodes, } - // check Docker Swarm image - swarmImage := c.cli.String("swarm-standalone-image") - if swarmImage == "" { - return fmt.Errorf("You must provide a Swarm image") + // Swarm Standalone config + if c.cli.Bool("swarm-standalone-enable") { + // enable Swarm Standalone + gc.SwarmStandaloneGlobalConfig = &swarm.SwarmStandaloneGlobalConfig{ + Image: c.cli.String("swarm-standalone-image"), + Discovery: c.cli.String("swarm-standalone-discovery"), + Strategy: c.cli.String("swarm-standalone-strategy"), + MasterFlags: c.cli.StringSlice("swarm-standalone-opt"), + JoinFlags: c.cli.StringSlice("swarm-standalone-join-opt"), + } + + // check Swarm discovery + if c.cli.String("swarm-standalone-discovery") == "" { + // generate new discovery token from Docker Hub + if err := gc.SwarmStandaloneGlobalConfig.GenerateDiscoveryToken(); err != nil { + return err + } + + log.Infof("New Swarm discovery token generated : '%s'", gc.SwarmStandaloneGlobalConfig.Discovery) + } } - // check Docker Swarm strategy - swarmStrategy := c.cli.String("swarm-standalone-strategy") - if swarmStrategy == "" { - return fmt.Errorf("You must provide a Swarm strategy") + // enable Swarm Mode + if c.cli.Bool("swarm-mode-enable") { + gc.SwarmModeGlobalConfig = &swarm.SwarmModeGlobalConfig{} } - // block enabling Swarm mode and Swarm standalone at the same time - if c.cli.Bool("swarm-standalone-enable") && c.cli.Bool("swarm-mode-enable") { - return fmt.Errorf("You can't enable both swarm modes at the same time") + // generate SSH key pair + if err := gc.GenerateSSHKeyPair(); err != nil { + return err } + c.nodesGlobalConfig = gc return nil } -func (c *Command) parseReserveNodesFlag() (map[string]int, error) { - reservations := make(map[string]int) +// generateNodesConfig generate Nodes configuration from CLI flags +func (c *CreateClusterCommand) generateNodesConfig(site string, jobID int, deployedNodes []string) (map[string]node.Node, error) { + // stores all deployed nodes configuration + nodesConfig := make(map[string]node.Node) + + // create configuration for deployed nodes + for i, n := range deployedNodes { + // generate machine name : {site}-{id} + machineName := fmt.Sprintf("%s-%d", site, i) + + // store node configuration + nodesConfig[machineName] = node.Node{ + GlobalConfig: c.nodesGlobalConfig, + NodeName: n, + MachineName: machineName, + G5kSite: site, + G5kJobID: jobID, + } + } - for _, r := range c.cli.StringSlice("g5k-reserve-nodes") { - // extract site name and number of nodes to reserve - v := strings.Split(r, ":") + return nodesConfig, nil +} - // we only need 2 parameters : site and number of nodes - if len(v) != 2 { - return nil, fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", r) - } +// ProvisionNodes provision the nodes +func (c *CreateClusterCommand) provisionNodes(sites map[string]map[string]node.Node) error { + log.Info("Starting nodes provisionning...") - // convert nodes number to int - nb, err := strconv.Atoi(v[1]) - if err != nil { - return nil, err - } + // TODO: bootstrap node + /* + - get a Swarm master node + - provision the node + - remove the node from the list + */ - // store nodes to reserve for site - reservations[v[0]] = nb + // provision all deployed nodes + var wg sync.WaitGroup + for _, site := range sites { + for _, n := range site { + wg.Add(1) + go func(n node.Node) { + defer wg.Done() + n.ProvisionNode() + }(n) + } } - return reservations, nil + // wait nodes provisionning to finish + wg.Wait() + + return nil } // CreateCluster create nodes in docker-machine -func (c *Command) CreateCluster() error { - // create libmachine client - client := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) - defer client.Close() - - // check cli parameters - if err := c.checkCliParameters(); err != nil { - return err - } - +func (c *CreateClusterCommand) createCluster() error { // create Grid5000 API client - c.g5kAPI = g5k.Init(c.cli.String("g5k-username"), c.cli.String("g5k-password")) + g5kAPI := g5k.Init(c.cli.String("g5k-username"), c.cli.String("g5k-password")) - // parse nodes reservations from CLI flag - reservations, err := c.parseReserveNodesFlag() - if err != nil { + // generate node global configuration + if err := c.generateNodesGlobalConfig(); err != nil { return err } + defer c.nodesGlobalConfig.LibMachineClient.Close() + + // stores the deployed nodes configuration by sites + nodesConfigBySites := make(map[string]map[string]node.Node) // process nodes reservations by sites - for site, nb := range reservations { + for site, nb := range c.nodesReservation { log.Infof("Reserving %d nodes on '%s' site...", nb, site) // reserve nodes - jobID, err := c.g5kAPI.ReserveNodes(site, nb, c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) + jobID, err := g5kAPI.ReserveNodes(site, nb, c.cli.String("g5k-resource-properties"), c.cli.String("g5k-walltime")) if err != nil { return err } // deploy nodes - deployedNodes, err := c.g5kAPI.DeployNodes(site, c.cli.String("g5k-ssh-public-key"), jobID, c.cli.String("g5k-image")) + deployedNodes, err := g5kAPI.DeployNodes(site, string(c.nodesGlobalConfig.SSHKeyPair.PublicKey), jobID, c.cli.String("g5k-image")) if err != nil { return err } - // provision nodes - if err := c.ProvisionNodes(client, site, deployedNodes, jobID); err != nil { + // generate deployed nodes configuration + nodesConfig, err := c.generateNodesConfig(site, jobID, deployedNodes) + if err != nil { return err } + + // copy nodes configuration + nodesConfigBySites[site] = nodesConfig + } + + // provision deployed nodes + if err := c.provisionNodes(nodesConfigBySites); err != nil { + return err + } + + return nil +} + +// RunCreateClusterCommand create a new cluster using cli flags +func RunCreateClusterCommand(cli *cli.Context) error { + c := CreateClusterCommand{cli: cli} + + // check CLI parameters + if err := c.checkCliParameters(); err != nil { + return err + } + + // create the cluster + if err := c.createCluster(); err != nil { + return err } return nil From acaa25eebded4c2e7ad287912df5d4d2bbc989cd Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 24 Mar 2017 17:27:39 +0100 Subject: [PATCH 26/38] feat(create-cluster): Support Swarm bootstrap node --- command/create_cluster.go | 68 +++++++++++++------ .../{privisionning.go => provisionning.go} | 0 libdockerg5k/swarm/mode.go | 20 +++++- 3 files changed, 66 insertions(+), 22 deletions(-) rename libdockerg5k/node/{privisionning.go => provisionning.go} (100%) diff --git a/command/create_cluster.go b/command/create_cluster.go index aeb33f5..15c151a 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -311,27 +311,48 @@ func (c *CreateClusterCommand) generateNodesConfig(site string, jobID int, deplo return nodesConfig, nil } +// extractSiteFromNodeName returns the site and the node ID from its name (format: {siteName}-{nodeID}) +func (c *CreateClusterCommand) extractSiteNodeIDFromNodeName(nodeName string) (string, string, error) { + // extract site and ID + v := strings.Split(nodeName, "-") + + // a name is only composed of the site and ID + if len(v) != 2 { + return "", "", fmt.Errorf("Syntax error in node name '%s', it should be '{siteName}-{nodeID}'", nodeName) + } + + // return the site + return v[0], v[1], nil +} + // ProvisionNodes provision the nodes -func (c *CreateClusterCommand) provisionNodes(sites map[string]map[string]node.Node) error { +func (c *CreateClusterCommand) provisionNodes(deployedNodes *map[string]node.Node) error { log.Info("Starting nodes provisionning...") - // TODO: bootstrap node - /* - - get a Swarm master node - - provision the node - - remove the node from the list - */ + // Swarm bootstrap node : + // We need to deploy one Swarm Master before any other nodes to get the Manager/Worker tokens (Swarm Mode) + for k := range c.swarmMasterNodes { + // get a bootstrap node (random Swarm Master) + bootstrapNode := (*deployedNodes)[k] + log.Infof("Swarm bootstrap node is '%s' ('%s')", bootstrapNode.MachineName, bootstrapNode.NodeName) + + // provision the node + bootstrapNode.ProvisionNode() + + // remove the node from the list + delete(*deployedNodes, k) + + break + } // provision all deployed nodes var wg sync.WaitGroup - for _, site := range sites { - for _, n := range site { - wg.Add(1) - go func(n node.Node) { - defer wg.Done() - n.ProvisionNode() - }(n) - } + for _, n := range *deployedNodes { + wg.Add(1) + go func(n node.Node) { + defer wg.Done() + n.ProvisionNode() + }(n) } // wait nodes provisionning to finish @@ -340,6 +361,13 @@ func (c *CreateClusterCommand) provisionNodes(sites map[string]map[string]node.N return nil } +// appendSiteDeployedNodesConfig append the site deployed nodes config to the global nodes config +func (c *CreateClusterCommand) appendSiteDeployedNodesConfig(globalNodesConfig *map[string]node.Node, siteNodesConfig *map[string]node.Node) { + for k, v := range *siteNodesConfig { + (*globalNodesConfig)[k] = v + } +} + // CreateCluster create nodes in docker-machine func (c *CreateClusterCommand) createCluster() error { // create Grid5000 API client @@ -352,7 +380,7 @@ func (c *CreateClusterCommand) createCluster() error { defer c.nodesGlobalConfig.LibMachineClient.Close() // stores the deployed nodes configuration by sites - nodesConfigBySites := make(map[string]map[string]node.Node) + deployedNodesConfig := make(map[string]node.Node) // process nodes reservations by sites for site, nb := range c.nodesReservation { @@ -365,23 +393,23 @@ func (c *CreateClusterCommand) createCluster() error { } // deploy nodes - deployedNodes, err := g5kAPI.DeployNodes(site, string(c.nodesGlobalConfig.SSHKeyPair.PublicKey), jobID, c.cli.String("g5k-image")) + deployedNodesName, err := g5kAPI.DeployNodes(site, string(c.nodesGlobalConfig.SSHKeyPair.PublicKey), jobID, c.cli.String("g5k-image")) if err != nil { return err } // generate deployed nodes configuration - nodesConfig, err := c.generateNodesConfig(site, jobID, deployedNodes) + siteDeployedNodesConfig, err := c.generateNodesConfig(site, jobID, deployedNodesName) if err != nil { return err } // copy nodes configuration - nodesConfigBySites[site] = nodesConfig + c.appendSiteDeployedNodesConfig(&deployedNodesConfig, &siteDeployedNodesConfig) } // provision deployed nodes - if err := c.provisionNodes(nodesConfigBySites); err != nil { + if err := c.provisionNodes(&deployedNodesConfig); err != nil { return err } diff --git a/libdockerg5k/node/privisionning.go b/libdockerg5k/node/provisionning.go similarity index 100% rename from libdockerg5k/node/privisionning.go rename to libdockerg5k/node/provisionning.go diff --git a/libdockerg5k/swarm/mode.go b/libdockerg5k/swarm/mode.go index 2b7f1f3..e6821c2 100644 --- a/libdockerg5k/swarm/mode.go +++ b/libdockerg5k/swarm/mode.go @@ -2,6 +2,9 @@ package swarm import ( "fmt" + "net" + + "strings" "github.com/docker/machine/libmachine/host" ) @@ -32,17 +35,30 @@ func (gc *SwarmModeGlobalConfig) InitSwarmModeCluster(h *host.Host) error { } // get Manager join token - gc.ManagerToken, err = h.RunSSHCommand("docker swarm join-token -q manager") + managerToken, err := h.RunSSHCommand("docker swarm join-token -q manager") if err != nil { return err } // get Worker join token - gc.WorkerToken, err = h.RunSSHCommand("docker swarm join-token -q worker") + workerToken, err := h.RunSSHCommand("docker swarm join-token -q worker") if err != nil { return err } + // get IP address of the host + ip, err := h.Driver.GetIP() + if err != nil { + return err + } + + // remove spaces/new lines at the begining/end of the tokens + gc.ManagerToken = strings.TrimSpace(managerToken) + gc.WorkerToken = strings.TrimSpace(workerToken) + + // set this host as bootstrap Swarm Manager + gc.BootstrapManagerURL = fmt.Sprintf("%s", net.JoinHostPort(ip, "2377")) + return nil } From 8fee680b8b2f1f9ecbbb5f92bec0c734472910c1 Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 28 Mar 2017 15:54:29 +0200 Subject: [PATCH 27/38] feat(create-cluster): Support Brace expansion on "g5k-reserve-nodes" cli parameter --- command/create_cluster.go | 46 +++++++++++++++------------------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 15c151a..ec9c481 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -10,6 +10,7 @@ import ( "github.com/docker/machine/commands/mcndirs" "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/log" + "github.com/kujtimiihoxha/go-brace-expansion" "github.com/Spirals-Team/docker-g5k/libdockerg5k/g5k" "github.com/Spirals-Team/docker-g5k/libdockerg5k/node" @@ -132,25 +133,26 @@ func (c *CreateClusterCommand) parseReserveNodesFlag() error { // initialize nodes reservation map c.nodesReservation = make(map[string]int) - // TODO: Brace expansion + for _, paramValue := range c.cli.StringSlice("g5k-reserve-nodes") { + // brace expansion support + for _, r := range gobrex.Expand(paramValue) { + // extract site name and number of nodes to reserve + v := strings.Split(r, ":") - for _, r := range c.cli.StringSlice("g5k-reserve-nodes") { - // extract site name and number of nodes to reserve - v := strings.Split(r, ":") + // we only need 2 parameters : site and number of nodes + if len(v) != 2 { + return fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", r) + } - // we only need 2 parameters : site and number of nodes - if len(v) != 2 { - return fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", r) - } + // convert nodes number to int + nb, err := strconv.Atoi(v[1]) + if err != nil { + return fmt.Errorf("Error while converting number of nodes in reservation parameters: '%s'", r) + } - // convert nodes number to int - nb, err := strconv.Atoi(v[1]) - if err != nil { - return fmt.Errorf("Error while converting number of nodes in reservation parameters: '%s'", r) + // store nodes to reserve for site + c.nodesReservation[v[0]] = nb } - - // store nodes to reserve for site - c.nodesReservation[v[0]] = nb } return nil @@ -311,20 +313,6 @@ func (c *CreateClusterCommand) generateNodesConfig(site string, jobID int, deplo return nodesConfig, nil } -// extractSiteFromNodeName returns the site and the node ID from its name (format: {siteName}-{nodeID}) -func (c *CreateClusterCommand) extractSiteNodeIDFromNodeName(nodeName string) (string, string, error) { - // extract site and ID - v := strings.Split(nodeName, "-") - - // a name is only composed of the site and ID - if len(v) != 2 { - return "", "", fmt.Errorf("Syntax error in node name '%s', it should be '{siteName}-{nodeID}'", nodeName) - } - - // return the site - return v[0], v[1], nil -} - // ProvisionNodes provision the nodes func (c *CreateClusterCommand) provisionNodes(deployedNodes *map[string]node.Node) error { log.Info("Starting nodes provisionning...") From baa5f24d190a29e9cffb94afb83c49ca1641740c Mon Sep 17 00:00:00 2001 From: gfieni Date: Tue, 28 Mar 2017 15:58:29 +0200 Subject: [PATCH 28/38] feat(create-cluster): Support brace expansion on "swarm-master" cli parameter --- command/create_cluster.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index ec9c481..7eeace2 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -163,11 +163,12 @@ func (c *CreateClusterCommand) parseSwarmMasterFlag() error { // initialize Swarm masters map c.swarmMasterNodes = make(map[string]bool) - // TODO: Brace expansion - - for _, n := range c.cli.StringSlice("swarm-master") { - // TODO: check if node exist (id too low/high) - c.swarmMasterNodes[n] = true + for _, paramValue := range c.cli.StringSlice("swarm-master") { + // brace expansion support + for _, n := range gobrex.Expand(paramValue) { + // TODO: check if node exist (id too low/high) + c.swarmMasterNodes[n] = true + } } return nil From 3516036dc5a132bf619eacbff866686b351599ba Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 29 Mar 2017 16:47:07 +0200 Subject: [PATCH 29/38] feat(create-cluster): Adding cli flags to configure Docker Engine options and labels. --- command/create_cluster.go | 80 +++++++++++++++++++++++++++--- command/utils.go | 34 +++++++++++++ libdockerg5k/node/node.go | 14 +----- libdockerg5k/node/provisionning.go | 4 ++ 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 command/utils.go diff --git a/command/create_cluster.go b/command/create_cluster.go index 7eeace2..6d2b076 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -3,7 +3,6 @@ package command import ( "fmt" "strconv" - "strings" "sync" "github.com/codegangsta/cli" @@ -17,6 +16,17 @@ import ( "github.com/Spirals-Team/docker-g5k/libdockerg5k/swarm" ) +const ( + // regexNodeName match the site (nodeSite) and the ID (nodeID) of a node from its name (nodeName) + regexNodeName = "(?P(?P[[:alpha:]]+)-(?P[[:digit:]]+))" + + // regexReservation match the site (site) and the number of nodes (nbNodes) from a reservation + regexReservation = "^(?P[[:alpha:]]+):(?P[[:digit:]]+)$" + + // regexNodeParamFlag match the node site/ID and the parameter (param, paramName, paramValue) from a CLI flag using the format : {nodeName}:paramName=paramValue + regexNodeParamFlag = "^" + regexNodeName + ":(?P(?P[[:ascii:]]+)=(?P[[:ascii:]]+))$" +) + var ( // CreateClusterCliCommand represent the CLI command "create-cluster" with its flags CreateClusterCliCommand = cli.Command{ @@ -126,6 +136,8 @@ type CreateClusterCommand struct { nodesReservation map[string]int swarmMasterNodes map[string]bool nodesGlobalConfig *node.GlobalConfig + nodesEngineLabel map[string][]string + nodesEngineOpt map[string][]string } // parseReserveNodesFlag parse the nodes reservation flag (site):(number of nodes) @@ -137,21 +149,19 @@ func (c *CreateClusterCommand) parseReserveNodesFlag() error { // brace expansion support for _, r := range gobrex.Expand(paramValue) { // extract site name and number of nodes to reserve - v := strings.Split(r, ":") - - // we only need 2 parameters : site and number of nodes - if len(v) != 2 { - return fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", r) + v, err := ParseCliFlag(regexReservation, r) + if err != nil { + return fmt.Errorf("Syntax error in nodes reservation parameter: '%s'", paramValue) } // convert nodes number to int - nb, err := strconv.Atoi(v[1]) + nb, err := strconv.Atoi(v["nbNodes"]) if err != nil { return fmt.Errorf("Error while converting number of nodes in reservation parameters: '%s'", r) } // store nodes to reserve for site - c.nodesReservation[v[0]] = nb + c.nodesReservation[v["site"]] = nb } } @@ -174,6 +184,48 @@ func (c *CreateClusterCommand) parseSwarmMasterFlag() error { return nil } +// parseEngineOptFlag parse the nodes Engine Opt flag {site}-{id}:optname=optvalue +func (c *CreateClusterCommand) parseEngineOptFlag() error { + // initialize nodes Engine Opt map + c.nodesEngineOpt = make(map[string][]string) + + for _, paramValue := range c.cli.StringSlice("engine-opt") { + for _, f := range gobrex.Expand(paramValue) { + // extract node name and parameter + v, err := ParseCliFlag(regexNodeParamFlag, f) + if err != nil { + return fmt.Errorf("Syntax error in node Engine flag parameter: '%s'", paramValue) + } + + // append the parameter to the node's parameter list + c.nodesEngineOpt[v["nodeName"]] = append(c.nodesEngineOpt[v["nodeName"]], v["param"]) + } + } + + return nil +} + +// parseEngineLabelFlag parse the nodes Engine label flag {site}-{id}:flagname=flagvalue +func (c *CreateClusterCommand) parseEngineLabelFlag() error { + // initialize nodes Engine Label map + c.nodesEngineLabel = make(map[string][]string) + + for _, paramValue := range c.cli.StringSlice("engine-label") { + for _, f := range gobrex.Expand(paramValue) { + // extract node name and parameter + v, err := ParseCliFlag(regexNodeParamFlag, f) + if err != nil { + return fmt.Errorf("Syntax error in node Engine flag parameter: '%s'", paramValue) + } + + // append the label to the node's label list + c.nodesEngineLabel[v["nodeName"]] = append(c.nodesEngineLabel[v["nodeName"]], v["param"]) + } + } + + return nil +} + // checkCliParameters perform checks on CLI parameters func (c *CreateClusterCommand) checkCliParameters() error { // check username @@ -206,6 +258,16 @@ func (c *CreateClusterCommand) checkCliParameters() error { return fmt.Errorf("You must provide a walltime") } + // parse engine opt + if err := c.parseEngineOptFlag(); err != nil { + return err + } + + // parse engine label + if err := c.parseEngineLabelFlag(); err != nil { + return err + } + // check Swarm Standalone parameters if c.cli.Bool("swarm-standalone-enable") { // check Docker Swarm image @@ -308,6 +370,8 @@ func (c *CreateClusterCommand) generateNodesConfig(site string, jobID int, deplo MachineName: machineName, G5kSite: site, G5kJobID: jobID, + EngineOpt: c.nodesEngineOpt[machineName], + EngineLabel: c.nodesEngineLabel[machineName], } } diff --git a/command/utils.go b/command/utils.go new file mode 100644 index 0000000..acb0e49 --- /dev/null +++ b/command/utils.go @@ -0,0 +1,34 @@ +package command + +import ( + "fmt" + "regexp" +) + +// ParseCliFlag extract informations (using regex) from cli flags and returns a map (named capturing groups are required) +func ParseCliFlag(regex string, str string) (map[string]string, error) { + // compile the regex + re := regexp.MustCompile(regex) + + // get the capturing groups names + sn := re.SubexpNames() + if len(sn) == 0 { + return nil, fmt.Errorf("The use of named capturing groups is required") + } + + // extract informations from input string + m := re.FindStringSubmatch(str) + + // test if the number of extracted informations matches the number of capturing groups + if len(m) != len(sn) { + return nil, fmt.Errorf("Number of extracted informations don't match the number of capturing groups") + } + + // construct results map with capturing groups name and values + r := make(map[string]string) + for i, n := range sn { + r[n] = m[i] + } + + return r, nil +} diff --git a/libdockerg5k/node/node.go b/libdockerg5k/node/node.go index 0d7e9be..16a7ccd 100644 --- a/libdockerg5k/node/node.go +++ b/libdockerg5k/node/node.go @@ -50,16 +50,6 @@ type Node struct { G5kJobID int // Docker Engine - engineOpt []string - engineLabel []string -} - -// AddEngineOpt add an option to Docker Engine -func (n *Node) AddEngineOpt(opt string) { - n.engineOpt = append(n.engineOpt, opt) -} - -// AddEngineLabel add a label to Docker Engine -func (n *Node) AddEngineLabel(label string) { - n.engineLabel = append(n.engineLabel, label) + EngineOpt []string + EngineLabel []string } diff --git a/libdockerg5k/node/provisionning.go b/libdockerg5k/node/provisionning.go index ea68215..43ccecd 100644 --- a/libdockerg5k/node/provisionning.go +++ b/libdockerg5k/node/provisionning.go @@ -58,6 +58,10 @@ func (n *Node) ProvisionNode() error { return err } + // set Docker Engine parameters + h.HostOptions.EngineOptions.ArbitraryFlags = n.EngineOpt + h.HostOptions.EngineOptions.Labels = n.EngineLabel + // mandatory, or driver will use bad paths for certificates h.HostOptions.AuthOptions = createHostAuthOptions(n.MachineName) From 0ed5afd214f687b43ba90919fc0dd2ab910f58f2 Mon Sep 17 00:00:00 2001 From: gfieni Date: Wed, 29 Mar 2017 17:06:20 +0200 Subject: [PATCH 30/38] feat(commands): Allow cli flags to be set using environment variables --- command/create_cluster.go | 101 ++++++++++++++++++++++---------------- command/remove_cluster.go | 7 +-- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 6d2b076..4b7a834 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -35,96 +35,113 @@ var ( Action: RunCreateClusterCommand, Flags: []cli.Flag{ cli.StringFlag{ - Name: "g5k-username", - Usage: "Your Grid5000 account username", - Value: "", + EnvVar: "G5K_USERNAME", + Name: "g5k-username", + Usage: "Your Grid5000 account username", + Value: "", }, cli.StringFlag{ - Name: "g5k-password", - Usage: "Your Grid5000 account password", - Value: "", + EnvVar: "G5K_PASSWORD", + Name: "g5k-password", + Usage: "Your Grid5000 account password", + Value: "", }, cli.StringSliceFlag{ - Name: "g5k-reserve-nodes", - Usage: "Reserve nodes on a site (ex: lille:24)", + EnvVar: "G5K_RESERVE_NODES", + Name: "g5k-reserve-nodes", + Usage: "Reserve nodes on a site (ex: lille:24)", }, cli.StringFlag{ - Name: "g5k-walltime", - Usage: "Machine's lifetime (HH:MM:SS)", - Value: "1:00:00", + EnvVar: "G5K_WALLTIME", + Name: "g5k-walltime", + Usage: "Machine's lifetime (HH:MM:SS)", + Value: "1:00:00", }, cli.StringFlag{ - Name: "g5k-image", - Usage: "Name of the image to deploy", - Value: "jessie-x64-min", + EnvVar: "G5K_IMAGE", + Name: "g5k-image", + Usage: "Name of the image to deploy", + Value: "jessie-x64-min", }, cli.StringFlag{ - Name: "g5k-resource-properties", - Usage: "Resource selection with OAR properties (SQL format)", - Value: "", + EnvVar: "G5K_RESOURCE_PROPERTIES", + Name: "g5k-resource-properties", + Usage: "Resource selection with OAR properties (SQL format)", + Value: "", }, cli.StringSliceFlag{ - Name: "engine-opt", - Usage: "Specify arbitrary flags to include on the selected node(s) engine (site-id:flag=value)", + EnvVar: "ENGINE_OPT", + Name: "engine-opt", + Usage: "Specify arbitrary flags to include on the selected node(s) engine (site-id:flag=value)", }, cli.StringSliceFlag{ - Name: "engine-label", - Usage: "Specify labels for the selected node(s) engine (site-id:labelname=labelvalue)", + EnvVar: "ENGINE_LABEL", + Name: "engine-label", + Usage: "Specify labels for the selected node(s) engine (site-id:labelname=labelvalue)", }, cli.StringSliceFlag{ - Name: "swarm-master", - Usage: "Select node(s) to be promoted to Swarm Master(standalone)/Manager(Mode)", + EnvVar: "SWARM_MASTER", + Name: "swarm-master", + Usage: "Select node(s) to be promoted to Swarm Master(standalone)/Manager(Mode)", }, cli.BoolFlag{ - Name: "swarm-mode-enable", - Usage: "Create a Swarm mode cluster", + EnvVar: "SWARM_MODE_ENABLE", + Name: "swarm-mode-enable", + Usage: "Create a Swarm mode cluster", }, cli.BoolFlag{ - Name: "swarm-standalone-enable", - Usage: "Create a Swarm standalone cluster", + EnvVar: "SWARM_STANDALONE_ENABLE", + Name: "swarm-standalone-enable", + Usage: "Create a Swarm standalone cluster", }, cli.StringFlag{ - Name: "swarm-standalone-discovery", - Usage: "Discovery service to use with Swarm", - Value: "", + EnvVar: "SWARM_STANDALONE_DISCOVERY", + Name: "swarm-standalone-discovery", + Usage: "Discovery service to use with Swarm", + Value: "", }, cli.StringFlag{ - Name: "swarm-standalone-image", - Usage: "Specify Docker image to use for Swarm", - Value: "swarm:latest", + EnvVar: "SWARM_STANDALONE_IMAGE", + Name: "swarm-standalone-image", + Usage: "Specify Docker image to use for Swarm", + Value: "swarm:latest", }, cli.StringFlag{ - Name: "swarm-standalone-strategy", - Usage: "Define a default scheduling strategy for Swarm", - Value: "spread", + EnvVar: "SWARM_STANDALONE_STRATEGY", + Name: "swarm-standalone-strategy", + Usage: "Define a default scheduling strategy for Swarm", + Value: "spread", }, cli.StringSliceFlag{ - Name: "swarm-standalone-opt", - Usage: "Define arbitrary flags for Swarm master (can be provided multiple times)", + EnvVar: "SWARM_STANDALONE_OPT", + Name: "swarm-standalone-opt", + Usage: "Define arbitrary flags for Swarm master (can be provided multiple times)", }, cli.StringSliceFlag{ - Name: "swarm-standalone-join-opt", - Usage: "Define arbitrary flags for Swarm join (can be provided multiple times)", + EnvVar: "SWARM_STANDALONE_JOIN_OPT", + Name: "swarm-standalone-join-opt", + Usage: "Define arbitrary flags for Swarm join (can be provided multiple times)", }, cli.BoolFlag{ - Name: "weave-networking", - Usage: "Use Weave for networking (Only if Swarm standalone is enabled)", + EnvVar: "WEAVE_NETWORKING", + Name: "weave-networking", + Usage: "Use Weave for networking (Only if Swarm standalone is enabled)", }, }, } diff --git a/command/remove_cluster.go b/command/remove_cluster.go index e5abd73..73d3a0d 100644 --- a/command/remove_cluster.go +++ b/command/remove_cluster.go @@ -20,9 +20,10 @@ var ( Action: RunRemoveClusterCommand, Flags: []cli.Flag{ cli.IntFlag{ - Name: "g5k-job-id", - Usage: "Only remove nodes related to the provided job ID (By default ALL nodes from ALL jobs will be removed)", - Value: -1, + EnvVar: "G5K_JOB_ID", + Name: "g5k-job-id", + Usage: "Only remove nodes related to the provided job ID (By default ALL nodes from ALL jobs will be removed)", + Value: -1, }, }, } From 9302331eefecdbc846c54a5704e3d16829f35146 Mon Sep 17 00:00:00 2001 From: gfieni Date: Thu, 30 Mar 2017 14:43:18 +0200 Subject: [PATCH 31/38] docs(README): Add new features informations/examples, update cli flags usage --- README.md | 119 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 3a1bb61..8a638a5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A tool to create a Docker Swarm cluster for Docker Machine on the Grid5000 testb ## Requirements * [Docker](https://www.docker.com/products/overview#/install_the_platform) * [Docker Machine](https://docs.docker.com/machine/install-machine) -* [Docker Machine Driver for Grid5000](https://github.com/Spirals-Team/docker-machine-driver-g5k) +* [Docker Machine Driver for Grid5000 (v1.4.1+)](https://github.com/Spirals-Team/docker-machine-driver-g5k) * [Go tools (Only for installation from sources)](https://golang.org/doc/install) You need a Grid5000 account to use this tool. See [this page](https://www.grid5000.fr/mediawiki/index.php/Grid5000:Get_an_account) to create an account. @@ -45,76 +45,117 @@ Please follow the instructions from the [Grid5000 Wiki](https://www.grid5000.fr/ ### Command line flags +All flags can be set using environment variables, the name is the same as the flag in uppercase and replacing hyphens by underscores. +For example, the env variable for the flag `--g5k-resource-properties` is `G5K_RESOURCE_PROPERTIES`. + +Flags marked with `[]` can be set multiple times and the values will be added. + +Flags marked with `{}` support brace expansions (same format as sh/bash shells) to generate combinations. +For example, in the flag `--g5k-reserve-nodes` you can use it to reserve the same number of nodes on multiple sites : `{lille,nantes}:16`, +or select a range of nodes like in the `--swarm-master` flag : `lille-{0..2}`. + #### Cluster creation flags -| Option | Description | Default value | Required | -|------------------------------|---------------------------------------------------------|-----------------------|------------| -| `--g5k-username` | Your Grid5000 account username | | Yes | -| `--g5k-password` | Your Grid5000 account password | | Yes | -| `--g5k-reserve-nodes` | Reserve nodes on a site (ex: lille:24) | | Yes | -| `--g5k-walltime` | Timelife of the machine | "1:00:00" | No | -| `--g5k-ssh-private-key` | Path of your ssh private key | "~/.ssh/id_rsa" | No | -| `--g5k-ssh-public-key` | Path of your ssh public key | "< private-key >.pub" | No | -| `--g5k-image` | Name of the image to deploy | "jessie-x64-min" | No | -| `--g5k-resource-properties` | Resource selection with OAR properties (SQL format) | | No | -| `--swarm-enable` | Enable Swarm (standalone) on the cluster | False | No | -| `--swarm-discovery` | Discovery service to use with Swarm | Generate a new token | No | -| `--swarm-image` | Specify Docker image to use for Swarm | "swarm:latest" | No | -| `--swarm-strategy` | Define a default scheduling strategy for Swarm | "spread" | No | -| `--swarm-opt` | Define arbitrary flags for Swarm master | | No | -| `--swarm-join-opt` | Define arbitrary flags for Swarm join | | No | -| `--swarm-master-join` | Make Swarm master join the Swarm pool | False | No | -| `--weave-networking` | Use Weave for networking (Only if Swarm is enabled) | False | No | +| Option | Description | Default value | Required | +|--------------------------------|---------------------------------------------------------|-----------------------|------------| +| `--g5k-username` | Your Grid5000 account username | | Yes | +| `--g5k-password` | Your Grid5000 account password | | Yes | +| `--g5k-reserve-nodes` [ ],{ } | Reserve nodes on a site | | Yes | +| `--g5k-walltime` | Timelife of the machine | "1:00:00" | No | +| `--g5k-image` | Name of the image to deploy | "jessie-x64-min" | No | +| `--g5k-resource-properties` | Resource selection with OAR properties (SQL format) | | No | +| `--engine-opt` [ ],{ } | Specify flags to include on the selected node(s) engine | | No | +| `--engine-label` [ ],{ } | Specify labels for the selected node(s) engine | | No | +| `--swarm-master` [ ],{ } | Select node(s) to be promoted to Swarm Master | | No | +| `--swarm-mode-enable` | Create a Swarm mode cluster | | No | +| `--swarm-standalone-enable` | Create a Swarm standalone cluster | | No | +| `--swarm-standalone-discovery` | Discovery service to use with Swarm | Generate a new token | No | +| `--swarm-standalone-image` | Specify Docker image to use for Swarm | "swarm:latest" | No | +| `--swarm-standalone-strategy` | Define a default scheduling strategy for Swarm | "spread" | No | +| `--swarm-standalone-opt` | Define arbitrary global flags for Swarm master | | No | +| `--swarm-standalone-join-opt` | Define arbitrary global flags for Swarm join | | No | +| `--weave-networking` | Use Weave for networking (Only with Swarm standalone) | | No | + +Engine flags `--engine-*` format is `node-name:key=val` and brace expansions are supported. +For example, `lille-0:mykey=myval`, `lille-{0..5}:mykey=myval`. + +For `--engine-opt` flag, please refer to [Docker documentation](https://docs.docker.com/engine/reference/commandline/dockerd/) for supported parameters. +**Test your parameters on a single node before deploying a cluster ! If your flags are incorrect, Docker wont start and you should redeploy the entire cluster !** #### Cluster deletion flags -| Option | Description | Default value | Required | -|------------------------------|---------------------------------------------------------|-----------------------|------------| -| `--g5k-job-id` | Only remove nodes related to the provided job ID | | No | +| Option | Description | Default value | Required | +|--------------------------------|---------------------------------------------------------|-----------------------|------------| +| `--g5k-job-id` [ ] | Only remove nodes related to the provided job ID | "1:00:00" | No | ### Examples #### Cluster creation -An example of a 6 nodes Docker reservation. There is only Docker installed, and Swarm is not configured. +An example of a 16 nodes Docker reservation (Only Docker, Swarm is not configured): +```bash +docker-g5k create-cluster \ +--g5k-username "user" \ +--g5k-password "********" \ +--g5k-reserve-nodes "lille:16" \ +``` + +An example of a 16 nodes Docker reservation with Engine options and labels: +```bash +docker-g5k create-cluster \ +--g5k-username "user" \ +--g5k-password "********" \ +--g5k-reserve-nodes "lille:16" \ +--engine-opt "lille-{0..15}:graph=/tmp" \ +--engine-label "lille-0:mylabelname1=mylabelvalue1" \ +--engine-label "lille-{1..5}:mylabelname2=mylabelvalue2" +``` + +An example of a 16 nodes Docker reservation using resource properties (nodes with more thant 8GB of RAM and at least 4 CPU cores): ```bash docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ ---g5k-reserve-nodes "lille:6" \ ---g5k-ssh-private-key ~/.ssh/g5k-key +--g5k-reserve-nodes "lille:16" \ +--g5k-resource-properties "memnode > 8192 and cpucore >= 4" ``` -An example of a 3 nodes Docker Swarm cluster creation: +An example of multi-sites cluster creation: ```bash docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ ---g5k-reserve-nodes "lille:3" \ ---g5k-ssh-private-key ~/.ssh/g5k-key \ ---swarm-enable +--g5k-reserve-nodes "lille:16" \ +--g5k-reserve-nodes "nantes:8" ``` -An example where 3 nodes join an existing Docker Swarm cluster using a discovery token: +An example of multi-sites cluster creation using brace expansion: ```bash docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ ---g5k-reserve-nodes "lille:3" \ ---swarm-discovery "token://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ ---g5k-ssh-private-key ~/.ssh/g5k-key \ ---swarm-enable +--g5k-reserve-nodes "{lille,nantes}:16" +``` + +An example of a 16 nodes Docker Swarm mode cluster creation using the first 3 nodes as Swarm Master: +```bash +docker-g5k create-cluster \ +--g5k-username "user" \ +--g5k-password "********" \ +--g5k-reserve-nodes "lille:16" \ +--swarm-mode-enable \ +--swarm-master "lille-{0..2}" ``` -An example of a 16 nodes Docker Swarm cluster creation with resource properties (nodes in cluster `chimint` with more thant 8GB of RAM and at least 4 CPU cores): +An example of a 16 nodes Docker Swarm standalone cluster creation using the first node as Swarm Master and Weave Networking: ```bash docker-g5k create-cluster \ --g5k-username "user" \ --g5k-password "********" \ --g5k-reserve-nodes "lille:16" \ ---g5k-ssh-private-key ~/.ssh/g5k-key \ ---swarm-enable \ ---g5k-resource-properties "cluster = 'chimint' and memnode > 8192 and cpucore >= 4" +--swarm-standalone-enable \ +--swarm-master "lille-0" \ +--weave-networking ``` #### Cluster deletion @@ -139,7 +180,7 @@ docker-machine ls **If you remove a node with Docker Machine 'rm' command, the job will be deleted and ALL nodes related to this job will become unavailable** -### Use with Weave networking +### Use with Weave networking (Only with Swarm standalone) First, you need to configure your Docker client to use the Swarm mode (You can get the Swarm master hostname with 'docker-machine ls'): ```bash From acaae62b92d252b301d3fc1a795badf4c1c684b0 Mon Sep 17 00:00:00 2001 From: gfieni Date: Thu, 30 Mar 2017 17:09:37 +0200 Subject: [PATCH 32/38] fix(command/utils): Fix check named capturing groups usage --- command/utils.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/command/utils.go b/command/utils.go index acb0e49..2cbeb17 100644 --- a/command/utils.go +++ b/command/utils.go @@ -12,9 +12,6 @@ func ParseCliFlag(regex string, str string) (map[string]string, error) { // get the capturing groups names sn := re.SubexpNames() - if len(sn) == 0 { - return nil, fmt.Errorf("The use of named capturing groups is required") - } // extract informations from input string m := re.FindStringSubmatch(str) @@ -30,5 +27,10 @@ func ParseCliFlag(regex string, str string) (map[string]string, error) { r[n] = m[i] } + // test if named capturing groups were used (otherwise there is only one element in the map) + if len(r) != len(sn) { + return nil, fmt.Errorf("The use of named capturing groups is required") + } + return r, nil } From 13e98ceae804730f8ec7a964333505c75dd52b5a Mon Sep 17 00:00:00 2001 From: gfieni Date: Thu, 30 Mar 2017 17:10:22 +0200 Subject: [PATCH 33/38] test(command/utils): Add tests for basic usage of ParseCliFlag function --- command/utils_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 command/utils_test.go diff --git a/command/utils_test.go b/command/utils_test.go new file mode 100644 index 0000000..c45527b --- /dev/null +++ b/command/utils_test.go @@ -0,0 +1,41 @@ +package command + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + testString = "res1,42./test/val-res_test4" + expectedResult = map[string]string{ + "": testString, + "test1": "res1", + "test2": "42", + "test3": "/test/val", + "test4": "res_test4", + } +) + +func TestParseCliFlagRegexWithoutNamedCapturingGroup(t *testing.T) { + regex := "([[:alnum:]]+),([[:digit:]]+).([[:ascii:]]+)-([[:word:]]+)" + _, err := ParseCliFlag(regex, testString) + + assert.Error(t, err) +} + +func TestParseCliFlagRegexWithNamedCapturingGroupCorrect(t *testing.T) { + regex := "(?P[[:alnum:]]+),(?P[[:digit:]]+).(?P[[:ascii:]]+)-(?P[[:word:]]+)" + res, err := ParseCliFlag(regex, testString) + + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(res, expectedResult)) +} + +func TestParseCliFlagRegexWithNamedCapturingGroupEmpty(t *testing.T) { + regex := "(?P[[:alnum:]]+),(?P[[:digit:]]+).(?P[[:ascii:]]+)-(?P[[:word:]]+)" + _, err := ParseCliFlag(regex, "") + + assert.Error(t, err) +} From 759538b36e15f5522d6eba6ff0e039d8420e13b3 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 31 Mar 2017 09:39:51 +0200 Subject: [PATCH 34/38] chores(Travis CI): Update Travis config for tests Add cli flag for go get to download packages required to build tests. Add verbose flag to go test. Enable tests ONLY for Linux because other OS throws an "exec format error" due to the job running on a Linux container. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bfd7ca0..e3c2f0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,11 @@ env: - GIMME_OS=windows GIMME_ARCH=386 install: - - go get -d -v ./... + - go get -d -t -v ./... script: - - go test ./... + # Only run tests on Linux + - if [ $BUILD_GOOS == "linux" ] ; then go test -v ./... ; fi - go build -v -o docker-g5k-$GIMME_OS-$GIMME_ARCH deploy: From 9ebc310e4909032a18db4f5cf27f8a7141d19a78 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 31 Mar 2017 12:08:09 +0200 Subject: [PATCH 35/38] doc(README): Update CLI flags structure, add Travis CI build status image --- README.md | 100 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 8a638a5..2c5da0e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/Spirals-Team/docker-g5k.svg)](https://travis-ci.org/Spirals-Team/docker-g5k) + # docker-g5k A tool to create a Docker Swarm cluster for Docker Machine on the Grid5000 testbed infrastructure. @@ -44,49 +46,71 @@ Do not forget to configure your DNS or use OpenVPN DNS auto-configuration. Please follow the instructions from the [Grid5000 Wiki](https://www.grid5000.fr/mediawiki/index.php/VPN). ### Command line flags - -All flags can be set using environment variables, the name is the same as the flag in uppercase and replacing hyphens by underscores. -For example, the env variable for the flag `--g5k-resource-properties` is `G5K_RESOURCE_PROPERTIES`. - -Flags marked with `[]` can be set multiple times and the values will be added. - -Flags marked with `{}` support brace expansions (same format as sh/bash shells) to generate combinations. -For example, in the flag `--g5k-reserve-nodes` you can use it to reserve the same number of nodes on multiple sites : `{lille,nantes}:16`, -or select a range of nodes like in the `--swarm-master` flag : `lille-{0..2}`. - -#### Cluster creation flags - -| Option | Description | Default value | Required | -|--------------------------------|---------------------------------------------------------|-----------------------|------------| -| `--g5k-username` | Your Grid5000 account username | | Yes | -| `--g5k-password` | Your Grid5000 account password | | Yes | -| `--g5k-reserve-nodes` [ ],{ } | Reserve nodes on a site | | Yes | -| `--g5k-walltime` | Timelife of the machine | "1:00:00" | No | -| `--g5k-image` | Name of the image to deploy | "jessie-x64-min" | No | -| `--g5k-resource-properties` | Resource selection with OAR properties (SQL format) | | No | -| `--engine-opt` [ ],{ } | Specify flags to include on the selected node(s) engine | | No | -| `--engine-label` [ ],{ } | Specify labels for the selected node(s) engine | | No | -| `--swarm-master` [ ],{ } | Select node(s) to be promoted to Swarm Master | | No | -| `--swarm-mode-enable` | Create a Swarm mode cluster | | No | -| `--swarm-standalone-enable` | Create a Swarm standalone cluster | | No | -| `--swarm-standalone-discovery` | Discovery service to use with Swarm | Generate a new token | No | -| `--swarm-standalone-image` | Specify Docker image to use for Swarm | "swarm:latest" | No | -| `--swarm-standalone-strategy` | Define a default scheduling strategy for Swarm | "spread" | No | -| `--swarm-standalone-opt` | Define arbitrary global flags for Swarm master | | No | -| `--swarm-standalone-join-opt` | Define arbitrary global flags for Swarm join | | No | -| `--weave-networking` | Use Weave for networking (Only with Swarm standalone) | | No | - -Engine flags `--engine-*` format is `node-name:key=val` and brace expansions are supported. -For example, `lille-0:mykey=myval`, `lille-{0..5}:mykey=myval`. +Flags marked with `[ ]` can be given multiple times and the values will be added. + +Flags marked with `{ }` support brace expansion (same format as sh/bash shells) to generate combinations. +Please refer to the flags format in "Flags usage" section of the command. + +#### For `create-cluster` command + +##### Flags description +* **`--g5k-username` : Your Grid5000 account username (required)** +* **`--g5k-password` : Your Grid5000 account password (required)** +* **`--g5k-reserve-nodes` : Reserve nodes on a site (required)** +* `--g5k-walltime` : Timelife of the nodes (format: "hh:mm:ss") +* `--g5k-image` : Name of the image to deploy on the nodes +* `--g5k-resource-properties` : Resource selection with OAR properties (SQL format) +* `--engine-opt` : Specify flags to include on the selected node(s) engine +* `--engine-label` : Specify labels for the selected node(s) engine +* `--swarm-master` : Select node(s) to be promoted to Swarm Master +* `--swarm-mode-enable` : Create a Swarm mode cluster +* `--swarm-standalone-enable` : Create a Swarm standalone cluster +* `--swarm-standalone-discovery` : Discovery service to use with Swarm +* `--swarm-standalone-image` : Specify the Docker image to use for Swarm +* `--swarm-standalone-strategy` : Define a default scheduling strategy for Swarm +* `--swarm-standalone-opt` : Define arbitrary global flags for Swarm master +* `--swarm-standalone-join-opt` : Define arbitrary global flags for Swarm join +* `--weave-networking` : Use Weave for networking (Only with Swarm standalone) + +##### Flags usage +| Option | Environment | Default value | { } | [ ] | +|--------------------------------|------------------------------|-----------------------|-----|-----| +| `--g5k-username` | `G5K_USERNAME` | | No | No | +| `--g5k-password` | `G5K_PASSWORD` | | No | No | +| `--g5k-reserve-nodes` | `G5K_RESERVE_NODES` | | Yes | Yes | +| `--g5k-walltime` | `G5K_WALLTIME` | "1:00:00" | No | No | +| `--g5k-image` | `G5K_IMAGE` | "jessie-x64-min" | No | No | +| `--g5k-resource-properties` | `G5K_RESOURCE_PROPERTIES` | | No | No | +| `--engine-opt` | `ENGINE_OPT` | | Yes | Yes | +| `--engine-label` | `ENGINE_LABEL` | | Yes | Yes | +| `--swarm-master` | `SWARM_MASTER` | | Yes | Yes | +| `--swarm-mode-enable` | `SWARM_MODE_ENABME` | | No | No | +| `--swarm-standalone-enable` | `SWARM_STANDALONE_ENABLE` | | No | No | +| `--swarm-standalone-discovery` | `SWARM_STANDALONE_DISCOVERY` | New token | No | No | +| `--swarm-standalone-image` | `SWARM_STANDALONE_IMAGE` | "swarm:latest" | No | No | +| `--swarm-standalone-strategy` | `SWARM_STANDALONE_STRATEGY` | "spread" | No | No | +| `--swarm-standalone-opt` | `SWARM_STANDALONE_OPT` | | No | Yes | +| `--swarm-standalone-join-opt` | `SWARM_STANDALONE_JOIN_OPT` | | No | Yes | +| `--weave-networking` | `WEAVE_NETWORKING` | | No | No | + +Flag `--g5k-reserve-nodes` format is `site:numberOfNodes` and brace expansion are supported. +For example, `lille-16`, `{lille,nantes}-16`. + +Engine flags `--engine-*` format is `node-name:key=val` and brace expansion are supported. +For example, `lille-0:mykey=myval`, `lille-{0..5}:mykey=myval`, `lille-{0,2,4}:mykey=myval`. For `--engine-opt` flag, please refer to [Docker documentation](https://docs.docker.com/engine/reference/commandline/dockerd/) for supported parameters. **Test your parameters on a single node before deploying a cluster ! If your flags are incorrect, Docker wont start and you should redeploy the entire cluster !** -#### Cluster deletion flags +#### For `remove-cluster` command + +##### Flags description +* `--g5k-job-id` : Only remove nodes related to the provided job ID -| Option | Description | Default value | Required | -|--------------------------------|---------------------------------------------------------|-----------------------|------------| -| `--g5k-job-id` [ ] | Only remove nodes related to the provided job ID | "1:00:00" | No | +##### Flags usage +| Option | Environment | Default value | { } | [ ] | +|--------------------------------|------------------------------|-----------------------|-----|-----| +| `--g5k-job-id` | `G5K_JOB_ID` | | No | Yes | ### Examples From 2632e609927ce75c192b3a69e85103d70b1151b4 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 31 Mar 2017 14:54:28 +0200 Subject: [PATCH 36/38] refactor(create-cluster): Moving direct access of cli context to function argument for cli parameter parsing functions. --- command/create_cluster.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index 4b7a834..bf5beb8 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -158,11 +158,11 @@ type CreateClusterCommand struct { } // parseReserveNodesFlag parse the nodes reservation flag (site):(number of nodes) -func (c *CreateClusterCommand) parseReserveNodesFlag() error { +func (c *CreateClusterCommand) parseReserveNodesFlag(flag []string) error { // initialize nodes reservation map c.nodesReservation = make(map[string]int) - for _, paramValue := range c.cli.StringSlice("g5k-reserve-nodes") { + for _, paramValue := range flag { // brace expansion support for _, r := range gobrex.Expand(paramValue) { // extract site name and number of nodes to reserve @@ -186,11 +186,11 @@ func (c *CreateClusterCommand) parseReserveNodesFlag() error { } // parseSwarmMasterFlag parse the Swarm Master flag (site)-(id) -func (c *CreateClusterCommand) parseSwarmMasterFlag() error { +func (c *CreateClusterCommand) parseSwarmMasterFlag(flag []string) error { // initialize Swarm masters map c.swarmMasterNodes = make(map[string]bool) - for _, paramValue := range c.cli.StringSlice("swarm-master") { + for _, paramValue := range flag { // brace expansion support for _, n := range gobrex.Expand(paramValue) { // TODO: check if node exist (id too low/high) @@ -202,11 +202,11 @@ func (c *CreateClusterCommand) parseSwarmMasterFlag() error { } // parseEngineOptFlag parse the nodes Engine Opt flag {site}-{id}:optname=optvalue -func (c *CreateClusterCommand) parseEngineOptFlag() error { +func (c *CreateClusterCommand) parseEngineOptFlag(flag []string) error { // initialize nodes Engine Opt map c.nodesEngineOpt = make(map[string][]string) - for _, paramValue := range c.cli.StringSlice("engine-opt") { + for _, paramValue := range flag { for _, f := range gobrex.Expand(paramValue) { // extract node name and parameter v, err := ParseCliFlag(regexNodeParamFlag, f) @@ -223,11 +223,11 @@ func (c *CreateClusterCommand) parseEngineOptFlag() error { } // parseEngineLabelFlag parse the nodes Engine label flag {site}-{id}:flagname=flagvalue -func (c *CreateClusterCommand) parseEngineLabelFlag() error { +func (c *CreateClusterCommand) parseEngineLabelFlag(flag []string) error { // initialize nodes Engine Label map c.nodesEngineLabel = make(map[string][]string) - for _, paramValue := range c.cli.StringSlice("engine-label") { + for _, paramValue := range flag { for _, f := range gobrex.Expand(paramValue) { // extract node name and parameter v, err := ParseCliFlag(regexNodeParamFlag, f) @@ -261,7 +261,7 @@ func (c *CreateClusterCommand) checkCliParameters() error { } // parse nodes reservation - if err := c.parseReserveNodesFlag(); err != nil { + if err := c.parseReserveNodesFlag(c.cli.StringSlice("g5k-reserve-nodes")); err != nil { return err } @@ -276,12 +276,12 @@ func (c *CreateClusterCommand) checkCliParameters() error { } // parse engine opt - if err := c.parseEngineOptFlag(); err != nil { + if err := c.parseEngineOptFlag(c.cli.StringSlice("engine-opt")); err != nil { return err } // parse engine label - if err := c.parseEngineLabelFlag(); err != nil { + if err := c.parseEngineLabelFlag(c.cli.StringSlice("engine-label")); err != nil { return err } @@ -313,7 +313,7 @@ func (c *CreateClusterCommand) checkCliParameters() error { // parse Swarm master flag only if Swarm is enabled if c.cli.Bool("swarm-standalone-enable") || c.cli.Bool("swarm-mode-enable") { - if err := c.parseSwarmMasterFlag(); err != nil { + if err := c.parseSwarmMasterFlag(c.cli.StringSlice("swarm-master")); err != nil { return err } } From 6518e60ebc750742856f808a36a798fabe8d7762 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 31 Mar 2017 15:23:14 +0200 Subject: [PATCH 37/38] fix(create-cluster): Add check if a Swarm master is defined --- command/create_cluster.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/command/create_cluster.go b/command/create_cluster.go index bf5beb8..705c833 100644 --- a/command/create_cluster.go +++ b/command/create_cluster.go @@ -193,8 +193,13 @@ func (c *CreateClusterCommand) parseSwarmMasterFlag(flag []string) error { for _, paramValue := range flag { // brace expansion support for _, n := range gobrex.Expand(paramValue) { - // TODO: check if node exist (id too low/high) - c.swarmMasterNodes[n] = true + // extract site and node ID + v, err := ParseCliFlag(regexNodeName, n) + if err != nil { + return fmt.Errorf("Syntax error in Swarm master parameter: '%s'", paramValue) + } + + c.swarmMasterNodes[v["nodeName"]] = true } } @@ -313,6 +318,12 @@ func (c *CreateClusterCommand) checkCliParameters() error { // parse Swarm master flag only if Swarm is enabled if c.cli.Bool("swarm-standalone-enable") || c.cli.Bool("swarm-mode-enable") { + // check if a Swarm master is defined + if len(c.cli.StringSlice("swarm-master")) == 0 { + return fmt.Errorf("You need to select a node to be Swarm master") + } + + // parse Swarm master flag if err := c.parseSwarmMasterFlag(c.cli.StringSlice("swarm-master")); err != nil { return err } From 15a0fa2f9280a341c847c997ef5dfefa0ba6adb1 Mon Sep 17 00:00:00 2001 From: gfieni Date: Fri, 31 Mar 2017 15:39:05 +0200 Subject: [PATCH 38/38] test(create-cluster): Add tests for cli parsing functions of create-cluster command --- command/create_cluster_test.go | 172 +++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 command/create_cluster_test.go diff --git a/command/create_cluster_test.go b/command/create_cluster_test.go new file mode 100644 index 0000000..a9646f5 --- /dev/null +++ b/command/create_cluster_test.go @@ -0,0 +1,172 @@ +package command + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test ParseReserveNodes flag +func TestParseReserveNodesFlagEmpty(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseReserveNodesFlag([]string{}) + assert.NoError(t, err) +} + +func TestParseReserveNodesFlagIncorrectSiteName(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseReserveNodesFlag([]string{"site1:10"}) + assert.Error(t, err) +} + +func TestParseReserveNodesFlagIncorrectFormatSingleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseReserveNodesFlag([]string{"test=30"}) + assert.Error(t, err) +} + +func TestParseReserveNodesFlagIncorrectFormatMultipleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseReserveNodesFlag([]string{"test:30", "incorrect=20", "testt:10"}) + assert.Error(t, err) +} + +func TestParseReserveNodesFlagCorrectFormatSingleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseReserveNodesFlag([]string{"test:10"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesReservation, map[string]int{"test": 10})) +} + +func TestParseReserveNodesFlagCorrectFormatMultipleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseReserveNodesFlag([]string{"test:10", "testt:20", "testtt:30"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesReservation, map[string]int{"test": 10, "testt": 20, "testtt": 30})) +} + +// Test ParseSwarmMaster flag +func TestParseSwarmMasterFlagEmpty(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseSwarmMasterFlag([]string{}) + assert.NoError(t, err) +} + +func TestParseSwarmMasterFlagIncorrectSingleNodeName(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseSwarmMasterFlag([]string{"test"}) + assert.Error(t, err) +} + +func TestParseSwarmMasterFlagIncorrectMultipleNodeName(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseSwarmMasterFlag([]string{"test-1", "incorrect", "test-2"}) + assert.Error(t, err) +} + +func TestParseSwarmMasterFlagCorrectSingleNodeName(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseSwarmMasterFlag([]string{"test-1"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.swarmMasterNodes, map[string]bool{"test-1": true})) +} + +func TestParseSwarmMasterFlagCorrectMultipleNodeName(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseSwarmMasterFlag([]string{"test-1", "test-2", "test-3"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.swarmMasterNodes, map[string]bool{"test-1": true, "test-2": true, "test-3": true})) +} + +// Test ParseEngineOpt flag +func TestParseEngineOptFlagEmpty(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineOptFlag([]string{}) + assert.NoError(t, err) +} + +func TestParseEngineOptFlagIncorrectNodeNameSingleOpt(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineOptFlag([]string{"incorrect:key=val"}) + assert.Error(t, err) +} + +func TestParseEngineOptFlagIncorrectNodeNameMultipleOpt(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineOptFlag([]string{"site-1:key=val", "incorrect:key=val"}) + assert.Error(t, err) +} + +func TestParseEngineOptFlagCorrectNodeNameSingleOpt(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineOptFlag([]string{"site-1:key=val"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesEngineOpt, map[string][]string{"site-1": []string{"key=val"}})) +} + +func TestParseEngineOptFlagCorrectNodeNameMultipleOptSingleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineOptFlag([]string{"site-1:key=val", "site-2:key=val"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesEngineOpt, map[string][]string{ + "site-1": []string{"key=val"}, + "site-2": []string{"key=val"}, + })) +} + +func TestParseEngineOptFlagCorrectNodeNameMultipleOptMultipleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineOptFlag([]string{"site-1:key1=val1", "site-1:key2=val2", "site-2:key=val"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesEngineOpt, map[string][]string{ + "site-1": []string{"key1=val1", "key2=val2"}, + "site-2": []string{"key=val"}, + })) +} + +// Test ParseEngineLabel flag +func TestParseEngineLabelFlagEmpty(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineLabelFlag([]string{}) + assert.NoError(t, err) +} + +func TestParseEngineLabelFlagIncorrectNodeNameSingleOpt(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineLabelFlag([]string{"incorrect:key=val"}) + assert.Error(t, err) +} + +func TestParseEngineLabelFlagIncorrectNodeNameMultipleOpt(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineLabelFlag([]string{"site-1:key=val", "incorrect:key=val"}) + assert.Error(t, err) +} + +func TestParseEngineLabelFlagCorrectNodeNameSingleOpt(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineLabelFlag([]string{"site-1:key=val"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesEngineLabel, map[string][]string{"site-1": []string{"key=val"}})) +} + +func TestParseEngineLabelFlagCorrectNodeNameMultipleOptSingleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineLabelFlag([]string{"site-1:key=val", "site-2:key=val"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesEngineLabel, map[string][]string{ + "site-1": []string{"key=val"}, + "site-2": []string{"key=val"}, + })) +} + +func TestParseEngineLabelFlagCorrectNodeNameMultipleOptMultipleValue(t *testing.T) { + c := CreateClusterCommand{} + err := c.parseEngineLabelFlag([]string{"site-1:key1=val1", "site-1:key2=val2", "site-2:key=val"}) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(c.nodesEngineLabel, map[string][]string{ + "site-1": []string{"key1=val1", "key2=val2"}, + "site-2": []string{"key=val"}, + })) +}