提交 c386d974 编写于 作者: D Daniel Veillard

* proxy/libvirt_proxy.c proxy/proxy.h proxy/proxy_client.c: more

  progresses on the proxy implementation.
* src/xend_internal.c src/xend_internal.h: exported one routine
Daniel
上级 27b7a8be
Thu Jun 29 14:53:01 EDT 2006 Daniel Veillard <veillard@redhat.com>
* proxy/libvirt_proxy.c proxy/proxy.h proxy/proxy_client.c: more
progresses on the proxy implementation.
* src/xend_internal.c src/xend_internal.h: exported one routine
Wed Jun 28 19:23:25 CEST 2006 Daniel Veillard <veillard@redhat.com> Wed Jun 28 19:23:25 CEST 2006 Daniel Veillard <veillard@redhat.com>
* configure.in Makefile.am proxy/Makefile.am proxy/libvirt_proxy.c * configure.in Makefile.am proxy/Makefile.am proxy/libvirt_proxy.c
......
...@@ -20,9 +20,11 @@ ...@@ -20,9 +20,11 @@
#include "proxy.h" #include "proxy.h"
#include "internal.h" #include "internal.h"
#include "xen_internal.h" #include "xen_internal.h"
#include "xend_internal.h"
static int fdServer = -1; static int fdServer = -1;
static int debug = 0; static int debug = 0;
static int persist = 0;
static int done = 0; static int done = 0;
#define MAX_CLIENT 64 #define MAX_CLIENT 64
...@@ -33,6 +35,8 @@ static struct pollfd pollInfos[MAX_CLIENT + 1]; ...@@ -33,6 +35,8 @@ static struct pollfd pollInfos[MAX_CLIENT + 1];
static virConnect conninfos; static virConnect conninfos;
static virConnectPtr conn = &conninfos; static virConnectPtr conn = &conninfos;
static unsigned long xenVersion = 0;
/************************************************************************ /************************************************************************
* * * *
* Interfaces with the Xen hypervisor * * Interfaces with the Xen hypervisor *
...@@ -49,17 +53,34 @@ static virConnectPtr conn = &conninfos; ...@@ -49,17 +53,34 @@ static virConnectPtr conn = &conninfos;
static int static int
proxyInitXen(void) { proxyInitXen(void) {
int ret; int ret;
unsigned long xenVersion2;
ret = xenHypervisorOpen(conn, NULL, VIR_DRV_OPEN_QUIET); ret = xenHypervisorOpen(conn, NULL, VIR_DRV_OPEN_QUIET);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to open Xen hypervisor\n"); fprintf(stderr, "Failed to open Xen hypervisor\n");
return(-1); return(-1);
} else {
ret = xenHypervisorGetVersion(conn, &xenVersion);
if (ret != 0) {
fprintf(stderr, "Failed to get Xen hypervisor version\n");
return(-1);
}
} }
ret = xenDaemonOpen_unix(conn, "/var/lib/xend/xend-socket"); ret = xenDaemonOpen_unix(conn, "/var/lib/xend/xend-socket");
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to connect to Xen daemon\n"); fprintf(stderr, "Failed to connect to Xen daemon\n");
return(-1); return(-1);
} }
ret = xenDaemonGetVersion(conn, &xenVersion2);
if (ret != 0) {
fprintf(stderr, "Failed to get Xen daemon version\n");
return(-1);
}
if (debug)
fprintf(stderr, "Connected to hypervisor %lu and daemon %lu\n",
xenVersion, xenVersion2);
if (xenVersion2 > xenVersion)
xenVersion = xenVersion2;
return(0); return(0);
} }
...@@ -342,7 +363,7 @@ retry: ...@@ -342,7 +363,7 @@ retry:
goto comm_error; goto comm_error;
if (debug) if (debug)
fprintf(stderr, "Gor command %d from client %d\n", req->command, nr); fprintf(stderr, "Got command %d from client %d\n", req->command, nr);
switch (req->command) { switch (req->command) {
case VIR_PROXY_NONE: case VIR_PROXY_NONE:
...@@ -352,17 +373,39 @@ retry: ...@@ -352,17 +373,39 @@ retry:
case VIR_PROXY_VERSION: case VIR_PROXY_VERSION:
if (req->len != sizeof(virProxyPacket)) if (req->len != sizeof(virProxyPacket))
goto comm_error; goto comm_error;
TODO; req->data.larg = xenVersion;
req->data.larg = 3 * 1000000 + 2;
break; break;
case VIR_PROXY_NODE_INFO: case VIR_PROXY_LIST: {
case VIR_PROXY_LIST: int maxids;
if (req->len != sizeof(virProxyPacket))
goto comm_error;
maxids = (sizeof(buffer) - sizeof(virProxyPacket)) / sizeof(int);
maxids -= 10; /* just to be sure that should still be plenty */
ret = xenHypervisorListDomains(conn,
(int *) &buffer[sizeof(virProxyPacket)],
maxids);
if (ret < 0) {
req->len = sizeof(virProxyPacket);
req->data.arg = 0;
} else {
req->len = sizeof(virProxyPacket) + ret * sizeof(int);
req->data.arg = ret;
}
break;
}
case VIR_PROXY_NUM_DOMAIN: case VIR_PROXY_NUM_DOMAIN:
if (req->len != sizeof(virProxyPacket))
goto comm_error;
req->data.arg = xenHypervisorNumOfDomains(conn);
break;
case VIR_PROXY_MAX_MEMORY:
case VIR_PROXY_DOMAIN_INFO:
case VIR_PROXY_NODE_INFO:
case VIR_PROXY_LOOKUP_ID: case VIR_PROXY_LOOKUP_ID:
case VIR_PROXY_LOOKUP_UUID: case VIR_PROXY_LOOKUP_UUID:
case VIR_PROXY_LOOKUP_NAME: case VIR_PROXY_LOOKUP_NAME:
case VIR_PROXY_MAX_MEMORY: TODO;
case VIR_PROXY_DOMAIN_INFO:
break; break;
default: default:
goto comm_error; goto comm_error;
...@@ -399,7 +442,7 @@ proxyProcessRequests(void) { ...@@ -399,7 +442,7 @@ proxyProcessRequests(void) {
*/ */
ret = poll(&pollInfos[0], nbClients + 1, 1000); ret = poll(&pollInfos[0], nbClients + 1, 1000);
if (ret == 0) { /* timeout */ if (ret == 0) { /* timeout */
if (nbClients == 0) { if ((nbClients == 0) && (persist == 0)) {
exit_timeout--; exit_timeout--;
if (exit_timeout == 0) { if (exit_timeout == 0) {
done = 1; done = 1;
...@@ -465,10 +508,8 @@ proxyMainLoop(void) { ...@@ -465,10 +508,8 @@ proxyMainLoop(void) {
if (proxyListenUnixSocket(PROXY_SOCKET_PATH) < 0) if (proxyListenUnixSocket(PROXY_SOCKET_PATH) < 0)
break; break;
proxyProcessRequests(); proxyProcessRequests();
proxyCloseUnixSocket();
} }
proxyCloseClientSockets(); proxyCloseClientSockets();
proxyCloseUnixSocket();
} }
/** /**
...@@ -497,6 +538,8 @@ int main(int argc, char **argv) { ...@@ -497,6 +538,8 @@ int main(int argc, char **argv) {
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v")) { if (!strcmp(argv[i], "-v")) {
debug++; debug++;
} else if (!strcmp(argv[i], "-no-timeout")) {
persist = 1;
} else { } else {
usage(argv[0]); usage(argv[0]);
exit(1); exit(1);
...@@ -508,7 +551,22 @@ int main(int argc, char **argv) { ...@@ -508,7 +551,22 @@ int main(int argc, char **argv) {
/* exit(1); */ /* exit(1); */
} }
proxyInitXen(); /*
proxyMainLoop(); * setup a connection block
*/
memset(conn, 0, sizeof(conninfos));
conn->magic = VIR_CONNECT_MAGIC;
/*
* very fist thing, use the socket as an exclusive lock, this then
* allow to do timed exits, avoiding constant CPU usage in case of
* failure.
*/
if (proxyListenUnixSocket(PROXY_SOCKET_PATH) < 0)
exit(0);
if (proxyInitXen() == 0)
proxyMainLoop();
sleep(1);
proxyCloseUnixSocket();
exit(0); exit(0);
} }
...@@ -56,6 +56,29 @@ struct _virProxyPacket { ...@@ -56,6 +56,29 @@ struct _virProxyPacket {
typedef struct _virProxyPacket virProxyPacket; typedef struct _virProxyPacket virProxyPacket;
typedef virProxyPacket *virProxyPacketPtr; typedef virProxyPacket *virProxyPacketPtr;
/*
* If there is extra data sent from the proxy to the client,
* they are appended after the packet.
* the size may not be fixed, it's passed as len and includes the
* extra data.
*/
struct _virProxyFullPacket {
unsigned short version; /* version of the proxy protocol */
unsigned short command; /* command number a virProxyCommand */
unsigned short serial; /* command serial number */
unsigned short len; /* the length of the request */
union {
char string[8]; /* string data */
int arg; /* or int argument */
long larg; /* or long argument */
} data;
/* that should be aligned on a 16bytes boundary */
union {
int arg[1020]; /* extra int array */
} extra;
};
typedef struct _virProxyFullPacket virProxyFullPacket;
typedef virProxyFullPacket *virProxyFullPacketPtr;
/* /*
* Functions callable from libvirt library * Functions callable from libvirt library
*/ */
......
...@@ -42,12 +42,12 @@ static int debug = 1; ...@@ -42,12 +42,12 @@ static int debug = 1;
static void static void
virProxyError(virConnectPtr conn, virErrorNumber error, const char *info) virProxyError(virConnectPtr conn, virErrorNumber error, const char *info)
{ {
const char *errmsg;
if (error == VIR_ERR_OK) if (error == VIR_ERR_OK)
return; return;
#if 0 #if 0
const char *errmsg;
errmsg = __virErrorMsg(error, info); errmsg = __virErrorMsg(error, info);
__virRaiseError(conn, NULL, VIR_FROM_XEND, error, VIR_ERR_ERROR, __virRaiseError(conn, NULL, VIR_FROM_XEND, error, VIR_ERR_ERROR,
errmsg, info, NULL, 0, 0, errmsg, info); errmsg, info, NULL, 0, 0, errmsg, info);
...@@ -71,8 +71,7 @@ virProxyFindServerPath(void) ...@@ -71,8 +71,7 @@ virProxyFindServerPath(void)
{ {
static const char *serverPaths[] = { static const char *serverPaths[] = {
#ifdef STANDALONE #ifdef STANDALONE
"./libvirt_proxy", "/usr/bin/libvirt_proxy_dbg",
BUILDDIR "/proxy/libvirt_proxy",
#endif #endif
BINDIR "/libvirt_proxy", BINDIR "/libvirt_proxy",
NULL NULL
...@@ -320,11 +319,10 @@ xenProxyClose(virConnectPtr conn) { ...@@ -320,11 +319,10 @@ xenProxyClose(virConnectPtr conn) {
static int static int
xenProxyCommand(virConnectPtr conn, virProxyPacketPtr request, xenProxyCommand(virConnectPtr conn, virProxyPacketPtr request,
virProxyPacketPtr *answer) { virProxyFullPacketPtr answer) {
static int serial = 0; static int serial = 0;
int ret; int ret;
virProxyPacketPtr res = NULL; virProxyPacketPtr res = NULL;
char packet[4096];
if ((conn == NULL) || (conn->proxy < 0)) if ((conn == NULL) || (conn->proxy < 0))
return(-1); return(-1);
...@@ -364,8 +362,8 @@ retry: ...@@ -364,8 +362,8 @@ retry:
return(-1); return(-1);
} }
} else { } else {
/* read in packet and duplicate if needed */ /* read in packet provided */
ret = virProxyReadClientSocket(conn->proxy, &packet[0], ret = virProxyReadClientSocket(conn->proxy, (char *) answer,
sizeof(virProxyPacket)); sizeof(virProxyPacket));
if (ret < 0) if (ret < 0)
return(-1); return(-1);
...@@ -376,9 +374,9 @@ retry: ...@@ -376,9 +374,9 @@ retry:
xenProxyClose(conn); xenProxyClose(conn);
return(-1); return(-1);
} }
res = (virProxyPacketPtr) &packet[0]; res = (virProxyPacketPtr) answer;
if ((res->len < sizeof(virProxyPacket)) || if ((res->len < sizeof(virProxyPacket)) ||
(res->len > sizeof(packet))) { (res->len > sizeof(virProxyFullPacket))) {
fprintf(stderr, fprintf(stderr,
"Communication error with proxy: got %d bytes packet\n", "Communication error with proxy: got %d bytes packet\n",
res->len); res->len);
...@@ -386,7 +384,8 @@ retry: ...@@ -386,7 +384,8 @@ retry:
return(-1); return(-1);
} }
if (res->len > sizeof(virProxyPacket)) { if (res->len > sizeof(virProxyPacket)) {
ret = virProxyReadClientSocket(conn->proxy, &packet[ret], ret = virProxyReadClientSocket(conn->proxy,
&(answer->extra.arg[0]),
res->len - ret); res->len - ret);
if (ret != (int) (res->len - sizeof(virProxyPacket))) { if (ret != (int) (res->len - sizeof(virProxyPacket))) {
fprintf(stderr, fprintf(stderr,
...@@ -412,8 +411,6 @@ retry: ...@@ -412,8 +411,6 @@ retry:
fprintf(stderr, "gor asynchronous packet number %d\n", res->serial); fprintf(stderr, "gor asynchronous packet number %d\n", res->serial);
goto retry; goto retry;
} }
if (answer != NULL)
*answer = res;
return(0); return(0);
} }
...@@ -431,9 +428,10 @@ xenProxyInit(virConnectPtr conn) { ...@@ -431,9 +428,10 @@ xenProxyInit(virConnectPtr conn) {
int ret; int ret;
int fd; int fd;
if (!VIR_IS_CONNECT(conn)) {
if (conn == NULL) virProxyError(conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
return(-1); return (-1);
}
if (conn->proxy <= 0) { if (conn->proxy <= 0) {
fd = virProxyOpenClientSocket(PROXY_SOCKET_PATH); fd = virProxyOpenClientSocket(PROXY_SOCKET_PATH);
...@@ -517,14 +515,41 @@ xenProxyNodeGetInfo(virConnectPtr conn, virNodeInfoPtr info) { ...@@ -517,14 +515,41 @@ xenProxyNodeGetInfo(virConnectPtr conn, virNodeInfoPtr info) {
* @maxids: size of @ids * @maxids: size of @ids
* *
* Collect the list of active domains, and store their ID in @maxids * Collect the list of active domains, and store their ID in @maxids
* TODO: this is quite expensive at the moment since there isn't one
* xend RPC providing both name and id for all domains.
* *
* Returns the number of domain found or -1 in case of error * Returns the number of domain found or -1 in case of error
*/ */
static int static int
xenProxyListDomains(virConnectPtr conn, int *ids, int maxids) xenProxyListDomains(virConnectPtr conn, int *ids, int maxids)
{ {
virProxyPacket req;
virProxyFullPacket ans;
int ret;
int nb;
if (!VIR_IS_CONNECT(conn)) {
virProxyError(conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
return (-1);
}
if ((ids == NULL) || (maxids <= 0)) {
virProxyError(conn, VIR_ERR_INVALID_ARG, __FUNCTION__);
return (-1);
}
memset(&req, 0, sizeof(req));
req.command = VIR_PROXY_LIST;
req.len = sizeof(req);
ret = xenProxyCommand(conn, &req, &ans);
if (ret < 0) {
xenProxyClose(conn);
return(-1);
}
nb = ans.data.arg;
if ((nb > 1020) || (nb <= 0))
return(-1);
if (nb > maxids)
nb = maxids;
memmove(ids, &ans.extra.arg[0], nb * sizeof(int));
return(nb);
} }
/** /**
...@@ -538,6 +563,23 @@ xenProxyListDomains(virConnectPtr conn, int *ids, int maxids) ...@@ -538,6 +563,23 @@ xenProxyListDomains(virConnectPtr conn, int *ids, int maxids)
static int static int
xenProxyNumOfDomains(virConnectPtr conn) xenProxyNumOfDomains(virConnectPtr conn)
{ {
virProxyPacket req;
int ret;
int nb;
if (!VIR_IS_CONNECT(conn)) {
virProxyError(conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
return (-1);
}
memset(&req, 0, sizeof(req));
req.command = VIR_PROXY_NUM_DOMAIN;
req.len = sizeof(req);
ret = xenProxyCommand(conn, &req, NULL);
if (ret < 0) {
xenProxyClose(conn);
return(-1);
}
return(req.data.arg);
} }
/** /**
...@@ -617,13 +659,26 @@ int main(int argc, char **argv) { ...@@ -617,13 +659,26 @@ int main(int argc, char **argv) {
virConnect conn; virConnect conn;
memset(&conn, 0, sizeof(conn)); memset(&conn, 0, sizeof(conn));
conn.magic = VIR_CONNECT_MAGIC;
ret = xenProxyInit(&conn); ret = xenProxyInit(&conn);
if (ret == 0) { if (ret == 0) {
ret = xenProxyGetVersion(&conn, &ver); ret = xenProxyGetVersion(&conn, &ver);
if (ret != 0) { if (ret != 0) {
fprintf(stderr, "Failed to get version from proxy\n"); fprintf(stderr, "Failed to get version from proxy\n");
} else { } else {
int ids[50], i;
printf("Proxy running with version %lu\n", ver); printf("Proxy running with version %lu\n", ver);
ret = xenProxyNumOfDomains(&conn);
printf("There is %d running domains:", ret);
ret = xenProxyListDomains(&conn, &ids, 50);
if (ret < 0) {
fprintf(stderr, "Failed to list domains\n");
}
for (i = 0;i < ret;i++)
printf(" %d", ids[i]);
printf("\n");
} }
xenProxyClose(&conn); xenProxyClose(&conn);
} }
......
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
static const char * xenDaemonGetType(virConnectPtr conn); static const char * xenDaemonGetType(virConnectPtr conn);
static int xenDaemonNodeGetInfo(virConnectPtr conn, virNodeInfoPtr info); static int xenDaemonNodeGetInfo(virConnectPtr conn, virNodeInfoPtr info);
static int xenDaemonGetVersion(virConnectPtr conn, unsigned long *hvVer);
static int xenDaemonListDomains(virConnectPtr conn, int *ids, int maxids); static int xenDaemonListDomains(virConnectPtr conn, int *ids, int maxids);
static int xenDaemonNumOfDomains(virConnectPtr conn); static int xenDaemonNumOfDomains(virConnectPtr conn);
static virDomainPtr xenDaemonLookupByID(virConnectPtr conn, int id); static virDomainPtr xenDaemonLookupByID(virConnectPtr conn, int id);
...@@ -885,24 +884,24 @@ urlencode(const char *string) ...@@ -885,24 +884,24 @@ urlencode(const char *string)
* Returns 0 in case of success, -1 in case of error. * Returns 0 in case of success, -1 in case of error.
*/ */
int int
xenDaemonOpen_unix(virConnectPtr xend, const char *path) xenDaemonOpen_unix(virConnectPtr conn, const char *path)
{ {
struct sockaddr_un *addr; struct sockaddr_un *addr;
if ((xend == NULL) || (path == NULL)) if ((conn == NULL) || (path == NULL))
return (-1); return (-1);
addr = &xend->addr_un; addr = &conn->addr_un;
addr->sun_family = AF_UNIX; addr->sun_family = AF_UNIX;
memset(addr->sun_path, 0, sizeof(addr->sun_path)); memset(addr->sun_path, 0, sizeof(addr->sun_path));
strncpy(addr->sun_path, path, sizeof(addr->sun_path)); strncpy(addr->sun_path, path, sizeof(addr->sun_path));
xend->len = sizeof(addr->sun_family) + strlen(addr->sun_path); conn->len = sizeof(addr->sun_family) + strlen(addr->sun_path);
if ((unsigned int) xend->len > sizeof(addr->sun_path)) if ((unsigned int) conn->len > sizeof(addr->sun_path))
xend->len = sizeof(addr->sun_path); conn->len = sizeof(addr->sun_path);
xend->addr = (struct sockaddr *) addr; conn->addr = (struct sockaddr *) addr;
xend->type = PF_UNIX; conn->type = PF_UNIX;
return (0); return (0);
} }
...@@ -919,18 +918,18 @@ xenDaemonOpen_unix(virConnectPtr xend, const char *path) ...@@ -919,18 +918,18 @@ xenDaemonOpen_unix(virConnectPtr xend, const char *path)
* Returns 0 in case of success, -1 in case of error. * Returns 0 in case of success, -1 in case of error.
*/ */
int int
xenDaemonOpen_tcp(virConnectPtr xend, const char *host, int port) xenDaemonOpen_tcp(virConnectPtr conn, const char *host, int port)
{ {
struct in_addr ip; struct in_addr ip;
struct hostent *pent; struct hostent *pent;
if ((xend == NULL) || (host == NULL) || (port <= 0)) if ((conn == NULL) || (host == NULL) || (port <= 0))
return (-1); return (-1);
pent = gethostbyname(host); pent = gethostbyname(host);
if (pent == NULL) { if (pent == NULL) {
if (inet_aton(host, &ip) == 0) { if (inet_aton(host, &ip) == 0) {
virXendError(xend, VIR_ERR_UNKNOWN_HOST, host); virXendError(conn, VIR_ERR_UNKNOWN_HOST, host);
errno = ESRCH; errno = ESRCH;
return (-1); return (-1);
} }
...@@ -938,13 +937,13 @@ xenDaemonOpen_tcp(virConnectPtr xend, const char *host, int port) ...@@ -938,13 +937,13 @@ xenDaemonOpen_tcp(virConnectPtr xend, const char *host, int port)
memcpy(&ip, pent->h_addr_list[0], sizeof(ip)); memcpy(&ip, pent->h_addr_list[0], sizeof(ip));
} }
xend->len = sizeof(struct sockaddr_in); conn->len = sizeof(struct sockaddr_in);
xend->addr = (struct sockaddr *) &xend->addr_in; conn->addr = (struct sockaddr *) &conn->addr_in;
xend->type = PF_INET; conn->type = PF_INET;
xend->addr_in.sin_family = AF_INET; conn->addr_in.sin_family = AF_INET;
xend->addr_in.sin_port = htons(port); conn->addr_in.sin_port = htons(port);
memcpy(&xend->addr_in.sin_addr, &ip, sizeof(ip)); memcpy(&conn->addr_in.sin_addr, &ip, sizeof(ip));
return (0); return (0);
} }
...@@ -2128,7 +2127,7 @@ xenDaemonGetType(virConnectPtr conn) ...@@ -2128,7 +2127,7 @@ xenDaemonGetType(virConnectPtr conn)
* extracted by lack of capacities returns 0 and @hvVer is 0, otherwise * extracted by lack of capacities returns 0 and @hvVer is 0, otherwise
* @hvVer value is major * 1,000,000 + minor * 1,000 + release * @hvVer value is major * 1,000,000 + minor * 1,000 + release
*/ */
static int int
xenDaemonGetVersion(virConnectPtr conn, unsigned long *hvVer) xenDaemonGetVersion(virConnectPtr conn, unsigned long *hvVer)
{ {
static unsigned long version = 0; static unsigned long version = 0;
......
...@@ -600,6 +600,7 @@ int xenDaemonDomainLookupByName_ids(virConnectPtr xend, ...@@ -600,6 +600,7 @@ int xenDaemonDomainLookupByName_ids(virConnectPtr xend,
void xenDaemonRegister(void); void xenDaemonRegister(void);
int xenDaemonOpen(virConnectPtr conn, const char *name, int flags); int xenDaemonOpen(virConnectPtr conn, const char *name, int flags);
int xenDaemonClose(virConnectPtr conn); int xenDaemonClose(virConnectPtr conn);
int xenDaemonGetVersion(virConnectPtr conn, unsigned long *hvVer);
int xenDaemonDomainSuspend(virDomainPtr domain); int xenDaemonDomainSuspend(virDomainPtr domain);
int xenDaemonDomainResume(virDomainPtr domain); int xenDaemonDomainResume(virDomainPtr domain);
int xenDaemonDomainShutdown(virDomainPtr domain); int xenDaemonDomainShutdown(virDomainPtr domain);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册