Skip to content

Commit

Permalink
Merge pull request #13 from ggiamarchi/discovery
Browse files Browse the repository at this point in the history
Discovery
  • Loading branch information
ggiamarchi authored Feb 26, 2018
2 parents 73b2236 + 7da13cb commit 0d07b83
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 51 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ hosts:
username: "user"
password: "pass"
interface: "lanplus"
subnets: "10.0.0.0/24"
- name: h3
mac_addresses: ["00:00:00:00:00:03", "00:00:00:00:00:33"]

Expand Down Expand Up @@ -231,6 +232,21 @@ Code | Name | Description
`20O` | `Ok` | Host list had been retrieved


## Reboot a host

```
PATCH /v1/hosts/<name>/reboot
```

###### Response codes

Code | Name | Description
-------|---------------|---------------------------------------------------
`204` | `No Content` | Host had been successfully rebooted
`404` | `Not Found` | Host does not exist
`409` | `Conflict` | Reboot did not succeed for any reason


## Deploy a configuration for host(s)

```
Expand Down Expand Up @@ -285,6 +301,22 @@ Code | Name | Description
`404` | `Not found` | Either the configuation or a host is not found
`400` | `Bad request` | Malformed body


## Discover hosts over the network

This API populate the ARP table for all subnets in the PXE Pilot configuration

```
PATCH /v1/discovery
```

###### Response codes

Code | Name | Description
-------|---------------|---------------------------------------------------
`204` | `No Content` | Discovery operation completed without any issue


# License

Everything in this repository is published under the MIT license.
14 changes: 9 additions & 5 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,17 @@ func api(appConfig *model.AppConfig) *gin.Engine {
api := gin.New()
api.Use(logger.APILogger(), gin.Recovery())

healthcheck(api, appConfig)
v1 := api.Group("/v1")

readConfigurations(api, appConfig)
deployConfiguration(api, appConfig)
healthcheck(v1, appConfig)

readHosts(api, appConfig)
rebootHost(api, appConfig)
readConfigurations(v1, appConfig)
deployConfiguration(v1, appConfig)

readHosts(v1, appConfig)
rebootHost(v1, appConfig)

discovery(v1, appConfig)

return api
}
4 changes: 2 additions & 2 deletions api/configurations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (
"gopkg.in/gin-gonic/gin.v1"
)

func readConfigurations(api *gin.Engine, appConfig *model.AppConfig) {
func readConfigurations(api *gin.RouterGroup, appConfig *model.AppConfig) {
api.GET("/configurations", func(c *gin.Context) {
configurations := service.ReadConfigurations(appConfig)
c.JSON(200, configurations)
})
}

func deployConfiguration(api *gin.Engine, appConfig *model.AppConfig) {
func deployConfiguration(api *gin.RouterGroup, appConfig *model.AppConfig) {
api.PUT("/configurations/:name/deploy", func(c *gin.Context) {

var hosts model.HostsQuery
Expand Down
2 changes: 1 addition & 1 deletion api/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"gopkg.in/gin-gonic/gin.v1"
)

func healthcheck(api *gin.Engine, appConfig *model.AppConfig) {
func healthcheck(api *gin.RouterGroup, appConfig *model.AppConfig) {
api.GET("/healthcheck", func(c *gin.Context) {
c.Writer.WriteHeader(204)
})
Expand Down
29 changes: 26 additions & 3 deletions api/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
"gopkg.in/gin-gonic/gin.v1"
)

func readHosts(api *gin.Engine, appConfig *model.AppConfig) {
func readHosts(api *gin.RouterGroup, appConfig *model.AppConfig) {
api.GET("/hosts", func(c *gin.Context) {
hosts := service.ReadHosts(appConfig)
hosts := service.ReadHosts(appConfig, true)
c.JSON(200, hosts)
})
}

func rebootHost(api *gin.Engine, appConfig *model.AppConfig) {
func rebootHost(api *gin.RouterGroup, appConfig *model.AppConfig) {
api.PATCH("/hosts/:name/reboot", func(c *gin.Context) {
for _, host := range appConfig.Hosts {
if host.Name == c.Param("name") {
Expand All @@ -28,3 +28,26 @@ func rebootHost(api *gin.Engine, appConfig *model.AppConfig) {
c.Writer.WriteHeader(404)
})
}

func discovery(api *gin.RouterGroup, appConfig *model.AppConfig) {
api.PATCH("/discovery", func(c *gin.Context) {

hosts := service.ReadHosts(appConfig, false)

m := make(map[string]struct{})

for _, h := range hosts {
m[h.IPMI.Subnet] = struct{}{}
}
subnets := make([]string, 0, len(m))
for cidr := range m {
subnets = append(subnets, cidr)
}

if service.Discovery(subnets) != nil {
c.Writer.WriteHeader(500)
}

c.Writer.WriteHeader(204)
})
}
27 changes: 23 additions & 4 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func setupCLI() {
cmd.Action = func() {
logger.Init(!*debug)
var configurations = &[]*model.Configuration{}
statusCode, err := http.Request("GET", *serverURL, "/configurations", nil, configurations)
statusCode, err := http.Request("GET", *serverURL, "/v1/configurations", nil, configurations)
if err != nil || statusCode != 200 {
panic(err)
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func setupCLI() {
Message string
}{}

statusCode, err := http.Request("PUT", *serverURL, "/configurations/"+*config+"/deploy", hostsQuery, resp)
statusCode, err := http.Request("PUT", *serverURL, "/v1/configurations/"+*config+"/deploy", hostsQuery, resp)

if err != nil || statusCode != 200 {
os.Stdout.WriteString(resp.Message + "\n")
Expand All @@ -104,7 +104,7 @@ func setupCLI() {
cmd.Action = func() {
logger.Init(!*debug)
var hosts = &[]*model.Host{}
statusCode, err := http.Request("GET", *serverURL, "/hosts", nil, hosts)
statusCode, err := http.Request("GET", *serverURL, "/v1/hosts", nil, hosts)

if err != nil {
os.Stdout.WriteString("Error : " + err.Error())
Expand Down Expand Up @@ -158,7 +158,7 @@ func setupCLI() {

logger.Init(!*debug)

statusCode, err := http.Request("PATCH", *serverURL, "/hosts/"+*hostname+"/reboot", nil, nil)
statusCode, err := http.Request("PATCH", *serverURL, "/v1/hosts/"+*hostname+"/reboot", nil, nil)

// Print data table
table := tablewriter.NewWriter(os.Stdout)
Expand All @@ -174,7 +174,26 @@ func setupCLI() {
table.Render()
}
}
})
cmd.Command("discovery", "Discover hosts over subnets", func(cmd *cli.Cmd) {
cmd.Action = func() {
logger.Init(!*debug)
statusCode, err := http.Request("PATCH", *serverURL, "/v1/discovery", nil, nil)

// Print data table
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Discovery"})
table.SetAutoWrapText(false)
if err != nil {
table.Append([]string{"ERROR : " + err.Error()})
}
if err != nil || statusCode != 204 {
table.Append([]string{"ERROR"})
cli.Exit(1)
}
table.Append([]string{"OK"})
table.Render()
}
})
})

Expand Down
1 change: 1 addition & 0 deletions model/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type IPMI struct {
Interface string `json:"interface" yaml:"interface"`
Status string `json:"status" yaml:"status"`
Hostname string `json:"hostname" yaml:"hostname"`
Subnet string `json:"subnet" yaml:"subnet"`
}

func (i *IPMI) String() string {
Expand Down
34 changes: 34 additions & 0 deletions service/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package service

import (
"bytes"
"fmt"
"os/exec"
"strings"

"github.com/ggiamarchi/pxe-pilot/logger"
)

func ExecCommand(command string, args ...interface{}) (string, string, error) {

fmtCommand := fmt.Sprintf(command, args...)

splitCommand := strings.Split(fmtCommand, " ")

logger.Info("Executing command :: %s :: with args :: %v => %s", command, args, fmtCommand)

cmdName := splitCommand[0]
cmdArgs := splitCommand[1:len(splitCommand)]

cmd := exec.Command(cmdName, cmdArgs...)

var stdout bytes.Buffer
cmd.Stdout = &stdout

var stderr bytes.Buffer
cmd.Stderr = &stderr

err := cmd.Run()

return stdout.String(), stderr.String(), err
}
29 changes: 2 additions & 27 deletions service/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package service
import (
"bytes"
"fmt"
"os/exec"
"strings"

"github.com/ggiamarchi/pxe-pilot/model"
Expand Down Expand Up @@ -44,34 +43,10 @@ func ChassisPowerOff(context *model.IPMI) error {
return err
}

func execCommand(command string, args ...interface{}) (string, string, error) {

fmtCommand := fmt.Sprintf(command, args...)

splitCommand := strings.Split(fmtCommand, " ")

logger.Info("Executing command :: %s :: with args :: %v => %s", command, args, fmtCommand)

cmdName := splitCommand[0]
cmdArgs := splitCommand[1:len(splitCommand)]

cmd := exec.Command(cmdName, cmdArgs...)

var stdout bytes.Buffer
cmd.Stdout = &stdout

var stderr bytes.Buffer
cmd.Stderr = &stderr

err := cmd.Run()

return stdout.String(), stderr.String(), err
}

// getIPFromMAC reads the ARP table to find the IP address matching the given MAC address
func getIPFromMAC(mac string) (string, error) {

stdout, _, err := execCommand("sudo arp -an")
stdout, _, err := ExecCommand("sudo arp -an")

if err != nil {
return "", err
Expand Down Expand Up @@ -145,7 +120,7 @@ func ipmitool(context *model.IPMI, command string) (*string, *string, error) {
baseCmd := fmt.Sprintf("ipmitool%s -N 1 -R 2 -H %s -U %s -P %s ", interfaceOpt, context.Hostname, context.Username, context.Password)

fullCommand := baseCmd + command
stdout, stderr, err := execCommand(fullCommand)
stdout, stderr, err := ExecCommand(fullCommand)

if err != nil {
logger.Error("IPMI command failed <%s> - %s", fullCommand, err)
Expand Down
34 changes: 25 additions & 9 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ import (
"github.com/ggiamarchi/pxe-pilot/model"
)

func Discovery(subnets []string) error {
logger.Info("Discovery on subnets %+v", subnets)
var wg sync.WaitGroup
for _, cidr := range subnets {
wg.Add(1)
go func(cidr string) {
defer wg.Done()
ExecCommand("fping -c 1 -D -q -g %s", cidr)
}(cidr)
}
wg.Wait()
return nil
}

func ReadConfigurations(appConfig *model.AppConfig) []*model.Configuration {
files, _ := ioutil.ReadDir(appConfig.Configuration.Directory)
configurations := make([]*model.Configuration, len(files))
Expand All @@ -36,7 +50,7 @@ func RebootHost(host *model.Host) error {
return logger.Errorf("Reboot host '%s' : Unknown error", host.Name)
}

func ReadHosts(appConfig *model.AppConfig) []*model.Host {
func ReadHosts(appConfig *model.AppConfig, status bool) []*model.Host {

pxelinuxDir := appConfig.Tftp.Root + "/pxelinux.cfg"

Expand All @@ -53,13 +67,15 @@ func ReadHosts(appConfig *model.AppConfig) []*model.Host {

for i, host := range appConfig.Hosts {

if host.IPMI != nil {
wg.Add(1)
hostlocal := host
go func() {
defer wg.Done()
ChassisPowerStatus(hostlocal.IPMI)
}()
if status {
if host.IPMI != nil {
wg.Add(1)
hostlocal := host
go func() {
defer wg.Done()
ChassisPowerStatus(hostlocal.IPMI)
}()
}
}

hosts[i] = &model.Host{
Expand Down Expand Up @@ -130,7 +146,7 @@ func DeployConfiguration(appConfig *model.AppConfig, name string, hosts []*model
hostsByPrimaryMAC := make(map[string]*model.Host)
hostsByMAC := make(map[string]*model.Host)

for _, h := range ReadHosts(appConfig) {
for _, h := range ReadHosts(appConfig, false) {
hostsByName[h.Name] = h
hostsByPrimaryMAC[h.MACAddresses[0]] = h
for _, mac := range h.MACAddresses {
Expand Down
1 change: 1 addition & 0 deletions test/pxepilot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ hosts:
mac_addresses: ["00:00:00:00:00:01"]
ipmi:
mac_address: "00:00:00:00:00:a1"
subnet: 10.0.0.0/24
username: "user"
password: "password"
interface: "lanplus"
Expand Down

0 comments on commit 0d07b83

Please sign in to comment.