1
0
mirror of https://github.com/OpenNebula/one.git synced 2024-12-22 13:33:52 +03:00

F #2772: GOCA - leverage use of current xmlrpc package and update error handling

This commit is contained in:
treywelsh 2019-01-14 14:43:16 +01:00 committed by Ruben S. Montero
parent e65dd8c251
commit 96e71ec99d
2 changed files with 218 additions and 92 deletions

View File

@ -0,0 +1,153 @@
package goca
import (
"fmt"
"net/http"
)
// Client errors
/*
The OpenNebula server use the library xmlrpc-c.
List of xmlrpc fault codes:
XMLRPC_INTERNAL_ERROR -500
XMLRPC_TYPE_ERROR -501
XMLRPC_INDEX_ERROR -502
XMLRPC_PARSE_ERROR -503
XMLRPC_NETWORK_ERROR -504
XMLRPC_TIMEOUT_ERROR -505
XMLRPC_NO_SUCH_METHOD_ERROR -506
XMLRPC_REQUEST_REFUSED_ERROR -507
XMLRPC_INTROSPECTION_DISABLED_ERROR -508
XMLRPC_LIMIT_EXCEEDED_ERROR -509
XMLRPC_INVALID_UTF8_ERROR -510
*/
// ClientErrCode is used by ClientError to give more accurate information
type ClientErrCode int
const (
// ClientReqBuild if we can't build the xmlrpc request, ie. http or xml error
ClientReqBuild ClientErrCode = iota
// ClientReqHTTP if we can't do a request, ie. connectivity, redirection problems...
ClientReqHTTP
// ClientRespHTTP if we have an http response error or if we can't have a full response
ClientRespHTTP
// ClientRespXMLRPCFault if the response is an xmlrpc fault
ClientRespXMLRPCFault
// ClientRespXMLRPCParse if we can't parse the xmlrpc from the response
ClientRespXMLRPCParse
// ClientRespONeParse if we can't parse a correct OpenNebula response
ClientRespONeParse
)
func (s ClientErrCode) String() string {
switch s {
case ClientReqBuild:
return "REQUEST_BUILD"
case ClientReqHTTP:
return "REQUEST_HTTP"
case ClientRespHTTP:
return "RESPONSE_HTTP"
case ClientRespXMLRPCFault:
return "RESPONSE_XMLRPC_FAULT"
case ClientRespXMLRPCParse:
return "RESPONSE_XMLRPC_PARSE"
case ClientRespONeParse:
return "RESPONSE_ONE_PARSE"
default:
return ""
}
}
// ClientError is returned when we can't have a complete and well formed response from the client
type ClientError struct {
Code ClientErrCode
msg string
// Provide more informations to the user
httpResp *http.Response
err error
}
// Cause allow to get the underlying error
func (e *ClientError) Cause() error {
return e.err
}
// GetHTTPResponse return the http response for the codes ClientRespXMLRPCFault, ClientRespXMLRPCParse, ClientRespONeParse
func (e *ClientError) GetHTTPResponse() *http.Response {
return e.httpResp
}
func (e *ClientError) Error() string {
if e.err != nil {
return fmt.Sprintf("GOCA client error [%s]: %s: %s", e.Code.String(), e.msg, e.Cause())
}
return fmt.Sprintf("GOCA client error [%s]: %s", e.Code.String(), e.msg)
}
// OpenNebula errors
// OneErrCode is the error code from an OpenNebula error response
type OneErrCode int
const (
// OneSuccess code for a successful response
OneSuccess = 0x0000
// OneAuthenticationError code if the user could not be authenticated
OneAuthenticationError = 0x0100
// OneAuthorizationError code if the user is not authorized to perform the requested action
OneAuthorizationError = 0x0200
// OneNoExistsError code if the requested resource does not exist
OneNoExistsError = 0x0400
// OneActionError code if the state is wrong to perform the action
OneActionError = 0x0800
// OneXMLRPCAPIError code if there is wrong parameter passed, e.g. param should be -1 or -2, but -3 was received
OneXMLRPCAPIError = 0x1000
// OneInternalError code if there is an internal error, e.g. the resource could not be loaded from the DB
OneInternalError = 0x2000
)
func (s OneErrCode) String() string {
switch s {
case OneSuccess:
return "SUCCESS"
case OneAuthenticationError:
return "AUTHENTICATION"
case OneAuthorizationError:
return "AUTHORIZATION"
case OneNoExistsError:
return "NO_EXISTS"
case OneActionError:
return "ACTION"
case OneXMLRPCAPIError:
return "XML_RPC_API"
case OneInternalError:
return "INTERNAL"
default:
return ""
}
}
// ResponseError is a complete well formed OpenNebula response error
type ResponseError struct {
Code OneErrCode
msg string
}
func (e *ResponseError) Error() string {
return fmt.Sprintf("OpenNebula error [%s]: %s", e.Code.String(), e.msg)
}

View File

