2018-03-22 19:07:43 +03:00
/*
* testutilsqemuschema . c : helper functions for QEMU QAPI schema testing
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*/
# include <config.h>
# include "testutils.h"
2018-07-09 16:44:52 +03:00
# include "testutilsqemu.h"
2018-03-22 19:07:43 +03:00
# include "testutilsqemuschema.h"
# include "qemu/qemu_qapi.h"
static int
testQEMUSchemaValidateRecurse ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virHashTablePtr schema ,
virBufferPtr debug ) ;
static int
2018-07-12 14:19:33 +03:00
testQEMUSchemaValidateBuiltin ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virBufferPtr debug )
2018-03-22 19:07:43 +03:00
{
const char * t = virJSONValueObjectGetString ( root , " json-type " ) ;
const char * s = NULL ;
bool b = false ;
int ret = - 1 ;
if ( STREQ_NULLABLE ( t , " value " ) ) {
s = " {any} " ;
ret = 0 ;
goto cleanup ;
}
switch ( virJSONValueGetType ( obj ) ) {
case VIR_JSON_TYPE_STRING :
if ( STRNEQ_NULLABLE ( t , " string " ) )
goto cleanup ;
s = virJSONValueGetString ( obj ) ;
break ;
case VIR_JSON_TYPE_NUMBER :
if ( STRNEQ_NULLABLE ( t , " int " ) & &
STRNEQ_NULLABLE ( t , " number " ) )
goto cleanup ;
s = " {number} " ;
break ;
case VIR_JSON_TYPE_BOOLEAN :
if ( STRNEQ_NULLABLE ( t , " boolean " ) )
goto cleanup ;
virJSONValueGetBoolean ( obj , & b ) ;
if ( b )
s = " true " ;
else
s = " false " ;
break ;
case VIR_JSON_TYPE_NULL :
if ( STRNEQ_NULLABLE ( t , " null " ) )
goto cleanup ;
break ;
case VIR_JSON_TYPE_OBJECT :
case VIR_JSON_TYPE_ARRAY :
goto cleanup ;
}
ret = 0 ;
cleanup :
if ( ret = = 0 )
virBufferAsprintf ( debug , " '%s': OK " , s ) ;
else
virBufferAsprintf ( debug , " ERROR: expected type '%s', actual type %d " ,
t , virJSONValueGetType ( obj ) ) ;
return ret ;
}
struct testQEMUSchemaValidateObjectMemberData {
virJSONValuePtr rootmembers ;
virHashTablePtr schema ;
virBufferPtr debug ;
bool missingMandatory ;
} ;
static virJSONValuePtr
testQEMUSchemaStealObjectMemberByName ( const char * name ,
virJSONValuePtr members )
{
virJSONValuePtr member ;
virJSONValuePtr ret = NULL ;
size_t i ;
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( members ) ; i + + ) {
2018-03-22 19:07:43 +03:00
member = virJSONValueArrayGet ( members , i ) ;
if ( STREQ_NULLABLE ( name , virJSONValueObjectGetString ( member , " name " ) ) ) {
ret = virJSONValueArraySteal ( members , i ) ;
break ;
}
}
return ret ;
}
static int
testQEMUSchemaValidateObjectMember ( const char * key ,
virJSONValuePtr value ,
void * opaque )
{
struct testQEMUSchemaValidateObjectMemberData * data = opaque ;
virJSONValuePtr keymember = NULL ;
const char * keytype ;
virJSONValuePtr keyschema = NULL ;
int ret = - 1 ;
virBufferStrcat ( data - > debug , key , " : " , NULL ) ;
/* lookup 'member' entry for key */
if ( ! ( keymember = testQEMUSchemaStealObjectMemberByName ( key , data - > rootmembers ) ) ) {
virBufferAddLit ( data - > debug , " ERROR: attribute not in schema " ) ;
goto cleanup ;
}
/* lookup schema entry for keytype */
if ( ! ( keytype = virJSONValueObjectGetString ( keymember , " type " ) ) | |
! ( keyschema = virHashLookup ( data - > schema , keytype ) ) ) {
virBufferAsprintf ( data - > debug , " ERROR: can't find schema for type '%s' " ,
NULLSTR ( keytype ) ) ;
ret = - 2 ;
goto cleanup ;
}
/* recurse */
ret = testQEMUSchemaValidateRecurse ( value , keyschema , data - > schema ,
data - > debug ) ;
cleanup :
virBufferAddLit ( data - > debug , " \n " ) ;
virJSONValueFree ( keymember ) ;
return ret ;
}
static int
testQEMUSchemaValidateObjectMergeVariantMember ( size_t pos ATTRIBUTE_UNUSED ,
virJSONValuePtr item ,
void * opaque )
{
virJSONValuePtr array = opaque ;
virJSONValuePtr copy ;
if ( ! ( copy = virJSONValueCopy ( item ) ) )
return - 1 ;
if ( virJSONValueArrayAppend ( array , copy ) < 0 )
return - 1 ;
return 1 ;
}
/**
* testQEMUSchemaValidateObjectMergeVariant :
*
* Merges schema of variant @ variantname in @ root into @ root and removes the
* ' variants ' array from @ root .
*/
static int
testQEMUSchemaValidateObjectMergeVariant ( virJSONValuePtr root ,
const char * variantfield ,
const char * variantname ,
virHashTablePtr schema ,
virBufferPtr debug )
{
size_t i ;
virJSONValuePtr variants = NULL ;
virJSONValuePtr variant ;
virJSONValuePtr variantschema ;
virJSONValuePtr variantschemamembers ;
virJSONValuePtr rootmembers ;
const char * varianttype = NULL ;
int ret = - 1 ;
if ( ! ( variants = virJSONValueObjectStealArray ( root , " variants " ) ) ) {
virBufferAddLit ( debug , " ERROR: missing 'variants' in schema \n " ) ;
return - 2 ;
}
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( variants ) ; i + + ) {
2018-03-22 19:07:43 +03:00
variant = virJSONValueArrayGet ( variants , i ) ;
if ( STREQ_NULLABLE ( variantname ,
virJSONValueObjectGetString ( variant , " case " ) ) ) {
varianttype = virJSONValueObjectGetString ( variant , " type " ) ;
break ;
}
}
if ( ! varianttype ) {
virBufferAsprintf ( debug , " ERROR: variant '%s' for discriminator '%s' not found \n " ,
variantname , variantfield ) ;
goto cleanup ;
}
if ( ! ( variantschema = virHashLookup ( schema , varianttype ) ) | |
! ( variantschemamembers = virJSONValueObjectGetArray ( variantschema , " members " ) ) ) {
virBufferAsprintf ( debug ,
" ERROR: missing schema or schema members for variant '%s'(%s) \n " ,
variantname , varianttype ) ;
ret = - 2 ;
goto cleanup ;
}
rootmembers = virJSONValueObjectGetArray ( root , " members " ) ;
if ( virJSONValueArrayForeachSteal ( variantschemamembers ,
testQEMUSchemaValidateObjectMergeVariantMember ,
rootmembers ) < 0 ) {
ret = - 2 ;
goto cleanup ;
}
ret = 0 ;
cleanup :
virJSONValueFree ( variants ) ;
return ret ;
}
static int
testQEMUSchemaValidateObjectMandatoryMember ( size_t pos ATTRIBUTE_UNUSED ,
virJSONValuePtr item ,
void * opaque ATTRIBUTE_UNUSED )
{
struct testQEMUSchemaValidateObjectMemberData * data = opaque ;
if ( virJSONValueObjectHasKey ( item , " default " ) ! = 1 ) {
virBufferAsprintf ( data - > debug , " ERROR: missing mandatory attribute '%s' \n " ,
NULLSTR ( virJSONValueObjectGetString ( item , " name " ) ) ) ;
data - > missingMandatory = true ;
}
return 1 ;
}
static int
testQEMUSchemaValidateObject ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virHashTablePtr schema ,
virBufferPtr debug )
{
struct testQEMUSchemaValidateObjectMemberData data = { NULL , schema ,
debug , false } ;
virJSONValuePtr localroot = NULL ;
const char * variantfield ;
const char * variantname ;
int ret = - 1 ;
if ( virJSONValueGetType ( obj ) ! = VIR_JSON_TYPE_OBJECT ) {
virBufferAddLit ( debug , " ERROR: not an object " ) ;
return - 1 ;
}
virBufferAddLit ( debug , " { \n " ) ;
virBufferAdjustIndent ( debug , 3 ) ;
/* copy schema */
if ( ! ( localroot = virJSONValueCopy ( root ) ) ) {
ret = - 2 ;
goto cleanup ;
}
/* remove variant */
if ( ( variantfield = virJSONValueObjectGetString ( localroot , " tag " ) ) ) {
if ( ! ( variantname = virJSONValueObjectGetString ( obj , variantfield ) ) ) {
virBufferAsprintf ( debug , " ERROR: missing variant discriminator attribute '%s' \n " ,
variantfield ) ;
goto cleanup ;
}
if ( testQEMUSchemaValidateObjectMergeVariant ( localroot , variantfield ,
variantname ,
schema , debug ) < 0 )
goto cleanup ;
}
/* validate members */
data . rootmembers = virJSONValueObjectGetArray ( localroot , " members " ) ;
if ( virJSONValueObjectForeachKeyValue ( obj ,
testQEMUSchemaValidateObjectMember ,
& data ) < 0 )
goto cleanup ;
/* check missing mandatory values */
if ( virJSONValueArrayForeachSteal ( data . rootmembers ,
testQEMUSchemaValidateObjectMandatoryMember ,
& data ) < 0 ) {
ret = - 2 ;
goto cleanup ;
}
if ( data . missingMandatory )
goto cleanup ;
virBufferAdjustIndent ( debug , - 3 ) ;
virBufferAddLit ( debug , " } OK " ) ;
ret = 0 ;
cleanup :
virJSONValueFree ( localroot ) ;
return ret ;
}
static int
testQEMUSchemaValidateEnum ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virBufferPtr debug )
{
const char * objstr ;
virJSONValuePtr values = NULL ;
virJSONValuePtr value ;
size_t i ;
if ( virJSONValueGetType ( obj ) ! = VIR_JSON_TYPE_STRING ) {
virBufferAddLit ( debug , " ERROR: not a string " ) ;
return - 1 ;
}
objstr = virJSONValueGetString ( obj ) ;
if ( ! ( values = virJSONValueObjectGetArray ( root , " values " ) ) ) {
virBufferAsprintf ( debug , " ERROR: missing enum values in schema '%s' " ,
NULLSTR ( virJSONValueObjectGetString ( root , " name " ) ) ) ;
return - 2 ;
}
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( values ) ; i + + ) {
2018-03-22 19:07:43 +03:00
value = virJSONValueArrayGet ( values , i ) ;
if ( STREQ_NULLABLE ( objstr , virJSONValueGetString ( value ) ) ) {
virBufferAsprintf ( debug , " '%s' OK " , NULLSTR ( objstr ) ) ;
return 0 ;
}
}
virBufferAsprintf ( debug , " ERROR: enum value '%s' is not in schema " ,
NULLSTR ( objstr ) ) ;
return - 1 ;
}
static int
testQEMUSchemaValidateArray ( virJSONValuePtr objs ,
virJSONValuePtr root ,
virHashTablePtr schema ,
virBufferPtr debug )
{
const char * elemtypename = virJSONValueObjectGetString ( root , " element-type " ) ;
virJSONValuePtr elementschema ;
virJSONValuePtr obj ;
size_t i ;
if ( virJSONValueGetType ( objs ) ! = VIR_JSON_TYPE_ARRAY ) {
virBufferAddLit ( debug , " ERROR: not an array \n " ) ;
return - 1 ;
}
if ( ! elemtypename | |
! ( elementschema = virHashLookup ( schema , elemtypename ) ) ) {
virBufferAsprintf ( debug , " ERROR: missing schema for array element type '%s' " ,
NULLSTR ( elemtypename ) ) ;
return - 2 ;
}
virBufferAddLit ( debug , " [ \n " ) ;
virBufferAdjustIndent ( debug , 3 ) ;
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( objs ) ; i + + ) {
2018-03-22 19:07:43 +03:00
obj = virJSONValueArrayGet ( objs , i ) ;
if ( testQEMUSchemaValidateRecurse ( obj , elementschema , schema , debug ) < 0 )
return - 1 ;
virBufferAddLit ( debug , " , \n " ) ;
}
virBufferAddLit ( debug , " ] OK " ) ;
virBufferAdjustIndent ( debug , - 3 ) ;
return 0 ;
}
static int
testQEMUSchemaValidateAlternate ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virHashTablePtr schema ,
virBufferPtr debug )
{
virJSONValuePtr members ;
virJSONValuePtr member ;
size_t i ;
2018-04-20 00:29:02 +03:00
size_t n ;
2018-03-22 19:07:43 +03:00
const char * membertype ;
virJSONValuePtr memberschema ;
int indent ;
int rc ;
if ( ! ( members = virJSONValueObjectGetArray ( root , " members " ) ) ) {
virBufferAddLit ( debug , " ERROR: missing 'members' for alternate schema " ) ;
return - 2 ;
}
virBufferAddLit ( debug , " ( \n " ) ;
virBufferAdjustIndent ( debug , 3 ) ;
indent = virBufferGetIndent ( debug , false ) ;
n = virJSONValueArraySize ( members ) ;
for ( i = 0 ; i < n ; i + + ) {
membertype = NULL ;
/* P != NP */
virBufferAsprintf ( debug , " (alternate %zu/%zu) \n " , i + 1 , n ) ;
virBufferAdjustIndent ( debug , 3 ) ;
if ( ! ( member = virJSONValueArrayGet ( members , i ) ) | |
! ( membertype = virJSONValueObjectGetString ( member , " type " ) ) | |
! ( memberschema = virHashLookup ( schema , membertype ) ) ) {
virBufferAsprintf ( debug , " ERROR: missing schema for alternate type '%s' " ,
NULLSTR ( membertype ) ) ;
return - 2 ;
}
rc = testQEMUSchemaValidateRecurse ( obj , memberschema , schema , debug ) ;
virBufferAddLit ( debug , " \n " ) ;
virBufferSetIndent ( debug , indent ) ;
virBufferAsprintf ( debug , " (/alternate %zu/%zu) \n " , i + 1 , n ) ;
if ( rc = = 0 ) {
virBufferAdjustIndent ( debug , - 3 ) ;
virBufferAddLit ( debug , " ) OK " ) ;
return 0 ;
}
}
virBufferAddLit ( debug , " ERROR: no alternate type was matched " ) ;
return - 1 ;
}
static int
testQEMUSchemaValidateRecurse ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virHashTablePtr schema ,
virBufferPtr debug )
{
const char * n = virJSONValueObjectGetString ( root , " name " ) ;
const char * t = virJSONValueObjectGetString ( root , " meta-type " ) ;
if ( STREQ_NULLABLE ( t , " builtin " ) ) {
2018-07-12 14:19:33 +03:00
return testQEMUSchemaValidateBuiltin ( obj , root , debug ) ;
2018-03-22 19:07:43 +03:00
} else if ( STREQ_NULLABLE ( t , " object " ) ) {
return testQEMUSchemaValidateObject ( obj , root , schema , debug ) ;
} else if ( STREQ_NULLABLE ( t , " enum " ) ) {
return testQEMUSchemaValidateEnum ( obj , root , debug ) ;
} else if ( STREQ_NULLABLE ( t , " array " ) ) {
return testQEMUSchemaValidateArray ( obj , root , schema , debug ) ;
} else if ( STREQ_NULLABLE ( t , " alternate " ) ) {
return testQEMUSchemaValidateAlternate ( obj , root , schema , debug ) ;
}
virBufferAsprintf ( debug ,
" qapi schema meta-type '%s' of type '%s' not handled \n " ,
NULLSTR ( t ) , NULLSTR ( n ) ) ;
return - 2 ;
}
/**
* testQEMUSchemaValidate :
* @ obj : object to validate
* @ root : schema entry to start from
* @ schema : hash table containing schema entries
* @ debug : a virBuffer which will be filled with debug information if provided
*
* Validates whether @ obj conforms to the QAPI schema passed in via @ schema ,
* starting from the node @ root . Returns 0 , if @ obj matches @ schema , - 1 if it
* does not and - 2 if there is a problem with the schema or with internals .
*
* @ debug is filled with information regarding the validation process
*/
int
testQEMUSchemaValidate ( virJSONValuePtr obj ,
virJSONValuePtr root ,
virHashTablePtr schema ,
virBufferPtr debug )
{
return testQEMUSchemaValidateRecurse ( obj , root , schema , debug ) ;
}
2018-07-09 16:44:52 +03:00
/**
* testQEMUSchemaGetLatest :
*
* Returns the schema data as the qemu monitor would reply from the latest
* replies file used for qemucapabilitiestest for the x86_64 architecture .
*/
virJSONValuePtr
testQEMUSchemaGetLatest ( void )
{
char * capsLatestFile = NULL ;
char * capsLatest = NULL ;
char * schemaReply ;
char * end ;
virJSONValuePtr reply = NULL ;
virJSONValuePtr schema = NULL ;
if ( ! ( capsLatestFile = testQemuGetLatestCapsForArch ( abs_srcdir " /qemucapabilitiesdata " ,
" x86_64 " , " replies " ) ) ) {
VIR_TEST_VERBOSE ( " failed to find latest caps replies \n " ) ;
return NULL ;
}
2018-07-12 14:20:34 +03:00
VIR_TEST_DEBUG ( " replies file: '%s' \n " , capsLatestFile ) ;
2018-07-09 16:44:52 +03:00
if ( virTestLoadFile ( capsLatestFile , & capsLatest ) < 0 )
goto cleanup ;
if ( ! ( schemaReply = strstr ( capsLatest , " \" execute \" : \" query-qmp-schema \" " ) ) | |
! ( schemaReply = strstr ( schemaReply , " \n \n " ) ) | |
! ( end = strstr ( schemaReply + 2 , " \n \n " ) ) ) {
VIR_TEST_VERBOSE ( " failed to find reply to 'query-qmp-schema' in '%s' \n " ,
capsLatestFile ) ;
goto cleanup ;
}
schemaReply + = 2 ;
* end = ' \0 ' ;
if ( ! ( reply = virJSONValueFromString ( schemaReply ) ) ) {
VIR_TEST_VERBOSE ( " failed to parse 'query-qmp-schema' reply from '%s' \n " ,
capsLatestFile ) ;
goto cleanup ;
}
if ( ! ( schema = virJSONValueObjectStealArray ( reply , " return " ) ) ) {
VIR_TEST_VERBOSE ( " missing qapi schema data in reply in '%s' \n " ,
capsLatestFile ) ;
goto cleanup ;
}
cleanup :
VIR_FREE ( capsLatestFile ) ;
VIR_FREE ( capsLatest ) ;
virJSONValueFree ( reply ) ;
return schema ;
}
2018-03-22 19:07:43 +03:00
virHashTablePtr
testQEMUSchemaLoad ( void )
{
2018-07-09 16:44:52 +03:00
virJSONValuePtr schema ;
2018-03-22 19:07:43 +03:00
2018-07-09 16:44:52 +03:00
if ( ! ( schema = testQEMUSchemaGetLatest ( ) ) )
2018-03-22 19:07:43 +03:00
return NULL ;
2018-07-09 16:44:52 +03:00
return virQEMUQAPISchemaConvert ( schema ) ;
2018-03-22 19:07:43 +03:00
}