blob: ee1f6120b0fb8af51b9f260739c270aa50238b93 [file] [log] [blame]
/*
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.
*/
package client
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
)
const (
API_SERVERS = apiBase + "/servers"
API_SERVERS_DETAILS = apiBase + "/servers/details"
API_SERVER_ASSIGN_DELIVERY_SERVICES = API_SERVER_DELIVERY_SERVICES + "?replace=%t"
)
func needAndCanFetch(id *int, name *string) bool {
return (id == nil || *id == 0) && name != nil && *name != ""
}
// CreateServer creates a Server.
func (to *Session) CreateServer(server tc.ServerNullable) (tc.Alerts, ReqInf, error) {
var alerts tc.Alerts
var remoteAddr net.Addr
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if needAndCanFetch(server.CachegroupID, server.Cachegroup) {
cg, _, err := to.GetCacheGroupNullableByName(*server.Cachegroup, nil)
if err != nil {
return alerts, reqInf, fmt.Errorf("no cachegroup named %s: %v", *server.Cachegroup, err)
}
if len(cg) == 0 {
return alerts, reqInf, fmt.Errorf("no cachegroup named %s", *server.Cachegroup)
}
if cg[0].ID == nil {
return alerts, reqInf, fmt.Errorf("Cachegroup named %s has a nil ID", *server.Cachegroup)
}
server.CachegroupID = cg[0].ID
}
if needAndCanFetch(server.CDNID, server.CDNName) {
c, _, err := to.GetCDNByName(*server.CDNName, nil)
if err != nil {
return alerts, reqInf, fmt.Errorf("no CDN named %s: %v", *server.CDNName, err)
}
if len(c) == 0 {
return alerts, reqInf, fmt.Errorf("no CDN named %s", *server.CDNName)
}
server.CDNID = &c[0].ID
}
if needAndCanFetch(server.PhysLocationID, server.PhysLocation) {
ph, _, err := to.GetPhysLocationByName(*server.PhysLocation, nil)
if err != nil {
return alerts, reqInf, fmt.Errorf("no physlocation named %s: %v", *server.PhysLocation, err)
}
if len(ph) == 0 {
return alerts, reqInf, fmt.Errorf("no physlocation named %s", *server.PhysLocation)
}
server.PhysLocationID = &ph[0].ID
}
if needAndCanFetch(server.ProfileID, server.Profile) {
pr, _, err := to.GetProfileByName(*server.Profile, nil)
if err != nil {
return alerts, reqInf, fmt.Errorf("no profile named %s: %v", *server.Profile, err)
}
if len(pr) == 0 {
return alerts, reqInf, fmt.Errorf("no profile named %s", *server.Profile)
}
server.ProfileID = &pr[0].ID
}
if needAndCanFetch(server.StatusID, server.Status) {
st, _, err := to.GetStatusByName(*server.Status, nil)
if err != nil {
return alerts, reqInf, fmt.Errorf("no status named %s: %v", *server.Status, err)
}
if len(st) == 0 {
return alerts, reqInf, fmt.Errorf("no status named %s", *server.Status)
}
server.StatusID = &st[0].ID
}
if (server.TypeID == nil || *server.TypeID == 0) && server.Type != "" {
ty, _, err := to.GetTypeByName(server.Type, nil)
if err != nil {
return alerts, reqInf, fmt.Errorf("no type named %s: %v", server.Type, err)
}
if len(ty) == 0 {
return alerts, reqInf, fmt.Errorf("no type named %s", server.Type)
}
server.TypeID = &ty[0].ID
}
reqBody, err := json.Marshal(server)
if err != nil {
return alerts, reqInf, err
}
resp, remoteAddr, err := to.request(http.MethodPost, API_SERVERS, reqBody, nil)
reqInf.RemoteAddr = remoteAddr
if err != nil {
return alerts, reqInf, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&alerts)
return alerts, reqInf, err
}
// UpdateServerByID updates a Server by ID.
func (to *Session) UpdateServerByID(id int, server tc.ServerNullable) (tc.Alerts, ReqInf, error) {
var alerts tc.Alerts
var remoteAddr net.Addr
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
reqBody, err := json.Marshal(server)
if err != nil {
return alerts, reqInf, err
}
route := fmt.Sprintf("%s/%d", API_SERVERS, id)
resp, remoteAddr, err := to.request(http.MethodPut, route, reqBody, nil)
reqInf.RemoteAddr = remoteAddr
if err != nil {
return alerts, reqInf, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&alerts)
return alerts, reqInf, err
}
// GetServers returns a list of Servers.
// The 'params' parameter can be used to optionally pass URL "query string
// parameters" in the request.
// It returns, in order, the API response that Traffic Ops returned, a request
// info object, and any error that occurred.
func (to *Session) GetServers(params *url.Values, header http.Header) (tc.ServersV3Response, ReqInf, error) {
route := API_SERVERS
if params != nil {
route += "?" + params.Encode()
}
var data tc.ServersV3Response
resp, remoteAddr, err := to.request(http.MethodGet, route, nil, header)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if resp != nil {
reqInf.StatusCode = resp.StatusCode
if reqInf.StatusCode == http.StatusNotModified {
return tc.ServersV3Response{}, reqInf, nil
}
}
if err != nil {
return data, reqInf, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&data)
return data, reqInf, err
}
// GetFirstServer returns the first server in a servers GET response.
// If no servers match, an error is returned.
// The 'params' parameter can be used to optionally pass URL "query string
// parameters" in the request.
// It returns, in order, the API response that Traffic Ops returned, a request
// info object, and any error that occurred.
func (to *Session) GetFirstServer(params *url.Values, header http.Header) (tc.ServerNullable, ReqInf, error) {
serversResponse, reqInf, err := to.GetServers(params, header)
var firstServer tc.ServerNullable
if err != nil || reqInf.StatusCode == http.StatusNotModified {
return firstServer, reqInf, err
}
for _, firstServer = range serversResponse.Response {
return firstServer, reqInf, err
}
err = fmt.Errorf("unable to find server matching params %v", *params)
return firstServer, reqInf, err
}
// GetServerDetailsByHostName GETs Servers by the Server hostname.
func (to *Session) GetServerDetailsByHostName(hostName string, header http.Header) ([]tc.ServerDetailV30, ReqInf, error) {
v := url.Values{}
v.Add("hostName", hostName)
url := API_SERVERS_DETAILS + "?" + v.Encode()
resp, remoteAddr, err := to.request(http.MethodGet, url, nil, header)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if resp != nil {
reqInf.StatusCode = resp.StatusCode
if reqInf.StatusCode == http.StatusNotModified {
return []tc.ServerDetailV30{}, reqInf, nil
}
}
if err != nil {
return nil, reqInf, err
}
defer resp.Body.Close()
var data tc.ServersV3DetailResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, reqInf, err
}
return data.Response, reqInf, nil
}
// DeleteServerByID DELETEs a Server by ID.
func (to *Session) DeleteServerByID(id int) (tc.Alerts, ReqInf, error) {
route := fmt.Sprintf("%s/%d", API_SERVERS, id)
resp, remoteAddr, err := to.request(http.MethodDelete, route, nil, nil)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if err != nil {
return tc.Alerts{}, reqInf, err
}
defer resp.Body.Close()
var alerts tc.Alerts
err = json.NewDecoder(resp.Body).Decode(&alerts)
return alerts, reqInf, nil
}
// GetServerFQDN returns the Fully Qualified Domain Name (FQDN) of the first
// server found to have the Host Name 'n'.
func (to *Session) GetServerFQDN(n string, header http.Header) (string, tc.Alerts, ReqInf, error) {
// TODO fix to only request one server
params := url.Values{}
params.Add("hostName", n)
resp, reqInf, err := to.GetServers(&params, header)
if err != nil {
return "", resp.Alerts, reqInf, err
}
var fdn string
for _, server := range resp.Response {
if server.HostName != nil && server.DomainName != nil {
fdn = fmt.Sprintf("%s.%s", *server.HostName, *server.DomainName)
}
}
if fdn == "" {
err = fmt.Errorf("No Server %s found", n)
}
return fdn, resp.Alerts, reqInf, err
}
// GetServersShortNameSearch returns all of the Host Names of servers that
// contain 'shortname'.
func (to *Session) GetServersShortNameSearch(shortname string, header http.Header) ([]string, tc.Alerts, ReqInf, error) {
var serverlst []string
resp, reqInf, err := to.GetServers(nil, header)
if err != nil {
return serverlst, resp.Alerts, reqInf, err
}
for _, server := range resp.Response {
if server.HostName != nil && strings.Contains(*server.HostName, shortname) {
serverlst = append(serverlst, *server.HostName)
}
}
if len(serverlst) == 0 {
err = errors.New("No Servers Found")
}
return serverlst, resp.Alerts, reqInf, err
}
// AssignDeliveryServiceIDsToServerID assigns a set of Delivery Services to a
// single server, optionally replacing any and all existing assignments.
// 'server' should be the requested server's ID, 'dsIDs' should be a slice of
// the requested Delivery Services' IDs. If 'replace' is 'true', existing
// assignments to the server will be replaced.
func (to *Session) AssignDeliveryServiceIDsToServerID(server int, dsIDs []int, replace bool) (tc.Alerts, ReqInf, error) {
// datatypes here match the library tc.Server's and tc.DeliveryService's ID fields
var remoteAddr net.Addr
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
endpoint := fmt.Sprintf(API_SERVER_ASSIGN_DELIVERY_SERVICES, server, replace)
reqBody, err := json.Marshal(dsIDs)
if err != nil {
return tc.Alerts{}, reqInf, err
}
resp, remoteAddr, err := to.request(http.MethodPost, endpoint, reqBody, nil)
if resp != nil {
reqInf.StatusCode = resp.StatusCode
}
if err != nil {
return tc.Alerts{}, reqInf, err
}
defer log.Close(resp.Body, "unable to close response body")
reqInf.RemoteAddr = remoteAddr
var alerts tc.Alerts
err = json.NewDecoder(resp.Body).Decode(&alerts)
return alerts, reqInf, err
}
// GetServerIDDeliveryServices returns all of the Delivery Services assigned to the server identified
// by the integral, unique identifier 'server'.
func (to *Session) GetServerIDDeliveryServices(server int, header http.Header) ([]tc.DeliveryServiceNullable, ReqInf, error) {
endpoint := fmt.Sprintf(API_SERVER_DELIVERY_SERVICES, server)
resp, remoteAddr, err := to.request(http.MethodGet, endpoint, nil, header)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if resp != nil {
reqInf.StatusCode = resp.StatusCode
}
if err != nil {
return nil, reqInf, err
}
defer resp.Body.Close()
var data tc.DeliveryServicesNullableResponse
err = json.NewDecoder(resp.Body).Decode(&data)
return data.Response, reqInf, err
}
// GetServerUpdateStatus GETs the Server Update Status by the Server hostname.
func (to *Session) GetServerUpdateStatus(hostName string, header http.Header) (tc.ServerUpdateStatus, ReqInf, error) {
path := API_SERVERS + `/` + hostName + `/update_status`
resp, remoteAddr, err := to.request(http.MethodGet, path, nil, header)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if err != nil {
return tc.ServerUpdateStatus{}, reqInf, err
}
defer resp.Body.Close()
data := []tc.ServerUpdateStatus{}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return tc.ServerUpdateStatus{}, reqInf, err
}
if len(data) == 0 {
return tc.ServerUpdateStatus{}, reqInf, errors.New("Traffic Ops returned no update statuses for that server")
}
return data[0], reqInf, nil
}