// Copyright 2018 NetApp, Inc. All Rights Reserved.
package azgo
import (
"bytes"
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"time"
tridentconfig "github.com/netapp/trident/config"
log "github.com/sirupsen/logrus"
)
type ZAPIRequest interface {
ToXML() (string, error)
}
type ZAPIResponseIterable interface {
NextTag() string
}
type ZapiRunner struct {
ManagementLIF string
SVM string
Username string
Password string
Secure bool
OntapiVersion string
DebugTraceFlags map[string]bool // Example: {"api":false, "method":true}
}
// SendZapi sends the provided ZAPIRequest to the Ontap system
func (o *ZapiRunner) SendZapi(r ZAPIRequest) (*http.Response, error) {
if o.DebugTraceFlags["method"] {
fields := log.Fields{"Method": "SendZapi", "Type": "ZapiRunner"}
log.WithFields(fields).Debug(">>>> SendZapi")
defer log.WithFields(fields).Debug("<<<< SendZapi")
}
zapiCommand, err := r.ToXML()
if err != nil {
return nil, err
}
var s = ""
if o.SVM == "" {
s = fmt.Sprintf(`
%s
`, zapiCommand)
} else {
s = fmt.Sprintf(`
%s
`, "vfiler=\""+o.SVM+"\"", zapiCommand)
}
if o.DebugTraceFlags["api"] {
log.Debugf("sending to '%s' xml: \n%s", o.ManagementLIF, s)
}
url := "http://" + o.ManagementLIF + "/servlets/netapp.servlets.admin.XMLrequest_filer"
if o.Secure {
url = "https://" + o.ManagementLIF + "/servlets/netapp.servlets.admin.XMLrequest_filer"
}
if o.DebugTraceFlags["api"] {
log.Debugf("URL:> %s", url)
}
b := []byte(s)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/xml")
req.SetBasicAuth(o.Username, o.Password)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Transport: tr,
Timeout: time.Duration(tridentconfig.StorageAPITimeoutSeconds * time.Second),
}
response, err := client.Do(req)
if err != nil {
return nil, err
} else if response.StatusCode == 401 {
return nil, errors.New("response code 401 (Unauthorized): incorrect or missing credentials")
}
if o.DebugTraceFlags["api"] {
log.Debugf("response Status: %s", response.Status)
log.Debugf("response Headers: %s", response.Header)
}
return response, err
}
// ExecuteUsing converts this object to a ZAPI XML representation and uses the supplied ZapiRunner to send to a filer
func (o *ZapiRunner) ExecuteUsing(z ZAPIRequest, requestType string, v interface{}) (interface{}, error) {
return o.ExecuteWithoutIteration(z, requestType, v)
}
// ExecuteWithoutIteration does not attempt to perform any nextTag style iteration
func (o *ZapiRunner) ExecuteWithoutIteration(z ZAPIRequest, requestType string, v interface{}) (interface{}, error) {
if o.DebugTraceFlags["method"] {
fields := log.Fields{"Method": "ExecuteUsing", "Type": requestType}
log.WithFields(fields).Debug(">>>> ExecuteUsing")
defer log.WithFields(fields).Debug("<<<< ExecuteUsing")
}
resp, err := o.SendZapi(z)
if err != nil {
log.Errorf("API invocation failed. %v", err.Error())
return nil, err
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
log.Errorf("Error reading response body. %v", readErr.Error())
return nil, readErr
}
if o.DebugTraceFlags["api"] {
log.Debugf("response Body:\n%s", string(body))
}
//unmarshalErr := xml.Unmarshal(body, &v)
unmarshalErr := xml.Unmarshal(body, v)
if unmarshalErr != nil {
log.WithField("body", string(body)).Warnf("Error unmarshaling response body. %v", unmarshalErr.Error())
}
if o.DebugTraceFlags["api"] {
log.Debugf("%s result:\n%v", requestType, v)
}
return v, nil
}
// ToString implements a String() function via reflection
func ToString(val reflect.Value) string {
if reflect.TypeOf(val).Kind() == reflect.Ptr {
val = reflect.Indirect(val)
}
var buffer bytes.Buffer
if reflect.ValueOf(val).Kind() == reflect.Struct {
for i := 0; i < val.Type().NumField(); i++ {
fieldName := val.Type().Field(i).Name
fieldType := val.Type().Field(i)
fieldTag := fieldType.Tag
fieldValue := val.Field(i)
switch val.Field(i).Kind() {
case reflect.Ptr:
fieldValue = reflect.Indirect(val.Field(i))
default:
fieldValue = val.Field(i)
}
if fieldTag != "" {
xmlTag := fieldTag.Get("xml")
if xmlTag != "" {
fieldName = xmlTag
}
}
if fieldValue.IsValid() {
buffer.WriteString(fmt.Sprintf("%s: %v\n", fieldName, fieldValue))
} else {
buffer.WriteString(fmt.Sprintf("%s: %v\n", fieldName, "nil"))
}
}
}
return buffer.String()
}