Index: go/src/infra/crimson/server/crimsondb/crimsondb.go |
diff --git a/go/src/infra/crimson/server/crimsondb/crimsondb.go b/go/src/infra/crimson/server/crimsondb/crimsondb.go |
index 752e6bcfa22c4c7aae9f398ae6224769702b6239..a781036bc878fb793289e1e49ff16430064e6d1e 100644 |
--- a/go/src/infra/crimson/server/crimsondb/crimsondb.go |
+++ b/go/src/infra/crimson/server/crimsondb/crimsondb.go |
@@ -275,6 +275,210 @@ func SelectIPRange(ctx context.Context, req *crimson.IPRangeQuery) ([]IPRange, e |
return ipRanges, nil |
} |
+// scanHosts is a low-level function to scan sql results. |
+// Rows must contain site, hostname, mac_addr, ip, boot_class in that order. |
+func scanHosts(ctx context.Context, rows *sql.Rows) (*crimson.HostList, error) { |
+ hostList := crimson.HostList{} |
+ |
+ for rows.Next() { |
+ var ipString, macString string |
+ var hw net.HardwareAddr |
+ var ip net.IP |
+ var bootClass sql.NullString |
+ |
+ host := crimson.Host{} |
+ err := rows.Scan(&host.Site, &host.Hostname, &macString, &ipString, |
+ &bootClass) |
+ if bootClass.Valid { |
+ host.BootClass = bootClass.String |
+ } |
+ if err != nil { // Users can't trigger that. |
+ logging.Errorf(ctx, "%s", err) |
+ return nil, err |
+ } |
+ if macString != "" { |
+ hw, err = HexStringToHardwareAddr(macString) |
+ if err != nil { |
+ return nil, err |
+ } |
+ host.MacAddr = hw.String() |
+ } |
+ |
+ if ipString != "" { |
+ ip, err = HexStringToIP(ipString) |
+ if err != nil { |
+ return nil, err |
+ } |
+ host.Ip = ip.String() |
+ } |
+ hostList.Hosts = append(hostList.Hosts, &host) |
+ } |
+ err := rows.Err() |
+ if err != nil { |
+ logging.Errorf(ctx, "%s", err) |
+ return nil, err |
+ } |
+ return &hostList, nil |
+} |
+ |
+// InsertHost adds new hosts in the corresponding table. |
+func InsertHost(ctx context.Context, req *crimson.HostList) (err error) { |
+ db := DB(ctx) |
+ |
+ if len(req.Hosts) == 0 { |
+ logging.Errorf(ctx, "Received empty list of hosts to create.") |
+ return UserErrorf(InvalidArgument, |
+ "Received empty list of hosts to create.") |
+ } |
+ |
+ statement := bytes.Buffer{} |
+ params := []interface{}{} |
+ |
+ statement.WriteString("INSERT INTO host " + |
+ "(site, hostname, mac_addr, ip, boot_class) VALUES ") |
+ delimiter := "" |
+ |
+ // Check that all required fields have been provided. |
+ // TODO(pgervais): autogenerate missing values instead. |
+ for i, host := range req.Hosts { |
+ if host.Site == "" { |
+ err = UserErrorf(InvalidArgument, |
+ "Received empty host in entry #%s", i+1) |
+ return |
+ } |
+ if host.MacAddr == "" { |
+ err = UserErrorf(InvalidArgument, |
+ "Received empty MAC address in entry #%s", i+1) |
+ return |
+ } |
+ if host.Ip == "" { |
+ err = UserErrorf(InvalidArgument, |
+ "Received empty IP address in entry #%s", i+1) |
+ return |
+ } |
+ if host.Hostname == "" { |
+ err = UserErrorf(InvalidArgument, |
+ "Received empty hostname in entry #%s", i+1) |
+ return |
+ } |
+ |
+ // Compose query |
+ var ip, macAddr string |
+ statement.WriteString(delimiter) |
+ delimiter = ", \n" |
+ statement.WriteString("(?, ?, ?, ?, ?)") |
+ |
+ ip, err = IPStringToHexString(host.Ip) |
+ if err != nil { |
+ return |
+ } |
+ |
+ macAddr, err = MacAddrStringToHexString(host.MacAddr) |
+ if err != nil { |
+ return |
+ } |
+ |
+ if host.BootClass == "" { |
+ params = append( |
+ params, |
+ host.Site, host.Hostname, macAddr, ip, nil) |
+ } else { |
+ params = append( |
+ params, |
+ host.Site, host.Hostname, macAddr, ip, host.BootClass) |
+ } |
+ } |
+ |
+ _, err = db.Exec(statement.String(), params...) |
+ if err != nil { |
+ logging.Errorf(ctx, "Insertion of new hosts failed. %s", err) |
+ return |
+ } |
+ |
+ return |
+} |
+ |
+func SelectHost(ctx context.Context, req *crimson.HostQuery) (*crimson.HostList, error) { |
+ var err error |
+ |
+ db := DB(ctx) |
+ delimiter := "" |
+ |
+ statement := bytes.Buffer{} |
+ params := []interface{}{} |
+ |
+ statement.WriteString("SELECT site, hostname, mac_addr, ip, boot_class FROM host") |
+ delimiter = "\nWHERE " |
+ |
+ if req.Site != "" { |
+ statement.WriteString(delimiter) |
+ delimiter = "\nAND " |
+ statement.WriteString("site=?") |
+ params = append(params, req.Site) |
+ } |
+ |
+ if req.Hostname != "" { |
+ statement.WriteString(delimiter) |
+ delimiter = "\nAND " |
+ statement.WriteString("hostname=?") |
+ params = append(params, req.Hostname) |
+ } |
+ |
+ if req.MacAddr != "" { |
+ statement.WriteString(delimiter) |
+ delimiter = "\nAND " |
+ hw, err := MacAddrStringToHexString(req.MacAddr) |
+ if err != nil { |
+ return nil, UserErrorf( |
+ InvalidArgument, |
+ "parsing of Mac address failed: %s", req.MacAddr) |
+ } |
+ statement.WriteString("mac_addr=?") |
+ params = append(params, hw) |
+ } |
+ |
+ if req.Ip != "" { |
+ statement.WriteString(delimiter) |
+ delimiter = "\nAND " |
+ ip, err := IPStringToHexString(req.Ip) |
+ if err != nil { |
+ return nil, UserErrorf( |
+ InvalidArgument, |
+ "parsing of IP address failed: %s", req.Ip) |
+ } |
+ statement.WriteString("ip=?") |
+ params = append(params, ip) |
+ } |
+ |
+ if req.BootClass != "" { |
+ statement.WriteString(delimiter) |
+ delimiter = "\nAND " |
+ statement.WriteString("boot_class=?") |
+ params = append(params, req.BootClass) |
+ } |
+ |
+ if req.Limit > 0 { |
+ statement.WriteString("\nLIMIT ?") |
+ params = append(params, req.Limit) |
+ } |
+ |
+ sqlRows, err := db.Query(statement.String(), params...) |
+ |
+ if err != nil { |
+ logging.Errorf(ctx, "%s", err) |
+ return nil, err |
+ } |
+ defer sqlRows.Close() |
+ |
+ var rows *crimson.HostList |
+ rows, err = scanHosts(ctx, sqlRows) |
+ if err != nil { |
+ logging.Errorf(ctx, "%s", err) |
+ return nil, err |
+ } |
+ return rows, nil |
+} |
+ |
// UseDB stores a db handle into a context. |
func UseDB(ctx context.Context, db *sql.DB) context.Context { |
return context.WithValue(ctx, "dbHandle", db) |