Skip to content

Commit

Permalink
Merge pull request #98 from cybozu-go/feature/label
Browse files Browse the repository at this point in the history
Feature/label
  • Loading branch information
ueokande committed Aug 23, 2018
2 parents 4e13b87 + d4025c5 commit 8fd1e5b
Show file tree
Hide file tree
Showing 18 changed files with 342 additions and 192 deletions.
17 changes: 8 additions & 9 deletions cmd/sabactl/machines.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,14 @@ type machinesGetCmd struct {
}

var machinesGetQuery = map[string]string{
"serial": "Serial name",
"datacenter": "Datacenter name",
"rack": "Rack name",
"role": "Role name",
"product": "Product name (e.g. 'R630')",
"ipv4": "IPv4 address",
"ipv6": "IPv6 address",
"bmc-type": "BMC type",
"state": "State",
"serial": "Serial name",
"rack": "Rack name",
"role": "Role name",
"labels": "Label name and value (-labels key=val,...)",
"ipv4": "IPv4 address",
"ipv6": "IPv6 address",
"bmc-type": "BMC type",
"state": "State",
}

func (r *machinesGetCmd) SetFlags(f *flag.FlagSet) {
Expand Down
10 changes: 4 additions & 6 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,9 @@ In the HTTP request body, specify the following list of the machine information
Field | Description
----- | -----------
`serial=<serial>` | The serial number of the machine
`datacenter=<datacenter>` | The data center name where the machine is in
`labels={<key=value>, ...}` | The labels of the machine
`rack=<rack>` | The rack number where the machine is in. If it is omitted, value set to `0`
`role=<role>` | The role of the machine (e.g. `boot` or `worker`)
`product=<product>` | The product name of the machine (e.g. `R630`)
`bmc=<bmc>` | The BMC spec

**Successful response**
Expand All @@ -210,8 +209,8 @@ Field | Description
```console
$ curl -s -X POST 'localhost:10080/api/v1/machines' -d '
[
{ "serial": "1234abcd", "product": "R630", "datacenter": "ty3", "rack": 1, "role": "boot", "bmc": {"type": "iDRAC-9"} },
{ "serial": "2345bcde", "product": "R630", "datacenter": "ty3", "rack": 1, "role": "worker", "bmc": {"type": "iDRAC-9"} }
{ "serial": "1234abcd", "labels": {"product": "R630", "datacenter": "ty3"}, "rack": 1, "role": "boot", "bmc": {"type": "iDRAC-9"} },
{ "serial": "2345bcde", "labels": {"product": "R630", "datacenter": "ty3"}, "rack": 1, "role": "worker", "bmc": {"type": "iDRAC-9"} }
]'
```

Expand All @@ -222,10 +221,9 @@ Search registered machines. A user can specify the following URL queries.
Query | Description
----- | -----------
`serial=<serial>` | The serial number of the machine
`datacenter=<datacenter>` | The data center name where the machine is in
`labels=<key=value>,...` | The labels of the machine.
`rack=<rack>` | The rack number where the machine is in
`role=<role>` | The role of the machine
`product=<product>` | The product name of the machine(e.g. `R630`)
`ipv4=<ip address>` | IPv4 address
`ipv6=<ip address>` | IPv6 address
`bmc-type=<bmc-type>` | BMC type
Expand Down
6 changes: 4 additions & 2 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ Prepare `machines.json` as follows:
[
{
"serial": "1234abcd",
"product": "Dell R640",
"datacenter": "tokyo1",
"labels": {
"product": "Dell R640",
"datacenter": "tokyo1"
}
"rack": 0,
"role": "boot",
"bmc": {
Expand Down
25 changes: 13 additions & 12 deletions docs/machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ MachineSpec struct

`MachineSpec` can be represented as a JSON object having these fields:

Field | Type | Description
--------------- | -------- | -----------
`serial` | string | SMBIOS serial number of the machine.
`product` | string | Product name of the machine
`datacenter` | string | Data center name where the machine exists.
`rack` | int | Logical rack number (LRN) where the machine exists.
`index-in-rack` | int | Logical position in a rack.
`ipv4` | []string | IPv4 addresses for OS.
`ipv6` | []string | IPv6 addresses for OS.
`bmc` | object | BMC parameters; See below.
Field | Type | Description
--------------- | -------- | -----------
`serial` | string | SMBIOS serial number of the machine.
`labels` | map[string]string | Labels of the machine such as `product` or `datacenter`
`rack` | int | Logical rack number (LRN) where the machine exists.
`index-in-rack` | int | Logical position in a rack.
`ipv4` | []string | IPv4 addresses for OS.
`ipv6` | []string | IPv6 addresses for OS.
`bmc` | object | BMC parameters; See below.

Key in `bmc` | Type | Description
--------------- | ------ | -----------
Expand All @@ -41,8 +40,10 @@ A JSON representation of `Machine` looks like:
{
"spec": {
"serial": "1234abcd",
"product": "Dell R630",
"datacenter": "tokyo1",
"labels": {
"product": "Dell R630",
"datacenter": "tokyo1"
}
"rack": 1,
"index-in-rack": 1,
"role": "boot",
Expand Down
7 changes: 3 additions & 4 deletions docs/sabactl.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ Detailed specification of the input JSON file is same as that of the [`POST /api

```json
[
{ "serial": "<serial1>", "datacenter": "<datacenter1>", "rack": "<rack1>", "product": "<product1>", "role": "<role1>", "bmc": { "type": "iDRAC-9" }},
{ "serial": "<serial2>", "datacenter": "<datacenter2>", "rack": "<rack2>", "product": "<product2>", "role": "<role2>", "bmc": { "type": "iDRAC-9" }}
{ "serial": "<serial1>", "labels": {"product": "<product1>", "datacenter": "<datacenter1>"}, "rack": "<rack1>", "role": "<role1>", "bmc": { "type": "iDRAC-9" }},
{ "serial": "<serial2>", "labels": {"product": "<product2>", "datacenter": "<datacenter2>"}, "rack": "<rack2>", "role": "<role2>", "bmc": { "type": "iDRAC-9" }}
]
```

Expand All @@ -75,10 +75,9 @@ Show machines filtered by query parameters.
```console
$ sabactl machines get \
[--serial <serial>] \
[--datacenter <datacenter>] \
[--rack <rack>] \
[--role <role>] \
[--product <product>] \
[--labels <key=value>,...]
[--ipv4 <ip address>] \
[--ipv6 <ip address>] \
[--bmc-type <BMC type>] \
Expand Down
20 changes: 12 additions & 8 deletions e2e/sabactl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,23 @@ func testSabactlMachines(t *testing.T) {

specs := []*sabakan.MachineSpec{
{
Serial: "12345678",
Product: "R730xd",
Datacenter: "ty3",
Role: "worker",
Serial: "12345678",
Labels: map[string]string{
"product": "R730xd",
"datacenter": "ty3",
},
Role: "worker",
BMC: sabakan.MachineBMC{
Type: sabakan.BmcIdrac9,
},
},
{
Serial: "abcdefg",
Product: "R730xd",
Datacenter: "ty3",
Role: "boot",
Serial: "abcdefg",
Labels: map[string]string{
"product": "R730xd",
"datacenter": "ty3",
},
Role: "boot",
BMC: sabakan.MachineBMC{
Type: sabakan.BmcIpmi2,
},
Expand Down
31 changes: 21 additions & 10 deletions machines.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,26 @@ const (
)

var (
reValidRole = regexp.MustCompile(`^[a-zA-Z][0-9a-zA-Z._-]*$`)
reValidRole = regexp.MustCompile(`^[a-zA-Z][0-9a-zA-Z._-]*$`)
reValidLabelVal = regexp.MustCompile(`^[[:print:]]+$`)
reValidLabelName = regexp.MustCompile(`^[a-z0-9A-Z-_/.]+$`)
)

// IsValidRole returns true if role is valid as machine role
func IsValidRole(role string) bool {
return reValidRole.MatchString(role)
}

// IsValidLabelName returns true if label name is valid
func IsValidLabelName(name string) bool {
return reValidLabelName.MatchString(name)
}

// IsValidLabelValue returns true if label value is valid
func IsValidLabelValue(value string) bool {
return reValidLabelVal.MatchString(value)
}

// MachineBMC is a bmc interface struct for Machine
type MachineBMC struct {
IPv4 string `json:"ipv4"`
Expand All @@ -48,15 +60,14 @@ type MachineBMC struct {

// MachineSpec is a set of attributes to define a machine.
type MachineSpec struct {
Serial string `json:"serial"`
Product string `json:"product"`
Datacenter string `json:"datacenter"`
Rack uint `json:"rack"`
IndexInRack uint `json:"index-in-rack"`
Role string `json:"role"`
IPv4 []string `json:"ipv4"`
IPv6 []string `json:"ipv6"`
BMC MachineBMC `json:"bmc"`
Serial string `json:"serial"`
Labels map[string]string `json:"labels"`
Rack uint `json:"rack"`
IndexInRack uint `json:"index-in-rack"`
Role string `json:"role"`
IPv4 []string `json:"ipv4"`
IPv6 []string `json:"ipv6"`
BMC MachineBMC `json:"bmc"`
}

// Machine represents a server hardware.
Expand Down
34 changes: 34 additions & 0 deletions machines_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,40 @@ func TestIsValidRole(t *testing.T) {
}
}

func TestIsValidLabelName(t *testing.T) {
t.Parallel()

validNames := []string{"valid_name1", "valid-name2", "valid/name3"}
for _, vn := range validNames {
if !IsValidLabelName(vn) {
t.Error("validator should return true:", vn)
}
}
invalidNames := []string{"^in;valid name\\1", "in$valid#name&2", "invalid@name=3"}
for _, ivn := range invalidNames {
if IsValidLabelName(ivn) {
t.Error("validator should return false:", ivn)
}
}
}

func TestIsValidLabelValue(t *testing.T) {
t.Parallel()

validVals := []string{"^valid value@1", "valid$value-=2", "%valid':value;3"}
for _, vv := range validVals {
if !IsValidLabelValue(vv) {
t.Error("validator should return true:", vv)
}
}
invalidVals := []string{`inválidvaluõ1`, `iñvalidvålue`}
for _, ivv := range invalidVals {
if IsValidLabelValue(ivv) {
t.Error("validator should return false:", ivv)
}
}
}

func TestMachine(t *testing.T) {
t.Parallel()

Expand Down
64 changes: 34 additions & 30 deletions models/etcd/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@ import (
"github.com/cybozu-go/sabakan"
)

const (
labelSep = "="
)

// machinesIndex is on-memory index of the etcd values
type machinesIndex struct {
mux sync.RWMutex
Product map[string][]string
Datacenter map[string][]string
Rack map[string][]string
Role map[string][]string
IPv4 map[string]string
IPv6 map[string]string
BMCType map[string][]string
State map[sabakan.MachineState][]string
mux sync.RWMutex
Rack map[string][]string
Role map[string][]string
Labels map[string][]string
IPv4 map[string]string
IPv6 map[string]string
BMCType map[string][]string
State map[sabakan.MachineState][]string
}

func newMachinesIndex() *machinesIndex {
return &machinesIndex{
Product: make(map[string][]string),
Datacenter: make(map[string][]string),
Rack: make(map[string][]string),
Role: make(map[string][]string),
IPv4: make(map[string]string),
IPv6: make(map[string]string),
BMCType: make(map[string][]string),
State: make(map[sabakan.MachineState][]string),
Rack: make(map[string][]string),
Role: make(map[string][]string),
Labels: make(map[string][]string),
IPv4: make(map[string]string),
IPv6: make(map[string]string),
BMCType: make(map[string][]string),
State: make(map[sabakan.MachineState][]string),
}
}

Expand Down Expand Up @@ -71,8 +73,6 @@ func (mi *machinesIndex) AddIndex(m *sabakan.Machine) {

func (mi *machinesIndex) addNoLock(m *sabakan.Machine) {
spec := &m.Spec
mi.Product[spec.Product] = append(mi.Product[spec.Product], spec.Serial)
mi.Datacenter[spec.Datacenter] = append(mi.Datacenter[spec.Datacenter], spec.Serial)
mcrack := fmt.Sprint(spec.Rack)
mi.Rack[mcrack] = append(mi.Rack[mcrack], spec.Serial)
mi.Role[spec.Role] = append(mi.Role[spec.Role], spec.Serial)
Expand All @@ -90,6 +90,10 @@ func (mi *machinesIndex) addNoLock(m *sabakan.Machine) {
mi.IPv6[spec.BMC.IPv6] = spec.Serial
}
mi.State[m.Status.State] = append(mi.State[m.Status.State], spec.Serial)
for k, v := range spec.Labels {
labelKey := k + labelSep + v
mi.Labels[labelKey] = append(mi.Labels[labelKey], spec.Serial)
}
}

func indexOf(data []string, element string) int {
Expand All @@ -109,12 +113,8 @@ func (mi *machinesIndex) DeleteIndex(m *sabakan.Machine) {

func (mi *machinesIndex) deleteNoLock(m *sabakan.Machine) {
spec := &m.Spec
i := indexOf(mi.Product[spec.Product], spec.Serial)
mi.Product[spec.Product] = append(mi.Product[spec.Product][:i], mi.Product[spec.Product][i+1:]...)
i = indexOf(mi.Datacenter[spec.Datacenter], spec.Serial)
mi.Datacenter[spec.Datacenter] = append(mi.Datacenter[spec.Datacenter][:i], mi.Datacenter[spec.Datacenter][i+1:]...)
mcrack := fmt.Sprint(spec.Rack)
i = indexOf(mi.Rack[mcrack], spec.Serial)
i := indexOf(mi.Rack[mcrack], spec.Serial)
mi.Rack[mcrack] = append(mi.Rack[mcrack][:i], mi.Rack[mcrack][i+1:]...)
i = indexOf(mi.Role[spec.Role], spec.Serial)
mi.Role[spec.Role] = append(mi.Role[spec.Role][:i], mi.Role[spec.Role][i+1:]...)
Expand All @@ -131,6 +131,11 @@ func (mi *machinesIndex) deleteNoLock(m *sabakan.Machine) {

i = indexOf(mi.State[m.Status.State], spec.Serial)
mi.State[m.Status.State] = append(mi.State[m.Status.State][:i], mi.State[m.Status.State][i+1:]...)
for k, v := range spec.Labels {
labelKey := k + labelSep + v
i = indexOf(mi.Labels[labelKey], spec.Serial)
mi.Labels[labelKey] = append(mi.Labels[labelKey][:i], mi.Labels[labelKey][i+1:]...)
}
}

// UpdateIndex updates target machine on the index
Expand All @@ -147,12 +152,6 @@ func (mi *machinesIndex) query(q sabakan.Query) []string {

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

for _, serial := range mi.Product[q.Product()] {
res[serial] = struct{}{}
}
for _, serial := range mi.Datacenter[q.Datacenter()] {
res[serial] = struct{}{}
}
for _, serial := range mi.Rack[q.Rack()] {
res[serial] = struct{}{}
}
Expand All @@ -175,6 +174,11 @@ func (mi *machinesIndex) query(q sabakan.Query) []string {
for _, serial := range mi.State[sabakan.MachineState(q.State())] {
res[serial] = struct{}{}
}
for _, labelKey := range q.Labels() {
for _, serial := range mi.Labels[labelKey] {
res[serial] = struct{}{}
}
}

serials := make([]string, 0, len(res))
for serial := range res {
Expand Down
Loading

0 comments on commit 8fd1e5b

Please sign in to comment.