Commit 84e2ce9b authored by Matthieu Dorier's avatar Matthieu Dorier

moved from boost.python to pybind11

parent e42fea37
# (C) 2018 The University of Chicago
# See COPYRIGHT in top-level directory.
import _pybaketarget
import _pybakeserver
import pymargo
from pybake.target import BakeTargetID
......
......@@ -3,14 +3,8 @@
*
* See COPYRIGHT in top-level directory.
*/
#define BOOST_NO_AUTO_PTR
#include <boost/python.hpp>
#include <boost/python/return_opaque_pointer.hpp>
#include <boost/python/handle.hpp>
#include <boost/python/enum.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>
#include <boost/python/return_value_policy.hpp>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <string>
#include <vector>
#include <cstring>
......@@ -18,36 +12,38 @@
#include <margo.h>
#include <bake.h>
#include <bake-client.h>
#if HAS_NUMPY
#include <boost/python/numpy.hpp>
namespace np = boost::python::numpy;
#endif
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(margo_instance)
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(bake_provider_handle)
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(bake_client)
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(hg_addr)
namespace py11 = pybind11;
namespace np = py11;
namespace bpl = boost::python;
typedef py11::capsule pymargo_instance_id;
typedef py11::capsule pymargo_addr;
typedef py11::capsule pybake_client_t;
typedef py11::capsule pybake_provider_handle_t;
static bake_client_t pybake_client_init(margo_instance_id mid) {
#define MID2CAPSULE(__mid) py11::capsule((void*)(__mid), "margo_instance_id", nullptr)
#define ADDR2CAPSULE(__addr) py11::capsule((void*)(__addr), "hg_addr_t", nullptr)
#define BAKEPH2CAPSULE(__bph) py11::capsule((void*)(__bph), "bake_provider_handle_t", nullptr)
#define BAKECL2CAPSULE(__bcl) py11::capsule((void*)(__bcl), "bake_client_t", nullptr)
static pybake_client_t pybake_client_init(pymargo_instance_id mid) {
bake_client_t result = BAKE_CLIENT_NULL;
bake_client_init(mid, &result);
return result;
return BAKECL2CAPSULE(result);
}
static bake_provider_handle_t pybake_provider_handle_create(
bake_client_t client,
hg_addr_t addr,
static pybake_provider_handle_t pybake_provider_handle_create(
pybake_client_t client,
pymargo_addr addr,
uint8_t provider_id) {
bake_provider_handle_t providerHandle = BAKE_PROVIDER_HANDLE_NULL;
bake_provider_handle_create(client, addr, provider_id, &providerHandle);
return providerHandle;
return BAKEPH2CAPSULE(providerHandle);
}
static uint64_t pybake_get_eager_limit(
bake_provider_handle_t ph)
pybake_provider_handle_t ph)
{
uint64_t limit;
int ret = bake_provider_handle_get_eager_limit(ph, &limit);
......@@ -55,26 +51,26 @@ static uint64_t pybake_get_eager_limit(
return limit;
}
static bpl::object pybake_probe(
bake_provider_handle_t ph,
static py11::object pybake_probe(
pybake_provider_handle_t ph,
uint64_t max_targets)
{
bpl::list result;
py11::list result;
std::vector<bake_target_id_t> targets(max_targets);
uint64_t num_targets;
int ret;
Py_BEGIN_ALLOW_THREADS
ret = bake_probe(ph, max_targets, targets.data(), &num_targets);
Py_END_ALLOW_THREADS
if(ret != 0) return bpl::object();
if(ret != 0) return py11::object();
for(uint64_t i=0; i < num_targets; i++) {
result.append(bpl::object(targets[i]));
result.append(py11::cast(targets[i]));
}
return result;
}
static bpl::object pybake_create(
bake_provider_handle_t ph,
static py11::object pybake_create(
pybake_provider_handle_t ph,
bake_target_id_t bti,
size_t region_size)
{
......@@ -84,104 +80,107 @@ static bpl::object pybake_create(
Py_BEGIN_ALLOW_THREADS
ret = bake_create(ph, bti, region_size, &rid);
Py_END_ALLOW_THREADS
if(ret != 0) return bpl::object();
else return bpl::object(rid);
if(ret != 0) return py11::none();
else return py11::cast(rid);
}
static bpl::object pybake_write(
bake_provider_handle_t ph,
static py11::object pybake_write(
pybake_provider_handle_t ph,
const bake_region_id_t& rid,
uint64_t offset,
const std::string& data)
const py11::bytes& bdata)
{
int ret;
Py_BEGIN_ALLOW_THREADS
std::string data = (std::string)bdata;
ret = bake_write(ph, rid, offset, (const void*)data.data(), data.size());
Py_END_ALLOW_THREADS
if(ret == 0) return bpl::object(true);
else return bpl::object(false);
if(ret == 0) return py11::cast(true);
else return py11::cast(false);
}
#if HAS_NUMPY
static bpl::object pybake_write_numpy(
bake_provider_handle_t ph,
static py11::object pybake_write_numpy(
pybake_provider_handle_t ph,
const bake_region_id_t& rid,
uint64_t offset,
const np::ndarray& data)
const np::array& data)
{
if(!(data.get_flags() & np::ndarray::bitflag::V_CONTIGUOUS)) {
if(!(data.flags() &
(np::array::f_style | np::array::c_style))) {
std::cerr << "[pyBAKE error]: non-contiguous numpy arrays not yet supported" << std::endl;
return bpl::object(false);
return py11::cast(false);
}
size_t size = data.get_dtype().get_itemsize();
for(int i = 0; i < data.get_nd(); i++) {
size_t size = data.dtype().itemsize();
for(int i = 0; i < data.ndim(); i++) {
size *= data.shape(i);
}
void* buffer = data.get_data();
const void* buffer = data.data();
int ret;
Py_BEGIN_ALLOW_THREADS
ret = bake_write(ph, rid, offset, buffer, size);
Py_END_ALLOW_THREADS
if(ret != 0) return bpl::object(false);
else return bpl::object(true);
if(ret != 0) return py11::cast(false);
else return py11::cast(true);
}
#endif
static bpl::object pybake_persist(
bake_provider_handle_t ph,
static py11::object pybake_persist(
pybake_provider_handle_t ph,
const bake_region_id_t& rid)
{
int ret;
Py_BEGIN_ALLOW_THREADS
ret = bake_persist(ph, rid);
Py_END_ALLOW_THREADS
if(ret == 0) return bpl::object(true);
else return bpl::object(false);
if(ret == 0) return py11::cast(true);
else return py11::cast(false);
}
static bpl::object pybake_create_write_persist(
bake_provider_handle_t ph,
static py11::object pybake_create_write_persist(
pybake_provider_handle_t ph,
bake_target_id_t tid,
const std::string& data)
const py11::bytes& bdata)
{
bake_region_id_t rid;
int ret;
Py_BEGIN_ALLOW_THREADS
std::string data = (std::string)bdata;
ret = bake_create_write_persist(ph, tid,
data.data(), data.size(), &rid);
Py_END_ALLOW_THREADS
if(ret == 0) return bpl::object(rid);
else return bpl::object();
if(ret == 0) return py11::cast(rid);
else return py11::none();
}
#if HAS_NUMPY
static bpl::object pybake_create_write_persist_numpy(
bake_provider_handle_t ph,
static py11::object pybake_create_write_persist_numpy(
pybake_provider_handle_t ph,
bake_target_id_t tid,
const np::ndarray& data)
const np::array& data)
{
bake_region_id_t rid;
if(!(data.get_flags() & np::ndarray::bitflag::V_CONTIGUOUS)) {
if(!(data.flags() & (np::array::f_style | np::array::c_style))) {
std::cerr << "[pyBAKE error]: non-contiguous numpy arrays not yet supported" << std::endl;
return bpl::object();
return py11::none();
}
size_t size = data.get_dtype().get_itemsize();
for(int i = 0; i < data.get_nd(); i++) {
size_t size = data.dtype().itemsize();
for(int i = 0; i < data.ndim(); i++) {
size *= data.shape(i);
}
void* buffer = data.get_data();
const void* buffer = data.data();
int ret;
Py_BEGIN_ALLOW_THREADS
ret = bake_create_write_persist(ph, tid,
buffer, size, &rid);
Py_END_ALLOW_THREADS
if(ret == 0) return bpl::object(rid);
else return bpl::object();
if(ret == 0) return py11::cast(rid);
else return py11::none();
}
#endif
static bpl::object pybake_get_size(
bake_provider_handle_t ph,
static py11::object pybake_get_size(
pybake_provider_handle_t ph,
const bake_region_id_t& rid)
{
uint64_t size;
......@@ -189,12 +188,12 @@ static bpl::object pybake_get_size(
Py_BEGIN_ALLOW_THREADS
ret = bake_get_size(ph, rid, &size);
Py_END_ALLOW_THREADS
if(ret == 0) return bpl::object(size);
else return bpl::object();
if(ret == 0) return py11::cast(size);
else return py11::none();
}
static bpl::object pybake_read(
bake_provider_handle_t ph,
static py11::object pybake_read(
pybake_provider_handle_t ph,
const bake_region_id_t& rid,
uint64_t offset,
size_t size)
......@@ -205,13 +204,13 @@ static bpl::object pybake_read(
Py_BEGIN_ALLOW_THREADS
ret = bake_read(ph, rid, offset, (void*)result.data(), size, &bytes_read);
Py_END_ALLOW_THREADS
if(ret != 0) return bpl::object();
if(ret != 0) return py11::none();
result.resize(bytes_read);
return bpl::object(result);
return py11::bytes(result);
}
static bpl::object pybake_migrate(
bake_provider_handle_t source_ph,
static py11::object pybake_migrate(
pybake_provider_handle_t source_ph,
const bake_region_id_t& source_rid,
bool remove_source,
const std::string& dest_addr,
......@@ -224,64 +223,71 @@ static bpl::object pybake_migrate(
remove_source, dest_addr.c_str(), dest_provider_id,
dest_target_id, &dest_rid);
Py_END_ALLOW_THREADS
if(ret != BAKE_SUCCESS) return bpl::object();
return bpl::object(dest_rid);
if(ret != BAKE_SUCCESS) return py11::none();
return py11::cast(dest_rid);
}
#if HAS_NUMPY
static bpl::object pybake_read_numpy(
bake_provider_handle_t ph,
static py11::object pybake_read_numpy(
pybake_provider_handle_t ph,
const bake_region_id_t& rid,
uint64_t offset,
const bpl::tuple& shape,
const py11::tuple& shape,
const np::dtype& dtype)
{
np::ndarray result = np::empty(shape, dtype);
size_t size = dtype.get_itemsize();
for(int i=0; i < result.get_nd(); i++)
std::vector<ssize_t> sshape(shape.size());
for(unsigned int i=0; i<sshape.size(); i++) sshape[i] = shape[i].cast<ssize_t>();
np::array result(dtype, sshape);
size_t size = dtype.itemsize();
for(int i=0; i < result.ndim(); i++)
size *= result.shape(i);
uint64_t bytes_read;
int ret;
Py_BEGIN_ALLOW_THREADS
ret = bake_read(ph, rid, offset, (void*)result.get_data(), size, &bytes_read);
ret = bake_read(ph, rid, offset, (void*)result.data(), size, &bytes_read);
Py_END_ALLOW_THREADS
if(ret != 0) return bpl::object();
if(bytes_read != size) return bpl::object();
if(ret != 0) return py11::none();
if(bytes_read != size) return py11::none();
else return result;
}
#endif
BOOST_PYTHON_MODULE(_pybakeclient)
PYBIND11_MODULE(_pybakeclient, m)
{
#define ret_policy_opaque bpl::return_value_policy<bpl::return_opaque_pointer>()
#if HAS_NUMPY
np::initialize();
try { py11::module::import("numpy"); }
catch (...) {
std::cerr << "[Py-BAKE] Error: could not import numpy at C++ level" << std::endl;
exit(-1);
}
#endif
bpl::import("_pybaketarget");
bpl::opaque<bake_client>();
bpl::opaque<bake_provider_handle>();
bpl::def("client_init", &pybake_client_init, ret_policy_opaque);
bpl::def("client_finalize", &bake_client_finalize);
bpl::def("provider_handle_create", &pybake_provider_handle_create, ret_policy_opaque);
bpl::def("provider_handle_ref_incr", &bake_provider_handle_ref_incr);
bpl::def("provider_handle_release", &bake_provider_handle_release);
bpl::def("get_eager_limit", &pybake_get_eager_limit);
bpl::def("set_eager_limit", &bake_provider_handle_set_eager_limit);
bpl::def("probe", &pybake_probe);
bpl::def("create", &pybake_create);
bpl::def("write", &pybake_write);
bpl::def("persist", &pybake_persist);
bpl::def("create_write_persist", &pybake_create_write_persist);
bpl::def("get_size", &pybake_get_size);
bpl::def("read", &pybake_read);
bpl::def("remove", &bake_remove);
bpl::def("migrate", &pybake_migrate);
bpl::def("shutdown_service", &bake_shutdown_service);
py11::module::import("_pybaketarget");
m.def("client_init", &pybake_client_init);
m.def("client_finalize", [](pybake_client_t clt) {
return bake_client_finalize(clt);} );
m.def("provider_handle_create", &pybake_provider_handle_create);
m.def("provider_handle_ref_incr", [](pybake_provider_handle_t pbph) {
return bake_provider_handle_ref_incr(pbph); });
m.def("provider_handle_release", [](pybake_provider_handle_t pbph) {
return bake_provider_handle_release(pbph); });
m.def("get_eager_limit", &pybake_get_eager_limit);
m.def("set_eager_limit", [](pybake_provider_handle_t pbph, uint64_t lim) {
return bake_provider_handle_set_eager_limit(pbph, lim); });
m.def("probe", &pybake_probe);
m.def("create", &pybake_create);
m.def("write", &pybake_write);
m.def("persist", &pybake_persist);
m.def("create_write_persist", &pybake_create_write_persist);
m.def("get_size", &pybake_get_size);
m.def("read", &pybake_read);
m.def("remove", [](pybake_provider_handle_t pbph, bake_region_id_t rid) {
return bake_remove(pbph, rid);} );
m.def("migrate", &pybake_migrate);
m.def("shutdown_service", [](pybake_client_t client, pymargo_addr addr) {
return bake_shutdown_service(client, addr); });
#if HAS_NUMPY
bpl::def("write_numpy", &pybake_write_numpy);
bpl::def("create_write_persist_numpy", &pybake_create_write_persist_numpy);
bpl::def("read_numpy", &pybake_read_numpy);
m.def("write_numpy", &pybake_write_numpy);
m.def("create_write_persist_numpy", &pybake_create_write_persist_numpy);
m.def("read_numpy", &pybake_read_numpy);
#endif
#undef ret_policy_opaque
}
......@@ -3,14 +3,7 @@
*
* See COPYRIGHT in top-level directory.
*/
#define BOOST_NO_AUTO_PTR
#include <boost/python.hpp>
#include <boost/python/return_opaque_pointer.hpp>
#include <boost/python/handle.hpp>
#include <boost/python/enum.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>
#include <boost/python/return_value_policy.hpp>
#include <pybind11/pybind11.h>
#include <string>
#include <vector>
#include <cstring>
......@@ -19,59 +12,66 @@
#include <bake.h>
#include <bake-server.h>
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(margo_instance)
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(bake_server_context_t)
namespace py11 = pybind11;
namespace bpl = boost::python;
typedef py11::capsule pymargo_instance_id;
typedef py11::capsule pymargo_addr;
typedef py11::capsule pybake_provider_t;
static bake_provider_t pybake_provider_register(margo_instance_id mid, uint8_t provider_id) {
#define MID2CAPSULE(__mid) py11::capsule((void*)(__mid), "margo_instance_id", nullptr)
#define ADDR2CAPSULE(__addr) py11::capsule((void*)(__addr), "hg_addr_t", nullptr)
#define BAKEPR2CAPSULE(__bpr) py11::capsule((void*)(__bpr), "bake_provider_t", nullptr)
static pybake_provider_t pybake_provider_register(pymargo_instance_id mid, uint8_t provider_id) {
bake_provider_t provider;
int ret = bake_provider_register(mid, provider_id, BAKE_ABT_POOL_DEFAULT, &provider);
if(ret != 0) return NULL;
else return provider;
if(ret != 0) return py11::none();
else return BAKEPR2CAPSULE(provider);
}
static bpl::object pybake_provider_add_storage_target(
bake_provider_t provider,
static py11::object pybake_provider_add_storage_target(
pybake_provider_t provider,
const std::string& target_name) {
bake_target_id_t target_id;
std::memset(&target_id, 0, sizeof(target_id));
int ret = bake_provider_add_storage_target(
provider, target_name.c_str(), &target_id);
if(ret != 0) return bpl::object();
return bpl::object(target_id);
if(ret != 0) {
return py11::none();
}
return py11::cast(target_id);
}
static bool pybake_provider_remove_storage_target(
bake_provider_t provider,
pybake_provider_t provider,
bake_target_id_t target_id)
{
return 0 == bake_provider_remove_storage_target(provider, target_id);
}
static bool pybake_provider_remove_all_storage_targets(
bake_provider_t provider)
pybake_provider_t provider)
{
return 0 == bake_provider_remove_all_storage_targets(provider);
}
static uint64_t pybake_provider_count_storage_targets(
bake_provider_t provider)
pybake_provider_t provider)
{
uint64_t n = 0;
bake_provider_count_storage_targets(provider, &n);
return n;
}
static bpl::list pybake_provider_list_storage_targets(
bake_provider_t provider)
static py11::list pybake_provider_list_storage_targets(
pybake_provider_t provider)
{
std::vector<bake_target_id_t> result;
uint64_t n = pybake_provider_count_storage_targets(provider);
if(n == 0) return bpl::list();
if(n == 0) return py11::list();
result.resize(n);
bake_provider_list_storage_targets(provider, result.data());
bpl::list list_result;
py11::list list_result;
for(const auto& t : result) list_result.append(t);
return list_result;
}
......@@ -81,19 +81,14 @@ static bool pybake_make_pool(const std::string& pool_name,
return 0 == bake_makepool(pool_name.c_str(), pool_size, mode);
}
BOOST_PYTHON_MODULE(_pybakeserver)
PYBIND11_MODULE(_pybakeserver, m)
{
#define ret_policy_opaque bpl::return_value_policy<bpl::return_opaque_pointer>()
bpl::import("_pybaketarget");
bpl::opaque<bake_server_context_t>();
bpl::def("register", &pybake_provider_register, ret_policy_opaque);
bpl::def("add_storage_target", &pybake_provider_add_storage_target);
bpl::def("remove_storage_target", &pybake_provider_remove_storage_target);
bpl::def("remove_all_storage_targets", &pybake_provider_remove_all_storage_targets);
bpl::def("count_storage_targets", &pybake_provider_count_storage_targets);
bpl::def("list_storage_targets", &pybake_provider_list_storage_targets);
bpl::def("make_pool", &pybake_make_pool);
#undef ret_policy_opaque
py11::module::import("_pybaketarget");
m.def("register", &pybake_provider_register);
m.def("add_storage_target", &pybake_provider_add_storage_target);
m.def("remove_storage_target", &pybake_provider_remove_storage_target);
m.def("remove_all_storage_targets", &pybake_provider_remove_all_storage_targets);
m.def("count_storage_targets", &pybake_provider_count_storage_targets);
m.def("list_storage_targets", &pybake_provider_list_storage_targets);
m.def("make_pool", &pybake_make_pool);
}
......@@ -3,14 +3,7 @@
*
* See COPYRIGHT in top-level directory.
*/
#define BOOST_NO_AUTO_PTR
#include <boost/python.hpp>
#include <boost/python/return_opaque_pointer.hpp>
#include <boost/python/handle.hpp>
#include <boost/python/enum.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>
#include <boost/python/return_value_policy.hpp>
#include <pybind11/pybind11.h>
#include <string>
#include <vector>
#include <cstring>
......@@ -19,43 +12,45 @@
#include <bake.h>
#include <bake-server.h>
namespace bpl = boost::python;
namespace py11 = pybind11;
static std::string pybake_target_id_to_string(bake_target_id_t tid) {
static py11::bytes pybake_target_id_to_string(bake_target_id_t tid) {
char id[37];
uuid_unparse(tid.id, id);
return std::string(id);
return py11::bytes(std::string(id));
}
static bpl::object pybake_target_id_from_string(const std::string& tidstr) {
static py11::object pybake_target_id_from_string(const py11::bytes& btidstr) {
bake_target_id_t tid;
memset(tid.id, 0, sizeof(uuid_t));
if(tidstr.size() != 36) return bpl::object();
std::string tidstr = (std::string)btidstr;
if(tidstr.size() != 36) return py11::none();
int ret = uuid_parse((char*)tidstr.c_str(), tid.id);
if(ret == 0) return bpl::object(tid);
else return bpl::object();
if(ret == 0) return py11::cast(tid);
else return py11::none();
}
static std::string pybake_region_id_to_string(const bake_region_id_t& region_id) {
static py11::bytes pybake_region_id_to_string(const bake_region_id_t& region_id) {
std::string result((const char*)(&region_id), sizeof(region_id));
return result;
return py11::bytes(result);
}
static bpl::object pybake_region_id_from_string(const std::string& region_str) {
static py11::object pybake_region_id_from_string(const py11::bytes& bregion_str) {
bake_region_id_t result;
std::string region_str = (std::string)bregion_str;
memset(&result, 0, sizeof(result));
if(region_str.size() != sizeof(bake_region_id_t))
return bpl::object();
return py11::none();
memcpy(&result, region_str.data(), sizeof(bake_region_id_t));
return bpl::object(result);
return py11::cast(result);
}
BOOST_PYTHON_MODULE(_pybaketarget)
PYBIND11_MODULE(_pybaketarget, m)
{
bpl::class_<bake_target_id_t>("bake_target_id", bpl::no_init)
py11::class_<bake_target_id_t>(m,"bake_target_id")
.def("__str__", pybake_target_id_to_string);
bpl::def("target_id_from_string", pybake_target_id_from_string);
bpl::class_<bake_region_id_t>("bake_region_id", bpl::no_init)
m.def("target_id_from_string", pybake_target_id_from_string);
py11::class_<bake_region_id_t>(m,"bake_region_id")
.def("__str__", pybake_region_id_to_string);
bpl::def("region_id_from_string", pybake_region_id_from_string);
m.def("region_id_from_string", pybake_region_id_from_string);
}
......@@ -10,8 +10,6 @@ import sys
os.environ['OPT'] = " ".join(
flag for flag in opt.split() if flag != '-Wstrict-prototypes'
)
python_version = str(sys.version_info[0])+str(sys.version_info[1])
# Find out if Numpy is present
try:
import numpy
......@@ -23,23 +21,18 @@ except ImportError:
# For client...
pk = pkgconfig.parse('bake-client')
client_libraries = pk['libraries']
client_libraries.append('boost_python'+python_version)
if(has_numpy == 1):
client_libraries.append('boost_numpy'+python_version)
client_library_dirs = pk['library_dirs']
client_include_dirs = pk['include_dirs']
client_include_dirs.append(".")
# For server...
pk = pkgconfig.parse('bake-server')
server_libraries = pk['libraries']
server_libraries.append('boost_python'+python_version)
server_library_dirs = pk['library_dirs']
server_include_dirs = pk['include_dirs']
server_include_dirs.append(".")
# For target...
pk = pkgconfig.parse('uuid')
target_libraries = pk['libraries']
target_libraries.append('boost_python'+python_version)
target_library_dirs = pk['library_dirs']
target_include_dirs = pk['include_dirs']
target_include_dirs.append('.')
......
......@@ -8,7 +8,7 @@ from pybake.server import BakeProvider
mid = MargoInstance('tcp')
mid.enable_remote_shutdown()
mplex_id = 42
print "Server running at address "+str(mid.addr())+"with mplex_id="+str(mplex_id)
print "Server running at address "+str(mid.addr())+" with mplex_id="+str(mplex_id)
provider = BakeProvider(mid, mplex_id)
target = provider.add_storage_target("/dev/shm/baketarget")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment