diff --git a/CHANGELOG.md b/CHANGELOG.md index e052f082fd..baaee5af9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Updated /servers/details to use multiple interfaces in API v3 - Added [Edge Traffic Routing](https://traffic-control-cdn.readthedocs.io/en/latest/admin/traffic_router.html#edge-traffic-routing) feature which allows Traffic Router to localize more DNS record types than just the routing name for DNS delivery services - Astats csv support - astats will now respond to `Accept: text/csv` and return a csv formatted stats list +- Updated /deliveryservices/{{ID}}/servers to use multiple interfaces in API v3 +- Updated /deliveryservices/{{ID}}/servers/eligible to use multiple interfaces in API v3 ### Fixed - Fixed the `GET /api/x/jobs` and `GET /api/x/jobs/:id` Traffic Ops API routes to allow falling back to Perl via the routing blacklist diff --git a/docs/source/api/v3/deliveryservices_id_servers.rst b/docs/source/api/v3/deliveryservices_id_servers.rst index 731050535a..c6762eb53b 100644 --- a/docs/source/api/v3/deliveryservices_id_servers.rst +++ b/docs/source/api/v3/deliveryservices_id_servers.rst @@ -54,16 +54,22 @@ Response Structure :iloIpNetmask: The IPv4 subnet mask of the lights-out-management port\ [#ilowikipedia]_ :iloPassword: The password of the of the lights-out-management user - displays as ``******`` unless the requesting user has the 'admin' role)\ [#ilowikipedia]_ :iloUsername: The user name for lights-out-management\ [#ilowikipedia]_ -:interfaceMtu: The :abbr:`MTU (Maximum Transmission Unit)` to configure for ``interfaceName`` +:interfaces: An array of interface and IP address information - .. seealso:: `The Wikipedia article on Maximum Transmission Unit `_ + :max_bandwidth: The maximum allowed bandwidth for this interface to be considered "healthy" by Traffic Monitor. This has no effect if `monitor` is not true. Values are in kb/s. The value `null` means "no limit". + :monitor: A boolean indicating if Traffic Monitor should monitor this interface + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` to configure for ``interfaceName`` + + .. seealso:: `The Wikipedia article on Maximum Transmission Unit `_ + + :name: The network interface name used by the server. + + :ipAddresses: An array of the IP address information for the interface + + :address: The IPv4 or IPv6 address and subnet mask of the server - applicable for the interface ``name`` + :gateway: The IPv4 or IPv6 gateway address of the server - applicable for the interface ``name`` + :service_address: A boolean determining if content will be routed to the IP address -:interfaceName: The network interface name used by the server -:ip6Address: The IPv6 address and subnet mask of the server - applicable for the interface ``interfaceName`` -:ip6Gateway: The IPv6 gateway address of the server - applicable for the interface ``interfaceName`` -:ipAddress: The IPv4 address of the server- applicable for the interface ``interfaceName`` -:ipGateway: The IPv4 gateway of the server- applicable for the interface ``interfaceName`` -:ipNetmask: The IPv4 subnet mask of the server- applicable for the interface ``interfaceName`` :lastUpdated: The time and date at which this server was last updated, in an ISO-like format :mgmtIpAddress: The IPv4 address of the server's management port :mgmtIpGateway: The IPv4 gateway of the server's management port @@ -121,13 +127,6 @@ Response Structure "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth0", - "ip6Address": "fc01:9400:1000:8::100", - "ip6Gateway": "fc01:9400:1000:8::1", - "ipAddress": "172.16.239.100", - "ipGateway": "172.16.239.1", - "ipNetmask": "255.255.255.0", "lastUpdated": "2018-11-14 21:08:44+00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -146,7 +145,25 @@ Response Structure "tcpPort": 80, "type": "EDGE", "typeId": 11, - "updPending": false + "updPending": false, + "interfaces": [{ + "ipAddresses": [ + { + "address": "172.16.239.100", + "gateway": "172.16.239.1", + "service_address": true + }, + { + "address": "fc01:9400:1000:8::100", + "gateway": "fc01:9400:1000:8::1", + "service_address": true + } + ], + "max_bandwidth": 0, + "monitor": true, + "mtu": 1500, + "name": "eth0" + }] } ]} diff --git a/docs/source/api/v3/deliveryservices_id_servers_eligible.rst b/docs/source/api/v3/deliveryservices_id_servers_eligible.rst index f6bd15b11b..57f6495bba 100644 --- a/docs/source/api/v3/deliveryservices_id_servers_eligible.rst +++ b/docs/source/api/v3/deliveryservices_id_servers_eligible.rst @@ -59,16 +59,22 @@ Response Structure :iloIpNetmask: The IPv4 subnet mask of the lights-out-management port\ [#ilowikipedia]_ :iloPassword: The password of the of the lights-out-management user - displays as ``******`` unless the requesting user has the 'admin' role)\ [#ilowikipedia]_ :iloUsername: The user name for lights-out-management\ [#ilowikipedia]_ -:interfaceMtu: The :abbr:`MTU (Maximum Transmission Unit)` to configure for ``interfaceName`` +:interfaces: An array of interface and IP address information - .. seealso:: `The Wikipedia article on Maximum Transmission Unit `_ + :max_bandwidth: The maximum allowed bandwidth for this interface to be considered "healthy" by Traffic Monitor. This has no effect if `monitor` is not true. Values are in kb/s. The value `null` means "no limit". + :monitor: A boolean indicating if Traffic Monitor should monitor this interface + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` to configure for ``interfaceName`` + + .. seealso:: `The Wikipedia article on Maximum Transmission Unit `_ + + :name: The network interface name used by the server. + + :ipAddresses: An array of the IP address information for the interface + + :address: The IPv4 or IPv6 address and subnet mask of the server - applicable for the interface ``name`` + :gateway: The IPv4 or IPv6 gateway address of the server - applicable for the interface ``name`` + :service_address: A boolean determining if content will be routed to the IP address -:interfaceName: The network interface name used by the server -:ip6Address: The IPv6 address and subnet mask of the server - applicable for the interface ``interfaceName`` -:ip6Gateway: The IPv6 gateway address of the server - applicable for the interface ``interfaceName`` -:ipAddress: The IPv4 address of the server- applicable for the interface ``interfaceName`` -:ipGateway: The IPv4 gateway of the server- applicable for the interface ``interfaceName`` -:ipNetmask: The IPv4 subnet mask of the server- applicable for the interface ``interfaceName`` :lastUpdated: The time and date at which this server was last updated, in an ISO-like format :mgmtIpAddress: The IPv4 address of the server's management port :mgmtIpGateway: The IPv4 gateway of the server's management port @@ -114,13 +120,6 @@ Response Structure "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth0", - "ip6Address": "fc01:9400:1000:8::100", - "ip6Gateway": "fc01:9400:1000:8::1", - "ipAddress": "172.16.239.100", - "ipGateway": "172.16.239.1", - "ipNetmask": "255.255.255.0", "lastUpdated": "2018-10-30 16:01:12+00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -139,7 +138,25 @@ Response Structure "tcpPort": 80, "type": "EDGE", "typeId": 11, - "updPending": false + "updPending": false, + "interfaces": [{ + "ipAddresses": [ + { + "address": "172.16.239.100", + "gateway": "172.16.239.1", + "service_address": true + }, + { + "address": "fc01:9400:1000:8::100", + "gateway": "fc01:9400:1000:8::1", + "service_address": true + } + ], + "max_bandwidth": 0, + "monitor": true, + "mtu": 1500, + "name": "eth0" + }] } ]} diff --git a/lib/go-tc/deliveryservice_servers.go b/lib/go-tc/deliveryservice_servers.go index b082b567b2..bb1314a60c 100644 --- a/lib/go-tc/deliveryservice_servers.go +++ b/lib/go-tc/deliveryservice_servers.go @@ -63,11 +63,8 @@ const ( Eligible ) -type DSServersAttrResponse struct { - Response []DSServer `json:"response"` -} - -type DSServer struct { +// DSServerBase contains the base information for a Delivery Service Server. +type DSServerBase struct { Cachegroup *string `json:"cachegroup" db:"cachegroup"` CachegroupID *int `json:"cachegroupId" db:"cachegroup_id"` CDNID *int `json:"cdnId" db:"cdn_id"` @@ -85,13 +82,6 @@ type DSServer struct { ILOIPNetmask *string `json:"iloIpNetmask" db:"ilo_ip_netmask"` ILOPassword *string `json:"iloPassword" db:"ilo_password"` ILOUsername *string `json:"iloUsername" db:"ilo_username"` - InterfaceMtu *int `json:"interfaceMtu" db:"interface_mtu"` - InterfaceName *string `json:"interfaceName" db:"interface_name"` - IP6Address *string `json:"ip6Address" db:"ip6_address"` - IP6Gateway *string `json:"ip6Gateway" db:"ip6_gateway"` - IPAddress *string `json:"ipAddress" db:"ip_address"` - IPGateway *string `json:"ipGateway" db:"ip_gateway"` - IPNetmask *string `json:"ipNetmask" db:"ip_netmask"` LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"` MgmtIPAddress *string `json:"mgmtIpAddress" db:"mgmt_ip_address"` MgmtIPGateway *string `json:"mgmtIpGateway" db:"mgmt_ip_gateway"` @@ -114,3 +104,15 @@ type DSServer struct { ServerCapabilities []string `json:"-" db:"server_capabilities"` DeliveryServiceCapabilities []string `json:"-" db:"deliveryservice_capabilities"` } + +// DSServerV11 contains the legacy format for a Delivery Service Server. +type DSServerV11 struct { + DSServerBase + LegacyInterfaceDetails +} + +// DSServer contains information for a Delivery Service Server. +type DSServer struct { + DSServerBase + ServerInterfaces *[]ServerInterfaceInfo `json:"interfaces" db:"interfaces"` +} diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go index 0904a52590..08c43d1a44 100644 --- a/lib/go-tc/servers.go +++ b/lib/go-tc/servers.go @@ -105,7 +105,7 @@ func (sii *ServerInterfaceInfo) Value() (driver.Value, error) { } // Scan implements the sql.Scanner interface -// expects json.RawMessage and unmarshals to a deliveryservice struct +// expects json.RawMessage and unmarshals to a ServerInterfaceInfo struct func (sii *ServerInterfaceInfo) Scan(src interface{}) error { b, ok := src.([]byte) if !ok { diff --git a/lib/go-util/ptr.go b/lib/go-util/ptr.go index eb795836a9..8cf013f6df 100644 --- a/lib/go-util/ptr.go +++ b/lib/go-util/ptr.go @@ -31,6 +31,10 @@ func UintPtr(u uint) *uint { return &u } +func Uint64Ptr(u uint64) *uint64 { + return &u +} + func Int64Ptr(i int64) *int64 { return &i } diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go b/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go index cbc4edfe0f..f99a42c244 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go @@ -23,7 +23,6 @@ import ( "database/sql" "errors" "net/http" - "strconv" "strings" "github.com/apache/trafficcontrol/lib/go-tc" @@ -63,6 +62,26 @@ func GetServersEligible(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting eligible servers: "+err.Error())) return } + + if inf.Version.Major < 3 { + v11ServerList := []tc.DSServerV11{} + for _, srv := range servers { + v11server := tc.DSServerV11{} + v11server.DSServerBase = srv.DSServerBase + + interfaces := *srv.ServerInterfaces + legacyInterface, err := tc.InterfaceInfoToLegacyInterfaces(interfaces) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("converting to server detail v11: "+err.Error())) + return + } + v11server.LegacyInterfaceDetails = legacyInterface + + v11ServerList = append(v11ServerList, v11server) + } + api.WriteResp(w, r, v11ServerList) + return + } api.WriteResp(w, r, servers) } @@ -86,13 +105,26 @@ s.ilo_ip_gateway, s.ilo_ip_netmask, s.ilo_password, s.ilo_username, -COALESCE(s.interface_mtu, ` + strconv.Itoa(JumboFrameBPS) + `) as interface_mtu, -s.interface_name, -s.ip6_address, -s.ip6_gateway, -s.ip_address, -s.ip_gateway, -s.ip_netmask, + ARRAY ( +SELECT ( json_build_object ( +'ipAddresses', ARRAY ( +SELECT ( json_build_object ( +'address', ip_address.address, +'gateway', ip_address.gateway, +'serviceAddress', ip_address.service_address +)) +FROM ip_address +WHERE ip_address.interface = interface.name +AND ip_address.server = s.id +), +'max_bandwidth', interface.max_bandwidth, +'monitor', interface.monitor, +'mtu', COALESCE (interface.mtu, 9000), +'name', interface.name +)) +FROM interface +WHERE interface.server = s.id +) AS interfaces, s.last_updated, s.mgmt_ip_address, s.mgmt_ip_gateway, @@ -132,6 +164,7 @@ AND (t.name LIKE 'EDGE%' OR t.name LIKE 'ORG%') servers := []tc.DSServer{} for rows.Next() { + serverInterfaceInfo := []tc.ServerInterfaceInfo{} s := tc.DSServer{} err := rows.Scan( &s.Cachegroup, @@ -148,13 +181,7 @@ AND (t.name LIKE 'EDGE%' OR t.name LIKE 'ORG%') &s.ILOIPNetmask, &s.ILOPassword, &s.ILOUsername, - &s.InterfaceMtu, - &s.InterfaceName, - &s.IP6Address, - &s.IP6Gateway, - &s.IPAddress, - &s.IPGateway, - &s.IPNetmask, + pq.Array(&serverInterfaceInfo), &s.LastUpdated, &s.MgmtIPAddress, &s.MgmtIPGateway, @@ -180,6 +207,8 @@ AND (t.name LIKE 'EDGE%' OR t.name LIKE 'ORG%') if err != nil { return nil, errors.New("scanning delivery service eligible servers: " + err.Error()) } + s.ServerInterfaces = &serverInterfaceInfo + eligible := true if !strings.HasPrefix(s.Type, "ORG") { diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/eligible_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/eligible_test.go new file mode 100644 index 0000000000..f8d0c4101b --- /dev/null +++ b/traffic_ops/traffic_ops_golang/deliveryservice/eligible_test.go @@ -0,0 +1,156 @@ +package deliveryservice + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/jmoiron/sqlx" + + "gopkg.in/DATA-DOG/go-sqlmock.v1" +) + +func TestGetEligibleServers(t *testing.T) { + mockDB, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer mockDB.Close() + + db := sqlx.NewDb(mockDB, "sqlmock") + defer db.Close() + + testServers := getMockDSServers() + cols := []string{"cachegroup", + "cachegroup_id", + "cdn_id", + "cdn_name", + "domain_name", + "guid", + "host_name", + "https_port", + "id", + "ilo_ip_address", + "ilo_ip_gateway", + "ilo_ip_netmask", + "ilo_password", + "ilo_username", + "interfaces", + "last_updated", + "mgmt_ip_address", + "mgmt_ip_gateway", + "mgmt_ip_netmask", + "offline_reason", + "phys_location", + "phys_location_id", + "profile", + "profile_desc", + "profile_id", + "rack", + "router_host_name", + "router_port_name", + "status", + "status_id", + "tcp_port", + "server_type", + "server_type_id", + "upd_pending", + "server_capabilities", + "deliveryservice_capabilities"} + + rows := sqlmock.NewRows(cols) + + for _, s := range testServers { + rows = rows.AddRow( + s.Cachegroup, + s.CachegroupID, + s.CDNID, + s.CDNName, + s.DomainName, + s.GUID, + s.HostName, + s.HTTPSPort, + s.ID, + s.ILOIPAddress, + s.ILOIPGateway, + s.ILOIPNetmask, + s.ILOPassword, + s.ILOUsername, + []byte(`{"{\"ipAddresses\" : [{\"address\" : \"127.0.0.0\", \"gateway\" : null, \"service_address\" : true}], \"max_bandwidth\" : null, \"monitor\" : true, \"mtu\" : 1500, \"name\" : \"eth0\"}"}`), + s.LastUpdated, + s.MgmtIPAddress, + s.MgmtIPGateway, + s.MgmtIPNetmask, + s.OfflineReason, + s.PhysLocation, + s.PhysLocationID, + s.Profile, + s.ProfileDesc, + s.ProfileID, + s.Rack, + s.RouterHostName, + s.RouterPortName, + s.Status, + s.StatusID, + s.TCPPort, + s.Type, + s.TypeID, + s.UpdPending, + []byte(`{""}`), + []byte(`{""}`), + ) + } + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectCommit() + + actualSrvs, err := getEligibleServers(db.MustBegin().Tx, 1) + if err != nil { + t.Fatalf("an error '%s' occurred during read", err) + } + + if len(actualSrvs) != 1 { + t.Fatalf("servers.read expected len(actualSrvs) == 1, actual = %v", len(actualSrvs)) + } + + srvInts := *(actualSrvs[0]).ServerInterfaces + if len(srvInts) != 1 { + t.Fatalf("servers.read expected len(srvInts) == 1, actual = %v", len(srvInts)) + } + + if len(srvInts[0].IPAddresses) != 1 { + t.Fatalf("servers.read expected len(srvInts[0].IPAddresses) == 1, actual = %v", len(srvInts[0].IPAddresses)) + } +} + +func getMockDSServers() []tc.DSServer { + base := tc.DSServerBase{ + Cachegroup: util.StrPtr("cgTest"), + CachegroupID: util.IntPtr(1), + CDNID: util.IntPtr(1), + CDNName: util.StrPtr("cdnTest"), + DomainName: util.StrPtr("domain"), + } + srv := tc.DSServer{ + base, + &[]tc.ServerInterfaceInfo{}, // left empty because it must be written as json above since sqlmock does not support nested arrays + } + srvsExpected := []tc.DSServer{srv} + return srvsExpected +} diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go index 99d4bc4513..69fb684af0 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go @@ -38,7 +38,7 @@ import ( "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant" - validation "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation" "github.com/jmoiron/sqlx" "github.com/lib/pq" ) @@ -523,7 +523,28 @@ func getRead(w http.ResponseWriter, r *http.Request, unassigned bool, alerts tc. api.WriteAlerts(w, r, http.StatusInternalServerError, alerts) return } - api.WriteAlertsObj(w, r, 200, alerts, servers) + + if inf.Version.Major < 3 { + v11ServerList := []tc.DSServerV11{} + for _, srv := range servers { + v11server := tc.DSServerV11{} + v11server.DSServerBase = srv.DSServerBase + + interfaces := *srv.ServerInterfaces + legacyInterface, err := tc.InterfaceInfoToLegacyInterfaces(interfaces) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("converting to server detail v11: "+err.Error())) + return + } + v11server.LegacyInterfaceDetails = legacyInterface + + v11ServerList = append(v11ServerList, v11server) + } + api.WriteAlertsObj(w, r, http.StatusOK, alerts, v11ServerList) + return + } + + api.WriteAlertsObj(w, r, http.StatusOK, alerts, servers) } func read(tx *sqlx.Tx, dsID int, user *auth.CurrentUser, unassigned bool) ([]tc.DSServer, error) { @@ -541,10 +562,49 @@ func read(tx *sqlx.Tx, dsID int, user *auth.CurrentUser, unassigned bool) ([]tc. servers := []tc.DSServer{} for rows.Next() { + serverInterfaceInfo := []tc.ServerInterfaceInfo{} s := tc.DSServer{} - if err = rows.StructScan(&s); err != nil { + err := rows.Scan( + &s.Cachegroup, + &s.CachegroupID, + &s.CDNID, + &s.CDNName, + &s.DomainName, + &s.GUID, + &s.HostName, + &s.HTTPSPort, + &s.ID, + &s.ILOIPAddress, + &s.ILOIPGateway, + &s.ILOIPNetmask, + &s.ILOPassword, + &s.ILOUsername, + pq.Array(&serverInterfaceInfo), + &s.LastUpdated, + &s.MgmtIPAddress, + &s.MgmtIPGateway, + &s.MgmtIPNetmask, + &s.OfflineReason, + &s.PhysLocation, + &s.PhysLocationID, + &s.Profile, + &s.ProfileDesc, + &s.ProfileID, + &s.Rack, + &s.RouterHostName, + &s.RouterPortName, + &s.Status, + &s.StatusID, + &s.TCPPort, + &s.Type, + &s.TypeID, + &s.UpdPending, + ) + if err != nil { return nil, errors.New("error scanning dss rows: " + err.Error()) } + s.ServerInterfaces = &serverInterfaceInfo + if user.PrivLevel < auth.PrivLevelAdmin { s.ILOPassword = util.StrPtr("") } @@ -574,13 +634,26 @@ func dssSelectQuery() string { s.ilo_ip_netmask, s.ilo_password, s.ilo_username, - COALESCE(s.interface_mtu, ` + strconv.Itoa(JumboFrameBPS) + `) as interface_mtu, - s.interface_name, - s.ip6_address, - s.ip6_gateway, - s.ip_address, - s.ip_gateway, - s.ip_netmask, + ARRAY ( +SELECT ( json_build_object ( +'ipAddresses', ARRAY ( +SELECT ( json_build_object ( +'address', ip_address.address, +'gateway', ip_address.gateway, +'serviceAddress', ip_address.service_address +)) +FROM ip_address +WHERE ip_address.interface = interface.name +AND ip_address.server = s.id +), +'max_bandwidth', interface.max_bandwidth, +'monitor', interface.monitor, +'mtu', COALESCE (interface.mtu, 9000), +'name', interface.name +)) +FROM interface +WHERE interface.server = s.id +) AS interfaces, s.last_updated, s.mgmt_ip_address, s.mgmt_ip_gateway, diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go index 319dfc5973..0754bee705 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go @@ -20,8 +20,15 @@ package servers */ import ( - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" + "github.com/jmoiron/sqlx" + + "gopkg.in/DATA-DOG/go-sqlmock.v1" ) func TestValidateDSSAssignments(t *testing.T) { @@ -51,3 +58,129 @@ func TestValidateDSSAssignments(t *testing.T) { t.Fatalf("Expected no user error, got %v", userErr.Error()) } } + +func TestReadServers(t *testing.T) { + mockDB, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer mockDB.Close() + + db := sqlx.NewDb(mockDB, "sqlmock") + defer db.Close() + + testServers := getMockDSServers() + cols := []string{"cachegroup", + "cachegroup_id", + "cdn_id", + "cdn_name", + "domain_name", + "guid", + "host_name", + "https_port", + "id", + "ilo_ip_address", + "ilo_ip_gateway", + "ilo_ip_netmask", + "ilo_password", + "ilo_username", + "interfaces", + "last_updated", + "mgmt_ip_address", + "mgmt_ip_gateway", + "mgmt_ip_netmask", + "offline_reason", + "phys_location", + "phys_location_id", + "profile", + "profile_desc", + "profile_id", + "rack", + "router_host_name", + "router_port_name", + "status", + "status_id", + "tcp_port", + "server_type", + "server_type_id", + "upd_pending"} + + rows := sqlmock.NewRows(cols) + + for _, s := range testServers { + rows = rows.AddRow( + s.Cachegroup, + s.CachegroupID, + s.CDNID, + s.CDNName, + s.DomainName, + s.GUID, + s.HostName, + s.HTTPSPort, + s.ID, + s.ILOIPAddress, + s.ILOIPGateway, + s.ILOIPNetmask, + s.ILOPassword, + s.ILOUsername, + []byte(`{"{\"ipAddresses\" : [{\"address\" : \"127.0.0.0\", \"gateway\" : null, \"service_address\" : true}], \"max_bandwidth\" : null, \"monitor\" : true, \"mtu\" : 1500, \"name\" : \"eth0\"}"}`), + s.LastUpdated, + s.MgmtIPAddress, + s.MgmtIPGateway, + s.MgmtIPNetmask, + s.OfflineReason, + s.PhysLocation, + s.PhysLocationID, + s.Profile, + s.ProfileDesc, + s.ProfileID, + s.Rack, + s.RouterHostName, + s.RouterPortName, + s.Status, + s.StatusID, + s.TCPPort, + s.Type, + s.TypeID, + s.UpdPending, + ) + } + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectCommit() + + actualSrvs, err := read(db.MustBegin(), 1, &auth.CurrentUser{PrivLevel: 30}, false) + if err != nil { + t.Fatalf("an error '%s' occurred during read", err) + } + + if len(actualSrvs) != 1 { + t.Fatalf("servers.read expected len(actualSrvs) == 1, actual = %v", len(actualSrvs)) + } + + srvInts := *(actualSrvs[0]).ServerInterfaces + if len(srvInts) != 1 { + t.Fatalf("servers.read expected len(srvInts) == 1, actual = %v", len(srvInts)) + } + + if len(srvInts[0].IPAddresses) != 1 { + t.Fatalf("servers.read expected len(srvInts[0].IPAddresses) == 1, actual = %v", len(srvInts[0].IPAddresses)) + } +} + +func getMockDSServers() []tc.DSServer { + base := tc.DSServerBase{ + Cachegroup: util.StrPtr("cgTest"), + CachegroupID: util.IntPtr(1), + CDNID: util.IntPtr(1), + CDNName: util.StrPtr("cdnTest"), + DomainName: util.StrPtr("domain"), + } + srv := tc.DSServer{ + base, + &[]tc.ServerInterfaceInfo{}, // left empty because it must be written as json above since sqlmock does not support nested arrays + } + srvsExpected := []tc.DSServer{srv} + return srvsExpected +} diff --git a/traffic_ops/traffic_ops_golang/server/detail_test.go b/traffic_ops/traffic_ops_golang/server/detail_test.go index 3b937f8d8d..1eeaffb191 100644 --- a/traffic_ops/traffic_ops_golang/server/detail_test.go +++ b/traffic_ops/traffic_ops_golang/server/detail_test.go @@ -127,7 +127,7 @@ func TestGetDetailServers(t *testing.T) { } if len(srvInts[0].IPAddresses) != 1 { - t.Fatalf("servers.read expected len(srvInts[0].IpAddresses) == 1, actual = %v", len(srvInts[0].IPAddresses)) + t.Fatalf("servers.read expected len(srvInts[0].IPAddresses) == 1, actual = %v", len(srvInts[0].IPAddresses)) } if len(actualSrvs[0].HardwareInfo) != 3 { @@ -135,7 +135,7 @@ func TestGetDetailServers(t *testing.T) { } if !srvInts[0].IPAddresses[0].ServiceAddress { - t.Fatalf("srvInts[0].IpAddresses[0].ServiceAddress expected to be true, actual = %v", srvInts[0].IPAddresses[0].ServiceAddress) + t.Fatalf("srvInts[0].IPAddresses[0].ServiceAddress expected to be true, actual = %v", srvInts[0].IPAddresses[0].ServiceAddress) } } diff --git a/traffic_ops/v1-client/deliveryservice.go b/traffic_ops/v1-client/deliveryservice.go index 5fb7300b48..9f99b813a3 100644 --- a/traffic_ops/v1-client/deliveryservice.go +++ b/traffic_ops/v1-client/deliveryservice.go @@ -345,10 +345,10 @@ func (to *Session) GetDeliveryServiceMatches() ([]tc.DeliveryServicePatterns, Re return resp.Response, reqInf, nil } -func (to *Session) GetDeliveryServicesEligible(dsID int) ([]tc.DSServer, ReqInf, error) { +func (to *Session) GetDeliveryServicesEligible(dsID int) ([]tc.DSServerV11, ReqInf, error) { resp := struct { - Response []tc.DSServer `json:"response"` - }{Response: []tc.DSServer{}} + Response []tc.DSServerV11 `json:"response"` + }{Response: []tc.DSServerV11{}} uri := apiBase + `/deliveryservices/` + strconv.Itoa(dsID) + `/servers/eligible` reqInf, err := get(to, uri, &resp) if err != nil { diff --git a/traffic_ops/v2-client/deliveryservice.go b/traffic_ops/v2-client/deliveryservice.go index 7191391caa..e56aaed40a 100644 --- a/traffic_ops/v2-client/deliveryservice.go +++ b/traffic_ops/v2-client/deliveryservice.go @@ -317,10 +317,10 @@ func (to *Session) GetDeliveryServiceSSLKeysByID(XMLID string) (*tc.DeliveryServ // GetDeliveryServicesEligible returns the servers eligible for assignment to the Delivery // Service identified by the integral, unique identifier 'dsID'. -func (to *Session) GetDeliveryServicesEligible(dsID int) ([]tc.DSServer, ReqInf, error) { +func (to *Session) GetDeliveryServicesEligible(dsID int) ([]tc.DSServerV11, ReqInf, error) { resp := struct { - Response []tc.DSServer `json:"response"` - }{Response: []tc.DSServer{}} + Response []tc.DSServerV11 `json:"response"` + }{Response: []tc.DSServerV11{}} reqInf, err := get(to, fmt.Sprintf(API_DELIVERY_SERVICE_ELIGIBLE_SERVERS, dsID), &resp) if err != nil {