@ -1,8 +1,10 @@
package goca
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
@ -13,53 +15,6 @@ var (
client *oneClient
)
// OneClientErrCode is the error code from the OpenNebula response
type OneClientErrCode int
const (
// OneSuccess code for a successful response
OneSuccess = 0x0000
// OneAuthenticationError code if the user could not be authenticated
OneAuthenticationError = 0x0100
// OneAuthorizationError code if the user is not authorized to perform the requested action
OneAuthorizationError = 0x0200
// OneNoExistsError code if the requested resource does not exist
OneNoExistsError = 0x0400
// OneActionError code if the state is wrong to perform the action
OneActionError = 0x0800
// OneXmlRpcApiError code if there is wrong parameter passed, e.g. param should be -1 or -2, but -3 was received
OneXMLRPCAPIError = 0x1000
// OneInteralError code if there is an internal error, e.g. the resource could not be loaded from the DB
OneInteralError = 0x2000
)
func (s OneClientErrCode) String() string {
switch s {
case OneSuccess:
return "SUCCESS"
case OneAuthenticationError:
return "AUTHENTICATION"
case OneAuthorizationError:
return "AUTHORIZATION"
case OneNoExistsError:
return "NO_EXISTS"
case OneActionError:
return "ACTION"
case OneXMLRPCAPIError:
return "XML_RPC_API"
case OneInteralError:
return "INTERNAL"
default:
return ""
}
}
// OneConfig contains the information to communicate with OpenNebula
type OneConfig struct {
// Token is the authentication string. In the format of <user>:<password>
@ -71,36 +26,9 @@ type OneConfig struct {
}
type oneClient struct {
url string
token string
xmlrpcClient *xmlrpc.Client
xmlrpcClientError error
}
type InitError struct {
token string
xmlRpcErr error
}
func (r *InitError) Error() string {
return fmt.Sprintf("Unitialized client. Token: '%s', xmlrpcClientError: '%s'", r.token, r.xmlRpcErr)
}
type BadResponseError struct {
expectedType string
}
func (r *BadResponseError) Error() string {
return fmt.Sprintf("Unexpected XML-RPC response, Expected: %s", r.expectedType)
}
// ResponseError contains the error datas from an OpenNebula error reponse
type ResponseError struct {
Code OneClientErrCode
msg string
}
func (e *ResponseError) Error() string {
return fmt.Sprintf("%s (%s)", e.msg, e.Code.String())
httpClient *http.Client
}
type response struct {
@ -166,12 +94,10 @@ func NewConfig(user string, password string, xmlrpcURL string) OneConfig {
// SetClient assigns a value to the client variable
func SetClient(conf OneConfig) {
xmlrpcClient, xmlrpcClientError := xmlrpc.NewClient(conf.XmlrpcURL, nil)
client = &oneClient{
url: conf.XmlrpcURL,
token: conf.Token,
xmlrpcClient: xmlrpcClient,
xmlrpcClientError: xmlrpcClientError,
httpClient: &http.Client{},
}
}
@ -207,25 +133,67 @@ func (c *oneClient) Call(method string, args ...interface{}) (*response, error)
errCode int64
)
if c.xmlrpcClientError != nil {
return nil, &InitError{token: c.token, xmlRpcErr: c.xmlrpcClientError}
}
result := []interface{}{}
xmlArgs := make([]interface{}, len(args)+1)
xmlArgs[0] = c.token
copy(xmlArgs[1:], args[:])
err := c.xmlrpcClient.Call(method, xmlArgs, &result)
buf, err := xmlrpc.EncodeMethodCall(method, xmlArgs...)
if err != nil {
return nil, err
return nil,
&ClientError{Code: ClientReqBuild, msg: "xmlrpc request encoding", err: err}
}
req, err := http.NewRequest("POST", c.url, bytes.NewBuffer(buf))
if err != nil {
return nil,
&ClientError{Code: ClientReqBuild, msg: "http request build", err: err}
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil,
&ClientError{Code: ClientReqHTTP, msg: "http make request", err: err}
}
if resp.StatusCode/100 != 2 {
return nil, &ClientError{
Code: ClientRespHTTP,
msg: fmt.Sprintf("http status code: %d", resp.StatusCode),
httpResp: resp,
}
}
respData, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil,
&ClientError{Code: ClientRespHTTP, msg: "read http response body", err: err}
}
// Server side XML-RPC library: xmlrpc-c
xmlrpcResp := xmlrpc.NewResponse(respData)
// Handle the <fault> tag in the xml server response
if xmlrpcResp.Failed() {
err = xmlrpcResp.Err()
return nil,
&ClientError{ClientRespXMLRPCFault, "server response", resp, err}
}
result := []interface{}{}
// Unmarshall the XML-RPC response
if err := xmlrpcResp.Unmarshal(&result); err != nil {
return nil,
&ClientError{ClientRespXMLRPCParse, "unmarshal xmlrpc", resp, err}
}
// Parse according the XML-RPC OpenNebula API documentation
status, ok = result[0].(bool)
if ok == false {
return nil, &BadResponseError{expectedType: "Index 0: Boolean"}
return nil,
&ClientError{ClientRespONeParse, "index 0: boolean expected", resp, err}
}
body, ok = result[1].(string)
@ -234,18 +202,23 @@ func (c *oneClient) Call(method string, args ...interface{}) (*response, error)
if ok == false {
bodyBool, ok = result[1].(bool)
if ok == false {
return nil, &BadResponseError{expectedType: "Index 1: Int or String"}
return nil,
&ClientError{ClientRespONeParse, "index 1: boolean expected", resp, err}
}
}
}
errCode, ok = result[2].(int64)
if ok == false {
return nil, &BadResponseError{expectedType: "Index 2: Int"}
return nil,
&ClientError{ClientRespONeParse, "index 2: boolean expected", resp, err}
}
if status == false {
return nil, &ResponseError{Code: OneClientErrCode(errCode), msg: body}
return nil, &ResponseError{
Code: OneErrCode(errCode),
msg: body,
}
}
r := &response{status, body, int(bodyInt), bodyBool}