[libvirt] [PATCH 4/4] Addition of XenAPI support to libvirt

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



diff -Nur ./libvirt_org/src/xenapi/xenapi_utils.c ./libvirt/src/xenapi/xenapi_utils.c
--- ./libvirt_org/src/xenapi/xenapi_utils.c     1970-01-01 01:00:00.000000000 +0100
+++ ./libvirt/src/xenapi/xenapi_utils.c 2010-02-18 16:26:52.000000000 +0000
@@ -0,0 +1,507 @@
+/*
+ * xenapi_utils.c: Xen API driver -- utils parts.
+ * Copyright (C) 2009 Citrix Ltd.
+ * Sharadha Prabhakar <sharadha.prabhakar@xxxxxxxxxx>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <config.h>
+
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <libxml/uri.h>
+#include <xen_internal.h>
+#include <libxml/parser.h>
+#include <curl/curl.h>
+#include <xen/api/xen_common.h>
+#include <xen/api/xen_vm.h>
+#include <xen/api/xen_vm.h>
+#include <xen/api/xen_all.h>
+#include <xen/api/xen_vm_metrics.h>
+
+#include "libvirt_internal.h"
+#include "libvirt/libvirt.h"
+#include "virterror_internal.h"
+#include "datatypes.h"
+#include "xenapi_driver.h"
+#include "util.h"
+#include "uuid.h"
+#include "memory.h"
+#include "driver.h"
+#include "buf.h"
+#include "xenapi_utils.h"
+
+/* returns 'file' or 'block' for the storage type */
+int
+getStorageVolumeType(char *type)
+{
+    if((STREQ(type,"lvmoiscsi")) ||
+       (STREQ(type,"lvmohba")) ||
+       (STREQ(type,"lvm")) ||
+       (STREQ(type,"file")) ||
+       (STREQ(type,"iso")) ||
+       (STREQ(type,"ext")) ||
+       (STREQ(type,"nfs")))
+        return (int)VIR_STORAGE_VOL_FILE;
+    else if((STREQ(type,"iscsi")) ||
+       (STREQ(type,"equal")) ||
+       (STREQ(type,"hba")) ||
+       (STREQ(type,"cslg")) ||
+       (STREQ(type,"udev")) ||
+       (STREQ(type,"netapp")))
+        return (int)VIR_STORAGE_VOL_BLOCK;
+    return -1;
+}
+
+/* returns error description if any received from the server */
+char *
+returnErrorFromSession(xen_session *session)
+{
+    int i;
+    char *buf = NULL;
+    for (i=0; i<session->error_description_count-1; i++) {
+        if (buf==NULL) {
+            buf = (char *)realloc(buf,strlen(session->error_description[i])+1);
+            strcpy(buf,session->error_description[i]);
+        } else {
+            buf = (char *)realloc(buf,strlen(buf)+strlen(session->error_description[i])+2);
+            strcat(buf,":");
+            strcat(buf,session->error_description[i]);
+        }
+    }
+    return buf;
+}
+
+/* XenAPI error handler - internally calls libvirt error handler after clearing error
+flag in session */
+void xenapiSessionErrorHandler(virConnectPtr conn, virErrorNumber errNum,
+                               const char *buf, const char *filename, const char *func, size_t lineno)
+{
+    if (buf==NULL) {
+        char *ret=NULL;
+        ret = returnErrorFromSession(((struct _xenapiPrivate *)(conn->privateData))->session);
+        virReportErrorHelper (conn, VIR_FROM_XENAPI, errNum, filename, func, lineno, _("%s\n"), ret);
+        xen_session_clear_error(((struct _xenapiPrivate *)(conn->privateData))->session);
+        VIR_FREE(ret);
+    } else {
+        virReportErrorHelper (conn, VIR_FROM_XENAPI, errNum, filename, func, lineno, _("%s\n"), buf);
+    }
+}
+
+/* converts bitmap to string of the form '1,2...' */
+char *
+mapDomainPinVcpu(unsigned int vcpu, unsigned char *cpumap, int maplen)
+{
+    char buf[VIR_UUID_BUFLEN], mapstr[sizeof(cpumap_t) * 64];
+    char *ret=NULL;
+    int i, j;
+    mapstr[0] = 0;
+    for (i = 0; i < maplen; i++) {
+        for (j = 0; j < 8; j++) {
+            if (cpumap[i] & (1 << j)) {
+                snprintf(buf, sizeof(buf), "%d,", (8 * i) + j);
+                strcat(mapstr, buf);
+           }
+        }
+    }
+    mapstr[strlen(mapstr) - 1] = 0;
+    snprintf(buf, sizeof(buf), "%d", vcpu);
+    ret = strdup(mapstr);
+    return ret;
+}
+
+/* obtains the CPU bitmap from the string passed */
+void
+getCpuBitMapfromString(char *mask, unsigned char *cpumap, int maplen)
+{
+    int pos;
+    int max_bits = maplen * 8;
+    char *num = NULL;
+    bzero(cpumap, maplen);
+    num = strtok (mask, ",");
+    while (num != NULL) {
+        sscanf (num, "%d", &pos);
+        if (pos<0 || pos>max_bits-1)
+            printf ("number in str %d exceeds cpumap's max bits %d\n", pos, max_bits);
+        else
+            (cpumap)[pos/8] |= (1<<(pos%8));
+            num = strtok (NULL, ",");
+    }
+}
+
+
+/* mapping XenServer power state to Libvirt power state */
+virDomainState
+mapPowerState(enum xen_vm_power_state state)
+{
+    virDomainState virState;
+    switch (state) {
+        case (XEN_VM_POWER_STATE_HALTED):
+        case (XEN_VM_POWER_STATE_SUSPENDED):
+            virState = VIR_DOMAIN_SHUTOFF;
+            break;
+        case (XEN_VM_POWER_STATE_PAUSED):
+            virState = VIR_DOMAIN_PAUSED;
+            break;
+        case (XEN_VM_POWER_STATE_RUNNING):
+            virState = VIR_DOMAIN_RUNNING;
+            break;
+        case (XEN_VM_POWER_STATE_UNKNOWN):
+        case (XEN_VM_POWER_STATE_UNDEFINED):
+            virState = VIR_DOMAIN_NOSTATE;
+            break;
+        default:
+            virState = VIR_DOMAIN_NOSTATE;
+            break;
+    }
+    return virState;
+}
+
+/* Gets the value of the child for the current node passed */
+char *
+getXmlChildValue(xmlDocPtr doc, xmlNodePtr cur, const char *tag)
+{
+    char *key = NULL;
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+        if ((!xmlStrcmp(cur->name, (const xmlChar *)tag))){
+            cur = cur->xmlChildrenNode;
+            key = (char *)xmlNodeListGetString(doc,cur,1);
+        }
+        cur = cur->next;
+    }
+    return key;
+}
+
+/* allocate a flexible array and fill values(key,val) */
+int
+allocStringMap (xen_string_string_map **strings, char *key, char *val)
+{
+    int sz = ((*strings) == NULL)?0:(*strings)->size;
+    sz++;
+    *strings = realloc(*strings,sizeof(xen_string_string_map)+
+                       sizeof(xen_string_string_map_contents)*sz);
+    (*strings)->size = sz;
+    (*strings)->contents[sz-1].key = strdup(key);
+    (*strings)->contents[sz-1].val = strdup(val);
+    return 0;
+}
+
+/* create boot order string as understood by libvirt */
+void
+createXmlBootOrderString(char **order, char *key)
+{
+    int sz = ((*order)==NULL)?0:(strlen(*order)+1);
+    const char *temp=NULL;
+    if (strcmp(key,"fd")==0)
+        temp="a";
+    else if (strcmp(key,"hd")==0)
+        temp="c";
+    else if (strcmp(key,"cdrom")==0)
+        temp="d";
+    else if (strcmp(key,"network")==0)
+        temp="n";
+    if (temp!=NULL) {
+        *order = (char *)realloc(*order,sz+strlen(temp)+1);
+        if(sz==0) strcpy(*order,temp);
+        else strcat(*order,temp);
+    }
+}
+
+enum xen_on_normal_exit
+actionShutdownStringtoEnum(char *str)
+{
+    enum xen_on_normal_exit code = XEN_ON_NORMAL_EXIT_UNDEFINED;
+    if (STREQ(str, "destroy"))
+        code = XEN_ON_NORMAL_EXIT_DESTROY;
+    else if (STREQ(str, "restart"))
+        code = XEN_ON_NORMAL_EXIT_RESTART;
+    return code;
+}
+
+enum xen_on_crash_behaviour
+actionCrashStringtoEnum(char *str)
+{
+    enum xen_on_crash_behaviour code = XEN_ON_CRASH_BEHAVIOUR_UNDEFINED;
+    if (STREQ(str, "destroy"))
+        code = XEN_ON_CRASH_BEHAVIOUR_DESTROY;
+    else if (STREQ(str, "restart"))
+        code = XEN_ON_CRASH_BEHAVIOUR_RESTART;
+    else if (STREQ(str, "preserve"))
+        code = XEN_ON_CRASH_BEHAVIOUR_PRESERVE;
+    else if (STREQ(str, "rename-restart"))
+        code = XEN_ON_CRASH_BEHAVIOUR_RENAME_RESTART;
+    return code;
+}
+
+/* convert boot order string libvirt format to XenServer format */
+const char *
+mapXmlBootOrder(char c) {
+    switch(c) {
+        case 'a':
+           return "fd";
+       case 'c':
+           return "hd";
+       case 'd':
+           return "cdrom";
+       case 'n':
+           return "network";
+       default:
+           return NULL;
+    }
+}
+
+/* creates network intereface for VM */
+int
+createVifNetwork (virConnectPtr conn, xen_vm vm, char *device,
+                  char *bridge, char *mac)
+{
+    xen_vm xvm = NULL;
+    char *uuid = NULL;
+    xen_vm_get_uuid(((struct _xenapiPrivate *)(conn->privateData))->session, &uuid, vm);
+    if (uuid) {
+        if(!xen_vm_get_by_uuid(((struct _xenapiPrivate *)(conn->privateData))->session,
+                               &xvm, uuid))
+            return -1;
+        VIR_FREE(uuid);
+    }
+    xen_vm_record_opt *vm_opt = xen_vm_record_opt_alloc();
+    vm_opt->is_record = 0;
+    vm_opt->u.handle = xvm;
+    xen_network_set *net_set = NULL;
+    xen_network_record *net_rec = NULL;
+    int cnt=0;
+    if (xen_network_get_all(((struct _xenapiPrivate *)(conn->privateData))->session, &net_set)) {
+        for(cnt=0;cnt<(net_set->size);cnt++) {
+            if (xen_network_get_record(((struct _xenapiPrivate *)(conn->privateData))->session,
+                                        &net_rec, net_set->contents[cnt])) {
+                if (STREQ(net_rec->bridge,bridge)) {
+                    break;
+                } else {
+                    xen_network_record_free(net_rec);
+                }
+            }
+        }
+    }
+    if ( (cnt<net_set->size) && net_rec) {
+        xen_network network = NULL;
+        xen_network_get_by_uuid(((struct _xenapiPrivate *)(conn->privateData))->session,
+                                    &network, net_rec->uuid);
+        xen_network_record_opt *network_opt = xen_network_record_opt_alloc();
+        network_opt->is_record = 0;
+        network_opt->u.handle = network;
+       xen_vif_record *vif_record = xen_vif_record_alloc();
+       vif_record->mac = mac;
+        vif_record->vm = vm_opt;
+        vif_record->network = network_opt;
+        xen_vif vif=NULL;
+
+       vif_record->other_config = xen_string_string_map_alloc(0);
+       vif_record->runtime_properties = xen_string_string_map_alloc(0);
+       vif_record->qos_algorithm_params = xen_string_string_map_alloc(0);
+       vif_record->device = strdup(device);
+       xen_vif_create(((struct _xenapiPrivate *)(conn->privateData))->session,
+                      &vif, vif_record);
+       if (vif!=NULL) {
+           xen_vif_free(vif);
+           xen_vif_record_free(vif_record);
+           xen_network_record_free(net_rec);
+           xen_network_set_free(net_set);
+           return 0;
+       }
+       xen_vif_record_free(vif_record);
+       xen_network_record_free(net_rec);
+    }
+    if (net_set!=NULL) xen_network_set_free(net_set);
+    return -1;
+}
+
+/* Create a VM record from the XML description */
+int
+createVMRecordFromXml (virConnectPtr conn, const char *xmlDesc,
+                       xen_vm_record **record, xen_vm *vm)
+{
+    xmlDocPtr doc;
+    xmlNodePtr cur,child,temp;
+    doc = xmlParseMemory(xmlDesc,strlen(xmlDesc));
+    if (doc == NULL) {
+        xenapiSessionErrorHandler(conn,VIR_ERR_XML_ERROR ,"", __FILE__, __FUNCTION__, __LINE__);
+        return -1;
+    }
+    cur = xmlDocGetRootElement(doc);
+    if (cur == NULL) {
+        xenapiSessionErrorHandler(conn,VIR_ERR_XML_ERROR ,"", __FILE__, __FUNCTION__, __LINE__);
+        xmlFreeDoc(doc);
+        return -1;
+    }
+    if (xmlStrcmp(cur->name, (const xmlChar *) "domain")) {
+        xenapiSessionErrorHandler(conn,VIR_ERR_XML_ERROR ,"root node not domain", __FILE__, __FUNCTION__, __LINE__);
+        xmlFreeDoc(doc);
+        return -1;
+    }
+    *record = xen_vm_record_alloc();
+    char *name = getXmlChildValue(doc,cur,"name");
+    (*record)->name_label = strdup(name);
+    child = cur->xmlChildrenNode;
+    while (child!=NULL) {
+        if ((!xmlStrcmp(child->name, (const xmlChar *)"os"))) {
+            char *ostype = getXmlChildValue(doc,child,"type");
+            if (ostype!=NULL) {
+                if (STREQ(ostype,"hvm")) {
+                    (*record)->hvm_boot_policy = strdup("BIOS order");
+                     temp = child;
+                     child = child->xmlChildrenNode;
+                     char *boot_order = NULL;
+                     while (child != NULL) {
+                         if ((!xmlStrcmp(child->name, (const xmlChar *)"boot"))){
+                             xmlChar *key;
+                             key = xmlGetProp(child, (const xmlChar *)"dev");
+                             if (key!=NULL) {
+                                 createXmlBootOrderString(&boot_order,(char *)key);
+                                 xmlFree(key);
+                             }
+                         }
+                         child = child->next;
+                     }
+                     if (boot_order!=NULL) {
+                        xen_string_string_map *hvm_boot_params=NULL;
+                        allocStringMap(&hvm_boot_params, (char *)"order",boot_order);
+                        //int size = sizeof(xen_string_string_map) +
+                        //          (hvm_boot_params->size * sizeof(xen_string_string_map_contents));
+                        //(*record)->hvm_boot_params = (xen_string_string_map *) malloc(size);
+                        //memcpy((char *)(*record)->hvm_boot_params, (char *)hvm_boot_params, size);
+                         (*record)->hvm_boot_params = hvm_boot_params;
+                        VIR_FREE(boot_order);
+                         //freeStringMap(hvm_boot_params);
+                     }
+                    child = temp;
+                } else if (STREQ(ostype,"linux")) {
+                    (*record)->pv_bootloader = strdup("pygrub");
+                    char *kernel = getXmlChildValue(doc,child,"kernel");
+                    if (kernel != NULL){
+                        (*record)->pv_kernel = kernel;
+                        //strcpy((*record)->pv_kernel,kernel);
+                        //free(kernel);
+                    }
+                    char *initrd = getXmlChildValue(doc,child,"initrd");
+                    if (initrd != NULL) {
+                        (*record)->pv_ramdisk = initrd;//(char *)malloc(strlen(initrd)+1);
+                        //strcpy((*record)->pv_ramdisk,initrd);
+                        //free(initrd);
+                    }
+                    char *cmdline = getXmlChildValue(doc,child,"cmdline");
+                    if (cmdline != NULL) {
+                        (*record)->pv_args = cmdline; //(char *)malloc(strlen(cmdline)+1);
+                        //strcpy((*record)->pv_args,cmdline);
+                        //free(cmdline);
+                    }
+                    (*record)->hvm_boot_params = xen_string_string_map_alloc(0);
+                }
+                VIR_FREE(ostype);
+            }
+        }
+        child = child->next;
+    }
+    char *bootload_args =  getXmlChildValue(doc,cur,"bootloader_args");
+    if (bootload_args!=NULL) {
+        (*record)->pv_bootloader_args =bootload_args; //(char *)malloc(strlen(bootload_args)+1);
+       //strcpy((*record)->pv_bootloader_args,bootload_args);
+       //free(bootload_args);
+    }
+    char *memory = getXmlChildValue(doc,cur,"memory");
+    if (memory!=NULL) {
+        int64_t static_max=0;
+        static_max = atoll(memory);
+        (*record)->memory_static_max = static_max * 1024;
+        VIR_FREE(memory);
+    }
+    char *curmemory = getXmlChildValue(doc,cur,"currentmemory");
+    if (curmemory!=NULL) {
+        int64_t dy_max=0;
+        dy_max = atoll(curmemory);
+        (*record)->memory_dynamic_max = dy_max * 1024;
+        VIR_FREE(curmemory);
+    } else {
+        (*record)->memory_dynamic_max = (*record)->memory_static_max;
+    }
+    char *vcpu =  getXmlChildValue(doc, cur, "vcpu");
+    if (vcpu!=NULL) {
+        (*record)->vcpus_max = atoll(vcpu);
+       (*record)->vcpus_at_startup =  atoll(vcpu);
+       VIR_FREE(vcpu);
+    }
+    char *on_poweroff = getXmlChildValue(doc,cur,"on_poweroff");
+    if (on_poweroff!=NULL) {
+        (*record)->actions_after_shutdown = actionShutdownStringtoEnum(on_poweroff);
+        VIR_FREE(on_poweroff);
+    }
+    char *on_reboot = getXmlChildValue(doc,cur,"on_reboot");
+    if (on_reboot!=NULL) {
+        (*record)->actions_after_reboot = actionShutdownStringtoEnum(on_reboot);
+        VIR_FREE(on_reboot);
+    }
+    char *on_crash = getXmlChildValue(doc,cur,"on_crash");
+    if (on_crash!=NULL) {
+        (*record)->actions_after_crash = actionCrashStringtoEnum(on_crash);
+        VIR_FREE(on_crash);
+    }
+    temp = cur;
+    cur = cur->xmlChildrenNode;
+    xen_string_string_map *strings=NULL;
+    while (cur != NULL) {
+        if ((!xmlStrcmp(cur->name, (const xmlChar *)"features"))){
+            for (child=cur->children;child!=NULL;child=child->next) {
+                allocStringMap(&strings,(char *)child->name,(char *)"true");
+            }
+        }
+        cur = cur->next;
+    }
+    cur = temp;
+    if (strings!=NULL) {
+        //int size = sizeof(xen_string_string_map)+ sizeof(xen_string_string_map_contents)*strings->size;
+        (*record)->platform = strings; //(xen_string_string_map *)malloc(size);
+        //memcpy((void *)(*record)->platform,(void *)strings,size);
+    }
+    (*record)->vcpus_params = xen_string_string_map_alloc(0);
+    (*record)->other_config = xen_string_string_map_alloc(0);
+    (*record)->last_boot_cpu_flags = xen_string_string_map_alloc(0);
+    (*record)->xenstore_data = xen_string_string_map_alloc(0);
+    (*record)->hvm_shadow_multiplier = 1.000;
+    if (!xen_vm_create(((struct _xenapiPrivate *)(conn->privateData))->session,
+                        vm, *record)) {
+        xmlFreeDoc(doc);
+        xenapiSessionErrorHandler(conn,VIR_ERR_INTERNAL_ERROR ,NULL, __FILE__, __FUNCTION__, __LINE__);
+        return -1;
+    }
+    cur = cur->xmlChildrenNode;
+    int device_number=0;
+    xmlChar *bridge=NULL,*mac=NULL;
+    while (cur != NULL) {
+        if ((!xmlStrcmp(cur->name, (const xmlChar *)"interface"))){
+            for (child=cur->children;child!=NULL;child=child->next) {
+                if ((!xmlStrcmp(child->name, (const xmlChar *)"source"))) {
+                    bridge = xmlGetProp(child, (const xmlChar *)"bridge");
+                }
+                if ((!xmlStrcmp(child->name, (const xmlChar *)"mac"))) {
+                    mac = xmlGetProp(child, (const xmlChar *)"address");
+                }
+            }
+           if (mac!=NULL && bridge!=NULL) {
+               char device[NETWORK_DEVID_SIZE]="\0";
+               sprintf(device,"%d",device_number);
+               createVifNetwork(conn, *vm, device, (char *)bridge, (char *)mac);
+               xmlFree(bridge);
+               device_number++;
+           }
+        }
+       cur = cur->next;
+    }
+    xmlFreeDoc(doc);
+    return 0;
+}
+
diff -Nur ./libvirt_org/src/xenapi/xenapi_utils.h ./libvirt/src/xenapi/xenapi_utils.h
--- ./libvirt_org/src/xenapi/xenapi_utils.h     1970-01-01 01:00:00.000000000 +0100
+++ ./libvirt/src/xenapi/xenapi_utils.h 2010-02-18 16:28:10.000000000 +0000
@@ -0,0 +1,94 @@
+/*
+ * xenapi_utils.h: Xen API driver -- utils header
+ * Copyright (C) 2009 Citrix Ltd.
+ * Sharadha Prabhakar <sharadha.prabhakar@xxxxxxxxxx>
+ */
+
+#ifndef _VIR_XENAPI_UTILS_
+#define _VIR_XENAPI_UTILS_
+
+#include <stdio.h>
+#include <string.h>
+#include <config.h>
+
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <libxml/uri.h>
+#include <xen_internal.h>
+#include <libxml/parser.h>
+#include <curl/curl.h>
+#include <xen/api/xen_common.h>
+#include <xen/api/xen_vm.h>
+#include <xen/api/xen_vm.h>
+#include <xen/api/xen_all.h>
+#include <xen/api/xen_vm_metrics.h>
+//#include <xen/dom0_ops.h>
+
+#include "libvirt_internal.h"
+#include "libvirt/libvirt.h"
+#include "virterror_internal.h"
+#include "datatypes.h"
+#include "xenapi_driver.h"
+#include "util.h"
+#include "uuid.h"
+#include "memory.h"
+#include "driver.h"
+#include "buf.h"
+
+#define NETWORK_DEVID_SIZE  (10)
+
+typedef uint64_t cpumap_t;
+
+void getCpuBitMapfromString(char *mask, unsigned char *cpumap, int maplen);
+
+int getStorageVolumeType(char *type);
+
+char *returnErrorFromSession(xen_session *session);
+
+void xenapiSessionErrorHandler(virConnectPtr conn, virErrorNumber errNum,
+                               const char *buf, const char *filename, const char *func, size_t lineno);
+
+
+virDomainState
+mapPowerState(enum xen_vm_power_state state);
+
+char *
+mapDomainPinVcpu(unsigned int vcpu, unsigned char *cpumap, int maplen);
+
+char *
+getXmlChildValue(xmlDocPtr doc, xmlNodePtr cur, const char *tag);
+
+int
+createVMRecordFromXml (virConnectPtr conn, const char *xmlDesc,
+                       xen_vm_record **record, xen_vm *vm);
+
+int
+allocStringMap (xen_string_string_map **strings, char *key, char *val);
+
+void
+createXmlBootOrderString(char **order, char *key);
+
+enum xen_on_normal_exit
+actionShutdownStringtoEnum(char *str);
+
+enum xen_on_crash_behaviour
+actionCrashStringtoEnum(char *str);
+
+int
+createVifNetwork(virConnectPtr conn, xen_vm vm, char *device,
+                 char *bridge, char *mac);
+
+const char *
+mapXmlBootOrder(char c);
+
+
+
+
+
+
+
+#endif //_VIR_XENAPI_UTILS_

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list

[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]