package org.zstack.configuration;
import javassist.Modifier;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.zstack.core.Platform;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.config.GlobalConfig;
import org.zstack.core.config.GlobalConfigFacade;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.DbEntityLister;
import org.zstack.core.db.SQLBatchWithReturn;
import org.zstack.core.rest.RESTApiJsonTemplateGenerator;
import org.zstack.header.AbstractService;
import org.zstack.header.allocator.HostAllocatorConstant;
import org.zstack.header.allocator.HostAllocatorStrategyType;
import org.zstack.header.configuration.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.*;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.search.APIGetMessage;
import org.zstack.header.search.APISearchMessage;
import org.zstack.header.search.Inventory;
import org.zstack.header.search.SearchOp;
import org.zstack.header.storage.primary.PrimaryStorageConstant;
import org.zstack.header.vo.EO;
import org.zstack.header.vo.NoView;
import org.zstack.identity.AccountManager;
import org.zstack.search.GetQuery;
import org.zstack.search.SearchQuery;
import org.zstack.tag.TagManager;
import org.zstack.utils.*;
import org.zstack.utils.data.FieldPrinter;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.path.PathUtil;
import javax.persistence.Column;
import javax.persistence.Entity;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
public class ConfigurationManagerImpl extends AbstractService implements ConfigurationManager {
private static final CLogger logger = Utils.getLogger(ConfigurationManagerImpl.class);
private static final FieldPrinter printer = Utils.getFieldPrinter();
private static final Set<Class> allowedInstanceOfferingMessageAfterSoftDeletion = new HashSet<>();
private static final Set<Class> allowedDiskOfferingMessageAfterSoftDeletion = new HashSet<>();
static {
allowedDiskOfferingMessageAfterSoftDeletion.add(DiskOfferingDeletionMsg.class);
allowedInstanceOfferingMessageAfterSoftDeletion.add(InstanceOfferingDeletionMsg.class);
}
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private DbEntityLister dl;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private GlobalConfigFacade gcf;
@Autowired
private TagManager tagMgr;
@Autowired
private AccountManager acntMgr;
private Set<String> generatedPythonClassName;
private Map<String, InstanceOfferingFactory> instanceOfferingFactories = new HashMap<>();
private Map<String, DiskOfferingFactory> diskOfferingFactories = new HashMap<>();
private Set<String> generatedGroovyClassName = new HashSet<>();
private List<PythonApiBindingWriter> pythonApiBindingWriters = new ArrayList<>();
private void instanceOfferingPassThrough(InstanceOfferingMessage msg) {
InstanceOfferingVO vo = dbf.findByUuid(msg.getInstanceOfferingUuid(), InstanceOfferingVO.class);
if (vo == null && allowedInstanceOfferingMessageAfterSoftDeletion.contains(msg.getClass())) {
InstanceOfferingEO eo = dbf.findByUuid(msg.getInstanceOfferingUuid(), InstanceOfferingEO.class);
vo = ObjectUtils.newAndCopy(eo, InstanceOfferingVO.class);
}
if (vo == null) {
bus.replyErrorByMessageType((Message) msg, String.format("cannot find InstanceOffering[uuid:%s]," +
" it may have been deleted", msg.getInstanceOfferingUuid()));
return;
}
InstanceOfferingFactory factory = getInstanceOfferingFactory(vo.getType());
InstanceOffering offering = factory.getInstanceOffering(vo);
offering.handleMessage((Message) msg);
}
private void diskOfferingPassThrough(DiskOfferingMessage msg) {
DiskOfferingVO vo = dbf.findByUuid(msg.getDiskOfferingUuid(), DiskOfferingVO.class);
if (vo == null && allowedInstanceOfferingMessageAfterSoftDeletion.contains(msg.getClass())) {
DiskOfferingEO eo = dbf.findByUuid(msg.getDiskOfferingUuid(), DiskOfferingEO.class);
vo = ObjectUtils.newAndCopy(eo, DiskOfferingVO.class);
}
if (vo == null) {
bus.replyErrorByMessageType((Message) msg, String.format("cannot find DiskOffering[uuid:%s]," +
" it may have been deleted", msg.getDiskOfferingUuid()));
return;
}
DiskOfferingFactory factory = getDiskOfferingFactory(vo.getType());
DiskOffering offering = factory.getDiskOffering(vo);
offering.handleMessage((Message) msg);
}
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof InstanceOfferingMessage) {
instanceOfferingPassThrough((InstanceOfferingMessage) msg);
} else if (msg instanceof DiskOfferingMessage) {
diskOfferingPassThrough((DiskOfferingMessage) msg);
} else if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void handleLocalMessage(Message msg) {
bus.dealWithUnknownMessage(msg);
}
private void handleApiMessage(APIMessage msg) {
try {
if (msg instanceof APICreateInstanceOfferingMsg) {
handle((APICreateInstanceOfferingMsg) msg);
} else if (msg instanceof APIListInstanceOfferingMsg) {
handle((APIListInstanceOfferingMsg) msg);
} else if (msg instanceof APICreateDiskOfferingMsg) {
handle((APICreateDiskOfferingMsg) msg);
} else if (msg instanceof APIListDiskOfferingMsg) {
handle((APIListDiskOfferingMsg) msg);
} else if (msg instanceof APISearchInstanceOfferingMsg) {
handle((APISearchInstanceOfferingMsg) msg);
} else if (msg instanceof APISearchDiskOfferingMsg) {
handle((APISearchDiskOfferingMsg) msg);
} else if (msg instanceof APIGetInstanceOfferingMsg) {
handle((APIGetInstanceOfferingMsg) msg);
} else if (msg instanceof APIGetDiskOfferingMsg) {
handle((APIGetDiskOfferingMsg) msg);
} else if (msg instanceof APIGenerateApiJsonTemplateMsg) {
handle((APIGenerateApiJsonTemplateMsg) msg);
} else if (msg instanceof APIGenerateTestLinkDocumentMsg) {
handle((APIGenerateTestLinkDocumentMsg) msg);
} else if (msg instanceof APIGenerateGroovyClassMsg) {
handle((APIGenerateGroovyClassMsg) msg);
} else if (msg instanceof APIGenerateSqlVOViewMsg) {
handle((APIGenerateSqlVOViewMsg) msg);
} else if (msg instanceof APIGenerateApiTypeScriptDefinitionMsg) {
handle((APIGenerateApiTypeScriptDefinitionMsg) msg);
} else if (msg instanceof APIGenerateSqlForeignKeyMsg) {
handle((APIGenerateSqlForeignKeyMsg) msg);
} else if (msg instanceof APIGenerateSqlIndexMsg) {
handle((APIGenerateSqlIndexMsg) msg);
} else if (msg instanceof APIGetGlobalPropertyMsg) {
handle((APIGetGlobalPropertyMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
} catch (IOException e) {
throw new CloudRuntimeException(e);
}
}
private void handle(APIGetGlobalPropertyMsg msg) {
/*
Properties p = System.getProperties();
Enumeration keys = p.keys();
List<String> pps = new ArrayList<String>();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
String value = (String)p.get(key);
pps.add(String.format("%s = %s", key, value));
}
*/
List<String> pps = new ArrayList<>();
for (Map.Entry<String, String> e : Platform.getGlobalProperties().entrySet()) {
pps.add(String.format("%s: %s", e.getKey(), e.getValue()));
}
APIGetGlobalPropertyReply reply = new APIGetGlobalPropertyReply();
reply.setProperties(pps);
bus.reply(msg, reply);
}
private void handle(APIGenerateSqlIndexMsg msg) {
SqlIndexGenerator generator = new SqlIndexGenerator(msg);
generator.generate();
APIGenerateSqlIndexEvent evt = new APIGenerateSqlIndexEvent(msg.getId());
bus.publish(evt);
}
private void handle(APIGenerateSqlForeignKeyMsg msg) {
SqlForeignKeyGenerator generator = new SqlForeignKeyGenerator(msg);
generator.generate();
APIGenerateSqlForeignKeyEvent evt = new APIGenerateSqlForeignKeyEvent(msg.getId());
bus.publish(evt);
}
private void handle(APIGenerateApiTypeScriptDefinitionMsg msg) {
TypeScriptApiWriter writer = GroovyUtils.newInstance("scripts/TypeScriptApiWriterImpl.groovy", this.getClass().getClassLoader());
List<Class> apiMsgClass = BeanUtils.scanClassByType("org.zstack", APIMessage.class);
List<Class> apiEventClass = BeanUtils.scanClassByType("org.zstack", APIEvent.class);
List<Class> apiReplyClass = BeanUtils.scanClassByType("org.zstack", APIReply.class);
List<Class> inventoryClass = BeanUtils.scanClass("org.zstack", Inventory.class);
apiMsgClass = CollectionUtils.transformToList(apiMsgClass, new Function<Class, Class>() {
@Override
public Class call(Class arg) {
if (!java.lang.reflect.Modifier.isAbstract(arg.getModifiers())) {
return arg;
}
return null;
}
});
apiEventClass = CollectionUtils.transformToList(apiEventClass, new Function<Class, Class>() {
@Override
public Class call(Class arg) {
if (!java.lang.reflect.Modifier.isAbstract(arg.getModifiers())) {
return arg;
}
return null;
}
});
apiEventClass.addAll(apiReplyClass);
inventoryClass = CollectionUtils.transformToList(inventoryClass, new Function<Class, Class>() {
@Override
public Class call(Class arg) {
if (!java.lang.reflect.Modifier.isAbstract(arg.getModifiers())) {
return arg;
}
return null;
}
});
String exportPath = msg.getOutputPath() != null ?
msg.getOutputPath() : PathUtil.join(System.getProperty("user.home"), "zstack-api-typescript", "api.ts");
writer.write(exportPath, apiMsgClass, apiEventClass, inventoryClass);
APIGenerateApiTypeScriptDefinitionEvent evt = new APIGenerateApiTypeScriptDefinitionEvent(msg.getId());
bus.publish(evt);
}
private void generateVOViewSql(StringBuilder sb, Class<?> entityClass) {
if (!entityClass.isAnnotationPresent(Entity.class)) {
throw new IllegalArgumentException(String.format("class[%s] is annotated by @EO but not annotated by @Entity", entityClass.getName()));
}
EO at = entityClass.getAnnotation(EO.class);
if (!at.needView()) {
return;
}
Class EOClazz = at.EOClazz();
List<Field> fs = FieldUtils.getAllFields(entityClass);
sb.append(String.format("\nCREATE VIEW `zstack`.`%s` AS SELECT ", entityClass.getSimpleName()));
List<String> cols = new ArrayList<String>();
for (Field f : fs) {
if (!f.isAnnotationPresent(Column.class) || f.isAnnotationPresent(NoView.class)) {
continue;
}
cols.add(f.getName());
}
sb.append(org.apache.commons.lang.StringUtils.join(cols, ", "));
sb.append(String.format(" FROM `zstack`.`%s` WHERE %s IS NULL;\n", EOClazz.getSimpleName(), at.softDeletedColumn()));
}
private void handle(APIGenerateSqlVOViewMsg msg) {
try {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
scanner.addIncludeFilter(new AnnotationTypeFilter(EO.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Component.class));
StringBuilder sb = new StringBuilder();
for (String pkg : msg.getBasePackageNames()) {
for (BeanDefinition bd : scanner.findCandidateComponents(pkg)) {
Class<?> entityClazz = Class.forName(bd.getBeanClassName());
generateVOViewSql(sb, entityClazz);
}
}
String exportPath = PathUtil.join(System.getProperty("user.home"), "zstack-mysql-view");
FileUtils.deleteDirectory(new File(exportPath));
File folder = new File(exportPath);
folder.mkdirs();
File outfile = new File(PathUtil.join(exportPath, "view.sql"));
FileUtils.writeStringToFile(outfile, sb.toString());
APIGenerateSqlVOViewEvent evt = new APIGenerateSqlVOViewEvent(msg.getId());
bus.publish(evt);
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
private void handle(APIGenerateGroovyClassMsg msg) {
generateGroovyClasses(msg);
}
private void handle(APIGetDiskOfferingMsg msg) {
GetQuery q = new GetQuery();
String res = q.getAsString(msg, DiskOfferingInventory.class);
APIGetDiskOfferingReply reply = new APIGetDiskOfferingReply();
reply.setInventory(res);
bus.reply(msg, reply);
}
private void handle(APIGetInstanceOfferingMsg msg) {
GetQuery q = new GetQuery();
String res = q.getAsString(msg, InstanceOfferingInventory.class);
APIGetInstanceOfferingReply reply = new APIGetInstanceOfferingReply();
reply.setInventory(res);
bus.reply(msg, reply);
}
private String whiteSpace(int num) {
return StringUtils.repeat(" ", num);
}
private String classToInventoryPythonClass(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
boolean hasParent = (clazz.getSuperclass() != Object.class);
if (hasParent && !isPythonClassGenerated(clazz.getSuperclass())) {
sb.append(classToInventoryPythonClass(clazz.getSuperclass()));
}
if (hasParent) {
sb.append(String.format("\nclass %s(%s):", clazz.getSimpleName(), clazz.getSuperclass().getSimpleName()));
sb.append(String.format("\n%sdef __init__(self):", whiteSpace(4)));
sb.append(String.format("\n%ssuper(%s, self).__init__()", whiteSpace(8), clazz.getSimpleName()));
} else {
sb.append(String.format("\nclass %s(object):", clazz.getSimpleName()));
sb.append(String.format("\n%sdef __init__(self):", whiteSpace(4)));
}
Field[] fs = clazz.getDeclaredFields();
for (Field f : fs) {
sb.append(String.format("\n%sself.%s = None", whiteSpace(8), f.getName()));
}
sb.append(String.format("\n\n%sdef evaluate(self, inv):", whiteSpace(4)));
if (hasParent) {
sb.append(String.format("\n%ssuper(%s, self).evaluate(inv)", whiteSpace(8), clazz.getSimpleName()));
}
for (Field f : fs) {
sb.append(String.format("\n%sif hasattr(inv, '%s'):", whiteSpace(8), f.getName()));
sb.append(String.format("\n%sself.%s = inv.%s", whiteSpace(12), f.getName(), f.getName()));
sb.append(String.format("\n%selse:", whiteSpace(8)));
sb.append(String.format("\n%sself.%s = None\n", whiteSpace(12), f.getName()));
}
sb.append("\n\n");
markPythonClassAsGenerated(clazz);
return sb.toString();
}
private void classToApiMessageGroovyInformation(StringBuilder sb, Class<?> clazz) {
if (Modifier.isStatic(clazz.getModifiers())) {
return;
}
String name = clazz.getSimpleName().replace("\\.", "_").toUpperCase();
sb.append(String.format("\n%sdef %s = [name: '%s',", whiteSpace(4), name, clazz.getName()));
List<Field> fs = FieldUtils.getAllFields(clazz);
List<String> mandatoryFields = new ArrayList<String>();
for (Field f : fs) {
APIParam at = f.getAnnotation(APIParam.class);
if (at != null && at.required()) {
mandatoryFields.add(String.format("'%s'", f.getName()));
}
}
sb.append(String.format("requiredFields: [%s]]\n", StringUtils.join(mandatoryFields.iterator(), ",")));
}
private void generateApiMessageGroovyClass(StringBuilder sb, List<String> basePkgs) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
scanner.addIncludeFilter(new AssignableTypeFilter(APIMessage.class));
scanner.addIncludeFilter(new AssignableTypeFilter(APIReply.class));
scanner.addIncludeFilter(new AssignableTypeFilter(APIEvent.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Component.class));
for (String pkg : basePkgs) {
for (BeanDefinition bd : scanner.findCandidateComponents(pkg)) {
try {
Class<?> clazz = Class.forName(bd.getBeanClassName());
//classToApiMessageGroovyClass(sb, clazz);
classToApiMessageGroovyInformation(sb, clazz);
} catch (ClassNotFoundException e) {
logger.warn(String.format("Unable to generate groovy class for %s", bd.getBeanClassName()), e);
}
}
}
}
private void generateGroovyNotNullObjectClass(StringBuilder sb) {
sb.append(String.format("public class NotNullObject {}\n\n"));
}
private void generateRootMessageGroovyClass(StringBuilder sb) {
sb.append(String.format("\npublic class %s {", Message.class.getSimpleName()));
sb.append(String.format("\n%sMessageProperites props", whiteSpace(4)));
sb.append(String.format("\n%sdef headers = [:]", whiteSpace(4)));
sb.append(String.format("\n%sString id = Platform.uuid()", whiteSpace(4)));
sb.append(String.format("\n%sString serviceId = 'ApiMediator'", whiteSpace(4)));
sb.append(String.format("\n%sdef creatingTime\n", whiteSpace(4)));
sb.append(String.format("\n%sString toString() {", whiteSpace(4)));
sb.append(String.format("\n%sproperties.each {", whiteSpace(8)));
sb.append(String.format("\n%sif (it.value instanceof NotNullObject)" +
" { throw new UIRuntimeException(\"propertiy ${it.key} can not be null in ${fullName()}\")}", whiteSpace(12)));
sb.append(String.format("\n%s}\n", whiteSpace(8)));
sb.append(String.format("\n%sreturn JSON.dump([(fullName()):this])", whiteSpace(8)));
sb.append(String.format("\n%s}\n", whiteSpace(4)));
sb.append(String.format("\n%sdef fullName() {}", whiteSpace(4)));
sb.append(String.format("\n}\n\n"));
generatedGroovyClassName.add(Message.class.getSimpleName());
}
private void generateGroovyClasses(APIGenerateGroovyClassMsg msg) {
try {
APIGenerateGroovyClassEvent evt = new APIGenerateGroovyClassEvent(msg.getId());
String exportPath = msg.getOutputPath();
if (exportPath == null) {
exportPath = PathUtil.join(System.getProperty("user.home"), "zstack-groovy-template");
}
List<String> basePkgs = msg.getBasePackageNames();
if (basePkgs == null || basePkgs.isEmpty()) {
basePkgs = new ArrayList<String>(1);
basePkgs.add("org.zstack");
}
FileUtils.deleteDirectory(new File(exportPath));
File folder = new File(exportPath);
folder.mkdirs();
StringBuilder sb = new StringBuilder();
sb.append("package zstack.ui.api\n\n");
sb.append("interface ApiConstants {\n");
sb.append(String.format("%sdef API_EVENT_TYPE = '%s'\n", whiteSpace(4), new APIEvent().getType()));
/*
sb.append("import zstack.ui.core.JSON\n\n");
sb.append(String.mediaType("public class Session {\n"));
sb.append(String.mediaType("%sdef uuid\n", whiteSpace(4)));
sb.append(String.mediaType("}\n\n"));
generateRootMessageGroovyClass(sb);
generateGroovyNotNullObjectClass(sb);
*/
generateApiMessageGroovyClass(sb, basePkgs);
sb.append("\n}\n");
File classFile = new File(PathUtil.join(folder.getAbsolutePath(), "ApiConstants.groovy"));
FileUtils.writeStringToFile(classFile, sb.toString());
bus.publish(evt);
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
private void classToApiMessageGroovyClass(StringBuilder sb, Class<?> clazz) {
if (generatedGroovyClassName.contains(clazz.getSimpleName())) {
/* This class was generated as other's parent class */
return;
}
boolean hasParent = (clazz.getSuperclass() != Object.class);
if (hasParent && !generatedGroovyClassName.contains(clazz.getSuperclass().getSimpleName())) {
classToApiMessageGroovyClass(sb, clazz.getSuperclass());
}
if (hasParent) {
sb.append(String.format("\npublic class %s extends %s {", clazz.getSimpleName(), clazz.getSuperclass().getSimpleName()));
} else {
sb.append(String.format("\npublic class %s {", clazz.getSimpleName()));
}
Field[] fs = clazz.getDeclaredFields();
for (Field f : fs) {
APINoSee nosee = f.getAnnotation(APINoSee.class);
if (nosee != null) {
continue;
}
if (Modifier.isStatic(f.getModifiers())) {
continue;
}
APIParam at = f.getAnnotation(APIParam.class);
if (at != null && at.required()) {
sb.append(String.format("\n%sdef %s = new NotNullObject()", whiteSpace(4), f.getName()));
} else {
sb.append(String.format("\n%sdef %s", whiteSpace(4), f.getName()));
}
}
sb.append(String.format("\n\n%sdef fullName() { return '%s' }", whiteSpace(4), clazz.getName()));
if (APIEvent.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) {
try {
APIEvent evt = (APIEvent) clazz.newInstance();
sb.append(String.format("\n%sdef eventType() { return '%s' }", whiteSpace(4), evt.getType().toString()));
} catch (Exception e) {
throw new CloudRuntimeException(String.format("cannot generate event type for %s", clazz.getName()), e);
}
}
sb.append(String.format("\n}\n\n"));
generatedGroovyClassName.add(clazz.getSimpleName());
}
private String classToApiMessagePythonClass(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
boolean emptyLine = true;
String signature = String.format("%s_FULL_NAME", clazz.getSimpleName()).toUpperCase();
sb.append(String.format("\n%s = '%s'", signature, clazz.getName()));
sb.append(String.format("\nclass %s(object):", clazz.getSimpleName()));
sb.append(String.format("\n%sFULL_NAME='%s'", whiteSpace(4), clazz.getName()));
sb.append(String.format("\n%sdef __init__(self):", whiteSpace(4)));
List<Field> fs = FieldUtils.getAllFields(clazz);
for (Field f : fs) {
APINoSee nosee = f.getAnnotation(APINoSee.class);
if (nosee != null && !f.getName().equals("timeout") && !f.getName().equals("session")) {
continue;
}
APIParam at = f.getAnnotation(APIParam.class);
OverriddenApiParams o = clazz.getAnnotation(OverriddenApiParams.class);
if (o != null) {
for (OverriddenApiParam op : o.value()) {
if (op.field().equals(f.getName())) {
at = op.param();
break;
}
}
}
if (at != null && at.required()) {
sb.append(String.format("\n%s#mandatory field", whiteSpace(8)));
}
if (at != null && at.validValues().length != 0) {
List<String> values = new ArrayList<>(at.validValues().length);
Collections.addAll(values, at.validValues());
sb.append(String.format("\n%s#valid values: %s", whiteSpace(8), values));
}
if (at != null && at.validRegexValues() != null && at.validRegexValues().trim().equals("") == false) {
String regex = at.validRegexValues().trim();
sb.append(String.format("\n%s#valid regex values: %s", whiteSpace(8), regex));
}
if (Collection.class.isAssignableFrom(f.getType())) {
if (at != null && at.required()) {
sb.append(String.format("\n%sself.%s = NotNoneList()", whiteSpace(8), f.getName()));
} else {
sb.append(String.format("\n%sself.%s = OptionalList()", whiteSpace(8), f.getName()));
}
} else if (Map.class.isAssignableFrom(f.getType())) {
if (at != null && at.required()) {
sb.append(String.format("\n%sself.%s = NotNoneMap()", whiteSpace(8), f.getName()));
} else {
sb.append(String.format("\n%sself.%s = OptionalMap()", whiteSpace(8), f.getName()));
}
} else {
if (at != null && at.required()) {
sb.append(String.format("\n%sself.%s = NotNoneField()", whiteSpace(8), f.getName()));
} else {
sb.append(String.format("\n%sself.%s = None", whiteSpace(8), f.getName()));
}
}
emptyLine = false;
}
if (emptyLine) {
sb.append(String.format("\n%spass", whiteSpace(8)));
}
sb.append("\n\n");
markPythonClassAsGenerated(clazz);
return sb.toString();
}
private void generateSimplePythonClass(StringBuilder sb, Class<?> clazz) {
sb.append(String.format("\nclass %s(object):", clazz.getSimpleName()));
sb.append(String.format("\n%sdef __init__(self):", whiteSpace(4)));
for (Field f : clazz.getDeclaredFields()) {
sb.append(String.format("\n%sself.%s = None", whiteSpace(8), f.getName()));
}
sb.append("\n\n");
}
private void generateErrorCodePythonClass(StringBuilder sb) {
generateSimplePythonClass(sb, ErrorCode.class);
}
private void generateSessionPythonClass(StringBuilder sb) {
sb.append(String.format("\nclass Session(object):"));
sb.append(String.format("\n%sdef __init__(self):", whiteSpace(4)));
sb.append(String.format("\n%sself.uuid = None", whiteSpace(8)));
sb.append("\n\n");
}
private void generateSeachConditionClass(StringBuilder sb) {
generateSimplePythonClass(sb, APISearchMessage.NOLTriple.class);
generateSimplePythonClass(sb, APISearchMessage.NOVTriple.class);
}
private void generateMandoryFieldClass(StringBuilder sb) {
sb.append(String.format("\n\nclass NotNoneField(object):"));
sb.append(String.format("\n%spass\n", whiteSpace(4)));
sb.append(String.format("\n\nclass NotNoneList(object):"));
sb.append(String.format("\n%spass\n", whiteSpace(4)));
sb.append(String.format("\n\nclass OptionalList(object):"));
sb.append(String.format("\n%spass\n", whiteSpace(4)));
sb.append(String.format("\n\nclass NotNoneMap(object):"));
sb.append(String.format("\n%spass\n", whiteSpace(4)));
sb.append(String.format("\n\nclass OptionalMap(object):"));
sb.append(String.format("\n%spass\n", whiteSpace(4)));
}
private void generateBaseApiMessagePythonClass(StringBuilder sb) {
sb.append(String.format("\nclass %s(object):", APIMessage.class.getSimpleName()));
sb.append(String.format("\n%sdef __init__(self):", whiteSpace(4)));
sb.append(String.format("\n%ssuper(%s, self).__init__()", whiteSpace(8), APIMessage.class.getSimpleName()));
sb.append(String.format("\n%sself.timeout = None", whiteSpace(8)));
for (Field f : APIMessage.class.getDeclaredFields()) {
sb.append(String.format("\n%sself.%s = None", whiteSpace(8), f.getName()));
}
sb.append("\n\n");
markPythonClassAsGenerated(APIMessage.class);
sb.append(classToApiMessagePythonClass(APIDeleteMessage.class));
markPythonClassAsGenerated(APIDeleteMessage.class);
generateSeachConditionClass(sb);
}
private void generateApiNameList(StringBuilder sb, List<String> apiNames) {
Collections.sort(apiNames);
sb.append(String.format("\napi_names = ["));
for (String name : apiNames) {
sb.append(String.format("\n%s'%s',", whiteSpace(4), name));
}
sb.append("\n]\n");
}
private void generateApiMessagePythonClass(StringBuilder sb, List<String> basePkgs) {
generateSessionPythonClass(sb);
generateErrorCodePythonClass(sb);
generateBaseApiMessagePythonClass(sb);
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
scanner.addIncludeFilter(new AssignableTypeFilter(APIMessage.class));
scanner.addIncludeFilter(new AssignableTypeFilter(APIReply.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(NoPython.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Component.class));
List<String> apiNames = new ArrayList<>(100);
for (String pkg : basePkgs) {
for (BeanDefinition bd : scanner.findCandidateComponents(pkg).stream().sorted(Comparator.comparing(BeanDefinition::getBeanClassName)).collect(Collectors.toList())) {
try {
Class<?> clazz = Class.forName(bd.getBeanClassName());
if (clazz == APIMessage.class || clazz == APIListMessage.class || clazz == APIDeleteMessage.class || clazz == APISearchMessage.class) {
continue;
}
if (TypeUtils.isTypeOf(clazz, APISearchMessage.class, APIListMessage.class, APIGetMessage.class)) {
continue;
}
if (Modifier.isAbstract(clazz.getModifiers())) {
continue;
}
if (isPythonClassGenerated(clazz)) {
/* This class was generated as other's parent class */
continue;
}
sb.append(classToApiMessagePythonClass(clazz));
apiNames.add(clazz.getSimpleName());
} catch (ClassNotFoundException e) {
logger.warn(String.format("Unable to generate python class for %s", bd.getBeanClassName()), e);
}
}
}
apiNames.remove(APIMessage.class.getSimpleName());
apiNames.remove(APIListMessage.class.getSimpleName());
apiNames.remove(APIDeleteMessage.class.getSimpleName());
apiNames.remove(APISearchMessage.class.getSimpleName());
generateApiNameList(sb, apiNames);
}
private void generateConstantFromClassField(StringBuilder sb, Class<?> clazz) throws IllegalArgumentException, IllegalAccessException {
sb.append("\n#").append(clazz.getSimpleName());
if (clazz.isEnum()) {
for (Field f : clazz.getDeclaredFields()) {
if (f.isEnumConstant()) {
String name = f.getName().toUpperCase();
f.setAccessible(true);
String value = f.get(null).toString();
sb.append(String.format("\n%s = '%s'", name, value));
}
}
} else {
for (Field f : clazz.getDeclaredFields()) {
PythonClass at = f.getAnnotation(PythonClass.class);
if (at == null) {
continue;
}
String name = f.getName().toUpperCase();
f.setAccessible(true);
String value = f.get(null).toString();
sb.append(String.format("\n%s = '%s'", name, value));
}
}
sb.append("\n");
}
private void generateConstantPythonClass(StringBuilder sb, List<String> basePkgs) {
Reflections reflections = Platform.getReflections();
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(PythonClass.class);
for (Class<?> clazz : annotated.stream().sorted((c1, c2) -> {
return c1.getSimpleName().compareTo(c2.getSimpleName());
}).collect(Collectors.toList())) {
try {
generateConstantFromClassField(sb, clazz);
} catch (Exception e) {
logger.warn(String.format("Unable to generate python class for %s", clazz.getName()), e);
}
}
}
private void generateInventoryPythonClass(StringBuilder sb, List<String> basePkgs) {
List<String> inventoryPython = new ArrayList<>();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(PythonClassInventory.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Component.class));
for (String pkg : basePkgs) {
for (BeanDefinition bd : scanner.findCandidateComponents(pkg).stream().sorted((bd1, bd2) -> {
return bd1.getBeanClassName().compareTo(bd2.getBeanClassName());
}).collect(Collectors.toList())) {
try {
Class<?> clazz = Class.forName(bd.getBeanClassName());
if (isPythonClassGenerated(clazz)) {
/* This class was generated as other's parent class */
continue;
}
inventoryPython.add(classToInventoryPythonClass(clazz));
} catch (Exception e) {
logger.warn(String.format("Unable to generate python class for %s", bd.getBeanClassName()), e);
}
}
}
for (String invstr : inventoryPython) {
sb.append(invstr);
}
}
private boolean isPythonClassGenerated(Class<?> clazz) {
return generatedPythonClassName.contains(clazz.getSimpleName());
}
private void markPythonClassAsGenerated(Class<?> clazz) {
generatedPythonClassName.add(clazz.getSimpleName());
}
private void generateGlobalConfigPythonConstant(StringBuilder pysb) {
pysb.append("\n#GlobalConfigPythonConstant");
Map<String, List<String>> configs = new HashMap<>();
for (GlobalConfig c : gcf.getAllConfig().values()) {
List<String> cnames = configs.get(c.getCategory());
if (cnames == null) {
cnames = new ArrayList<>();
configs.put(c.getCategory(), cnames);
}
cnames.add(c.getName());
}
for (Map.Entry<String, List<String>> e : configs.entrySet()
.stream()
.sorted((e1, e2) -> {
return e1.getKey().toUpperCase().compareTo(e2.getKey().toUpperCase());
})
.collect(Collectors.toList())) {
pysb.append(String.format("\nclass GlobalConfig_%s(object):", e.getKey().toUpperCase().replaceAll("\\.", "_")));
for (String cname : e.getValue()) {
String var = cname.replaceAll("\\.", "_");
pysb.append(String.format("\n%s%s = '%s'", whiteSpace(4), var.toUpperCase(), cname));
}
pysb.append("\n");
pysb.append(String.format("\n%s@staticmethod", whiteSpace(4)));
pysb.append(String.format("\n%sdef get_category():", whiteSpace(4)));
pysb.append(String.format("\n%sreturn '%s'\n", whiteSpace(8), e.getKey()));
}
}
private void handle(APIGenerateTestLinkDocumentMsg msg) throws IOException {
String outputDir = msg.getOutputDir();
if (outputDir == null) {
outputDir = PathUtil.join(System.getProperty("user.home"), "zstack-testlink");
}
FileUtils.deleteDirectory(new File(outputDir));
File folder = new File(outputDir);
folder.mkdirs();
TestLinkDocumentGenerator.generateRequirementSpec(outputDir);
APIGenerateTestLinkDocumentEvent evt = new APIGenerateTestLinkDocumentEvent(msg.getId());
evt.setOutputDir(outputDir);
bus.publish(evt);
}
private void handle(APIGenerateApiJsonTemplateMsg msg) throws IOException {
generateApiJsonTemplate(msg.getExportPath(), msg.getBasePackageNames());
APIGenerateApiJsonTemplateEvent evt = new APIGenerateApiJsonTemplateEvent(msg.getId());
bus.publish(evt);
}
public void generateApiJsonTemplate(String exportPath, List<String> basePkgs) throws IOException {
generatedPythonClassName = new HashSet<>();
if (exportPath == null) {
exportPath = PathUtil.join(System.getProperty("user.home"), "zstack-python-template");
}
if (basePkgs == null || basePkgs.isEmpty()) {
basePkgs = new ArrayList<>(1);
basePkgs.add("org.zstack");
}
FileUtils.deleteDirectory(new File(exportPath));
File folder = new File(exportPath);
folder.mkdirs();
File jsonFolder = new File(PathUtil.join(folder.getAbsolutePath(), "json"));
jsonFolder.mkdirs();
File pythonFolder = new File(PathUtil.join(folder.getAbsolutePath(), "python"));
pythonFolder.mkdirs();
// write api_messages.py
{
StringBuilder apiNameBuilder = new StringBuilder();
apiNameBuilder.append("api_names = [\n");
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(APIEvent.class));
scanner.addIncludeFilter(new AssignableTypeFilter(APIReply.class));
scanner.addIncludeFilter(new AssignableTypeFilter(APIMessage.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Component.class));
for (String pkg : basePkgs) {
for (BeanDefinition bd : scanner.findCandidateComponents(pkg)) {
Class<?> clazz = null;
try {
clazz = Class.forName(bd.getBeanClassName());
logger.debug(String.format("dumping message: %s", bd.getBeanClassName()));
String template = RESTApiJsonTemplateGenerator.dump(clazz);
FileUtils.write(new File(PathUtil.join(jsonFolder.getAbsolutePath(),
clazz.getName() + ".json")), template);
} catch (Exception e) {
logger.warn(String.format("Unable to generate json template for %s", bd.getBeanClassName()), e);
}
if (clazz != null && APIMessage.class.isAssignableFrom(clazz)) {
if (TypeUtils.isTypeOf(clazz, APISearchMessage.class, APIGetMessage.class, APIListMessage.class)) {
continue;
}
apiNameBuilder.append(String.format("%s'%s',\n", whiteSpace(4), clazz.getName()));
}
}
}
apiNameBuilder.append("]\n");
FileUtils.write(new File(PathUtil.join(pythonFolder.getAbsolutePath(), "api_messages.py")),
apiNameBuilder.toString());
}
// write inventory.py
{
StringBuilder pysb = new StringBuilder();
generateMandoryFieldClass(pysb);
generateApiMessagePythonClass(pysb, basePkgs);
generateInventoryPythonClass(pysb, basePkgs);
generateConstantPythonClass(pysb, basePkgs);
generateGlobalConfigPythonConstant(pysb);
for (PythonApiBindingWriter writer : pythonApiBindingWriters) {
pysb.append("\n");
writer.writePython(pysb);
}
String pyStr = pysb.toString();
FileUtils.write(new File(PathUtil.join(pythonFolder.getAbsolutePath(), "inventory.py")), pyStr);
}
// write api_actions.py
{
PythonApiActionGenerator.generatePythonApiAction(basePkgs, pythonFolder.getAbsolutePath());
}
logger.info(String.format("Generated result in %s", folder.getAbsolutePath()));
generatedPythonClassName = null;
}
private void handle(APISearchDiskOfferingMsg msg) {
SearchQuery<DiskOfferingInventory> query = SearchQuery.create(msg, DiskOfferingInventory.class);
String content = query.listAsString();
APISearchDiskOfferingReply reply = new APISearchDiskOfferingReply();
reply.setContent(content);
bus.reply(msg, reply);
}
private void handle(APISearchInstanceOfferingMsg msg) {
SearchQuery<InstanceOfferingInventory> query = SearchQuery.create(msg, InstanceOfferingInventory.class);
query.add("visible", SearchOp.AND_EQ, Boolean.TRUE.toString());
String content = query.listAsString();
APISearchInstanceOfferingReply reply = new APISearchInstanceOfferingReply();
reply.setContent(content);
bus.reply(msg, reply);
}
private void handle(APIListInstanceOfferingMsg msg) {
List<InstanceOfferingVO> vos = dl.listByApiMessage(msg, InstanceOfferingVO.class);
List<InstanceOfferingInventory> invs = InstanceOfferingInventory.valueOf(vos);
APIListInstanceOfferingReply reply = new APIListInstanceOfferingReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void handle(APICreateInstanceOfferingMsg msg) {
APICreateInstanceOfferingEvent evt = new APICreateInstanceOfferingEvent(msg.getId());
String type = msg.getType() == null ? UserVmInstanceOfferingFactory.type.toString() : msg.getType();
InstanceOfferingFactory f = getInstanceOfferingFactory(type);
InstanceOfferingVO vo = new InstanceOfferingVO();
if (msg.getResourceUuid() != null) {
vo.setUuid(msg.getResourceUuid());
} else {
vo.setUuid(Platform.getUuid());
}
HostAllocatorStrategyType allocType = msg.getAllocatorStrategy() == null ? HostAllocatorStrategyType
.valueOf(HostAllocatorConstant.LEAST_VM_PREFERRED_HOST_ALLOCATOR_STRATEGY_TYPE) : HostAllocatorStrategyType.valueOf(msg.getAllocatorStrategy());
vo.setAllocatorStrategy(allocType.toString());
vo.setName(msg.getName());
vo.setCpuNum(msg.getCpuNum());
vo.setCpuSpeed(msg.getCpuSpeed());
vo.setDescription(msg.getDescription());
vo.setState(InstanceOfferingState.Enabled);
vo.setMemorySize(msg.getMemorySize());
vo.setDuration(InstanceOfferingDuration.Permanent);
vo.setType(type);
InstanceOfferingInventory inv = new SQLBatchWithReturn<InstanceOfferingInventory>() {
@Override
protected InstanceOfferingInventory scripts() {
InstanceOfferingInventory inv = f.createInstanceOffering(vo, msg);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), InstanceOfferingVO.class);
tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), InstanceOfferingVO.class.getSimpleName());
return inv;
}
}.execute();
evt.setInventory(inv);
bus.publish(evt);
logger.debug("Successfully added instance offering: " + printer.print(inv));
}
private void handle(APIListDiskOfferingMsg msg) {
List<DiskOfferingVO> vos = dl.listByApiMessage(msg, DiskOfferingVO.class);
List<DiskOfferingInventory> invs = DiskOfferingInventory.valueOf(vos);
APIListDiskOfferingReply reply = new APIListDiskOfferingReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void handle(APICreateDiskOfferingMsg msg) {
DiskOfferingVO vo = new DiskOfferingVO();
if (msg.getResourceUuid() != null) {
vo.setUuid(msg.getResourceUuid());
} else {
vo.setUuid(Platform.getUuid());
}
vo.setDescription(msg.getDescription());
vo.setName(msg.getName());
vo.setDiskSize(msg.getDiskSize());
vo.setSortKey(msg.getSortKey());
vo.setState(DiskOfferingState.Enabled);
if (msg.getAllocationStrategy() == null) {
vo.setAllocatorStrategy(PrimaryStorageConstant.DEFAULT_PRIMARY_STORAGE_ALLOCATION_STRATEGY_TYPE);
} else {
vo.setAllocatorStrategy(msg.getAllocationStrategy());
}
if (msg.getType() == null) {
vo.setType(DefaultDiskOfferingFactory.type.toString());
} else {
vo.setType(msg.getType());
}
DiskOfferingFactory f = getDiskOfferingFactory(vo.getType());
DiskOfferingInventory inv = new SQLBatchWithReturn<DiskOfferingInventory>() {
@Override
protected DiskOfferingInventory scripts() {
DiskOfferingInventory inv = f.createDiskOffering(vo, msg);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), DiskOfferingVO.class);
return inv;
}
}.execute();
tagMgr.createTagsFromAPICreateMessage(msg, inv.getUuid(), DiskOfferingVO.class.getSimpleName());
APICreateDiskOfferingEvent evt = new APICreateDiskOfferingEvent(msg.getId());
evt.setInventory(inv);
bus.publish(evt);
logger.debug("Successfully added disk offering: " + printer.print(inv));
}
@Override
public String getId() {
return bus.makeLocalServiceId(ConfigurationConstant.SERVICE_ID);
}
private void populateExtensions() {
for (InstanceOfferingFactory f : pluginRgty.getExtensionList(InstanceOfferingFactory.class)) {
InstanceOfferingFactory old = instanceOfferingFactories.get(f.getInstanceOfferingType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate InstanceOfferingFactory[%s, %s] for type[%s]", f.getClass().getName(),
old.getClass().getName(), f.getInstanceOfferingType()));
}
instanceOfferingFactories.put(f.getInstanceOfferingType().toString(), f);
}
for (DiskOfferingFactory f : pluginRgty.getExtensionList(DiskOfferingFactory.class)) {
DiskOfferingFactory old = diskOfferingFactories.get(f.getDiskOfferingType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate DiskOfferingFactory[%s, %s] for type[%s]",
f.getClass().getName(), old.getClass().getName(), f.getDiskOfferingType()));
}
diskOfferingFactories.put(f.getDiskOfferingType().toString(), f);
}
List<PythonApiBindingWriter> exts = pluginRgty.getExtensionList(PythonApiBindingWriter.class);
List<PythonApiBindingWriter> sortedExts = exts.stream().sorted((e1, e2) ->
e1.getClass().getName().compareTo(e2.getClass().getName())).collect(Collectors.toList());
for (PythonApiBindingWriter ext : sortedExts) {
pythonApiBindingWriters.add(ext);
}
}
private InstanceOfferingFactory getInstanceOfferingFactory(String type) {
InstanceOfferingFactory f = instanceOfferingFactories.get(type);
if (f == null) {
throw new IllegalArgumentException(String.format("unable to find InstanceOfferingFactory with type[%s]", type));
}
return f;
}
private DiskOfferingFactory getDiskOfferingFactory(String type) {
DiskOfferingFactory f = diskOfferingFactories.get(type);
if (f == null) {
throw new IllegalArgumentException(String.format("unable to find DiskOfferingFactory with type[%s]", type));
}
return f;
}
@Override
public boolean start() {
populateExtensions();
return true;
}
@Override
public boolean stop() {
return true;
}
}