From 486e5b99276611e703350a53143db830de4170aa Mon Sep 17 00:00:00 2001 From: Noah Watkins Date: Fri, 22 Mar 2013 12:42:47 -0700 Subject: [PATCH] java: support ceph_get_osd_addr Adds a few JNI utilities from the Android project (license: Apache) to help with IP address conversions. These functions are also updated to work in our environment (use Ceph exception utilities, edit header paths). Signed-off-by: Noah Watkins --- src/Makefile.am | 6 +- src/java/java/com/ceph/fs/CephMount.java | 18 +++ src/java/native/JniConstants.cpp | 42 ++++++ src/java/native/JniConstants.h | 52 +++++++ src/java/native/ScopedLocalRef.h | 63 ++++++++ src/java/native/libcephfs_jni.cc | 146 +++++++++++++++++++ src/java/test/com/ceph/fs/CephMountTest.java | 7 + 7 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 src/java/native/JniConstants.cpp create mode 100644 src/java/native/JniConstants.h create mode 100644 src/java/native/ScopedLocalRef.h diff --git a/src/Makefile.am b/src/Makefile.am index fd54df867e214..12bc53da6da9b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -561,7 +561,11 @@ endif ## - The JNI library is here ## - The Java source Makefile.am is in src/java if ENABLE_CEPHFS_JAVA -libcephfs_jni_la_SOURCES = java/native/libcephfs_jni.cc +libcephfs_jni_la_SOURCES = \ + java/native/libcephfs_jni.cc \ + java/native/ScopedLocalRef.h \ + java/native/JniConstants.cpp \ + java/native/JniConstants.h libcephfs_jni_la_LIBADD = libcephfs.la $(EXTRALIBS) libcephfs_jni_la_CPPFLAGS = $(JDK_CPPFLAGS) libcephfs_jni_la_CXXFLAGS = ${AM_CXXFLAGS} diff --git a/src/java/java/com/ceph/fs/CephMount.java b/src/java/java/com/ceph/fs/CephMount.java index dcc263063fedc..1dd373c759f10 100644 --- a/src/java/java/com/ceph/fs/CephMount.java +++ b/src/java/java/com/ceph/fs/CephMount.java @@ -21,6 +21,7 @@ package com.ceph.fs; import java.io.IOException; import java.io.FileNotFoundException; +import java.net.InetAddress; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.Arrays; @@ -1032,4 +1033,21 @@ public class CephMount { } private static native String[] native_ceph_get_osd_crush_location(long mountp, int osd); + + /** + * Get the network address of an OSD. + * + * @param osd The OSD device id. + * @return The network address. + */ + public InetAddress get_osd_address(int osd) { + rlock.lock(); + try { + return native_ceph_get_osd_addr(instance_ptr, osd); + } finally { + rlock.unlock(); + } + } + + private static native InetAddress native_ceph_get_osd_addr(long mountp, int osd); } diff --git a/src/java/native/JniConstants.cpp b/src/java/native/JniConstants.cpp new file mode 100644 index 0000000000000..cd54c99b980d1 --- /dev/null +++ b/src/java/native/JniConstants.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "JniConstants.h" +#include "ScopedLocalRef.h" + +#include + +jclass JniConstants::inet6AddressClass; +jclass JniConstants::inetAddressClass; +jclass JniConstants::inetSocketAddressClass; +jclass JniConstants::stringClass; + +static jclass findClass(JNIEnv* env, const char* name) { + ScopedLocalRef localClass(env, env->FindClass(name)); + jclass result = reinterpret_cast(env->NewGlobalRef(localClass.get())); + if (result == NULL) { + fprintf(stderr, "failed to find class '%s'", name); + abort(); + } + return result; +} + +void JniConstants::init(JNIEnv* env) { + inet6AddressClass = findClass(env, "java/net/Inet6Address"); + inetAddressClass = findClass(env, "java/net/InetAddress"); + inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress"); + stringClass = findClass(env, "java/lang/String"); +} diff --git a/src/java/native/JniConstants.h b/src/java/native/JniConstants.h new file mode 100644 index 0000000000000..acee86709d389 --- /dev/null +++ b/src/java/native/JniConstants.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JNI_CONSTANTS_H_included +#define JNI_CONSTANTS_H_included + +#include + +/** + * A cache to avoid calling FindClass at runtime. + * + * Class lookup is relatively expensive (2.5us on passion-eng at the time of writing), so we do + * all such lookups eagerly at VM startup. This means that code that never uses, say, + * java.util.zip.Deflater still has to pay for the lookup, but it means that on a device the cost + * is definitely paid during boot and amortized. A central cache also removes the temptation to + * dynamically call FindClass rather than add a small cache to each file that needs one. Another + * cost is that each class cached here requires a global reference, though in practice we save + * enough by not having a global reference for each file that uses a class such as java.lang.String + * which is used in several files. + * + * FindClass is still called in a couple of situations: when throwing exceptions, and in some of + * the serialization code. The former is clearly not a performance case, and we're currently + * assuming that neither is the latter. + * + * TODO: similar arguments hold for field and method IDs; we should cache them centrally too. + */ +struct JniConstants { + static void init(JNIEnv* env); + + static jclass inet6AddressClass; + static jclass inetAddressClass; + static jclass inetSocketAddressClass; + static jclass stringClass; +}; + +#define NATIVE_METHOD(className, functionName, signature) \ + { #functionName, signature, reinterpret_cast(className ## _ ## functionName) } + +#endif // JNI_CONSTANTS_H_included diff --git a/src/java/native/ScopedLocalRef.h b/src/java/native/ScopedLocalRef.h new file mode 100644 index 0000000000000..71d577679a957 --- /dev/null +++ b/src/java/native/ScopedLocalRef.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCOPED_LOCAL_REF_H_included +#define SCOPED_LOCAL_REF_H_included + +#include "jni.h" + +#include + +// A smart pointer that deletes a JNI local reference when it goes out of scope. +template +class ScopedLocalRef { +public: + ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) { + } + + ~ScopedLocalRef() { + reset(); + } + + void reset(T ptr = NULL) { + if (ptr != mLocalRef) { + if (mLocalRef != NULL) { + mEnv->DeleteLocalRef(mLocalRef); + } + mLocalRef = ptr; + } + } + + T release() __attribute__((warn_unused_result)) { + T localRef = mLocalRef; + mLocalRef = NULL; + return localRef; + } + + T get() const { + return mLocalRef; + } + +private: + JNIEnv* mEnv; + T mLocalRef; + + // Disallow copy and assignment. + ScopedLocalRef(const ScopedLocalRef&); + void operator=(const ScopedLocalRef&); +}; + +#endif // SCOPED_LOCAL_REF_H_included diff --git a/src/java/native/libcephfs_jni.cc b/src/java/native/libcephfs_jni.cc index 8da54989e25a4..5d9e26abca285 100644 --- a/src/java/native/libcephfs_jni.cc +++ b/src/java/native/libcephfs_jni.cc @@ -23,8 +23,12 @@ #include #include #include +#include #include +#include "ScopedLocalRef.h" +#include "JniConstants.h" + #include "include/cephfs/libcephfs.h" #include "common/dout.h" @@ -331,6 +335,8 @@ static void setup_field_ids(JNIEnv *env, jclass clz) if (!cephfileextent_ctor_fid) return; + JniConstants::init(env); + #undef GETFID cephmount_instance_ptr_fid = env->GetFieldID(clz, "instance_ptr", "J"); @@ -2795,3 +2801,143 @@ out: return path; } + +/* + * sockaddrToInetAddress uses with the following license, and is adapted for + * use in this project by using Ceph JNI exception utilities. + * + * ---- + * + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage& ss, jint* port) { + // Convert IPv4-mapped IPv6 addresses to IPv4 addresses. + // The RI states "Java will never return an IPv4-mapped address". + const sockaddr_in6& sin6 = reinterpret_cast(ss); + if (ss.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) { + // Copy the IPv6 address into the temporary sockaddr_storage. + sockaddr_storage tmp; + memset(&tmp, 0, sizeof(tmp)); + memcpy(&tmp, &ss, sizeof(sockaddr_in6)); + // Unmap it into an IPv4 address. + sockaddr_in& sin = reinterpret_cast(tmp); + sin.sin_family = AF_INET; + sin.sin_port = sin6.sin6_port; + memcpy(&sin.sin_addr.s_addr, &sin6.sin6_addr.s6_addr[12], 4); + // Do the regular conversion using the unmapped address. + return sockaddrToInetAddress(env, tmp, port); + } + + const void* rawAddress; + size_t addressLength; + int sin_port = 0; + int scope_id = 0; + if (ss.ss_family == AF_INET) { + const sockaddr_in& sin = reinterpret_cast(ss); + rawAddress = &sin.sin_addr.s_addr; + addressLength = 4; + sin_port = ntohs(sin.sin_port); + } else if (ss.ss_family == AF_INET6) { + const sockaddr_in6& sin6 = reinterpret_cast(ss); + rawAddress = &sin6.sin6_addr.s6_addr; + addressLength = 16; + sin_port = ntohs(sin6.sin6_port); + scope_id = sin6.sin6_scope_id; + } else if (ss.ss_family == AF_UNIX) { + const sockaddr_un& sun = reinterpret_cast(ss); + rawAddress = &sun.sun_path; + addressLength = strlen(sun.sun_path); + } else { + // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one + // really does imply an internal error. + //jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + // "sockaddrToInetAddress unsupported ss_family: %i", ss.ss_family); + cephThrowIllegalArg(env, "sockaddrToInetAddress unsupposed ss_family"); + return NULL; + } + if (port != NULL) { + *port = sin_port; + } + + ScopedLocalRef byteArray(env, env->NewByteArray(addressLength)); + if (byteArray.get() == NULL) { + return NULL; + } + env->SetByteArrayRegion(byteArray.get(), 0, addressLength, + reinterpret_cast(rawAddress)); + + if (ss.ss_family == AF_UNIX) { + // Note that we get here for AF_UNIX sockets on accept(2). The unix(7) man page claims + // that the peer's sun_path will contain the path, but in practice it doesn't, and the + // peer length is returned as 2 (meaning only the sun_family field was set). + // + // Ceph Note: this isn't supported. inetUnixAddress appears to just be + // something in Dalvik/Android stuff. + cephThrowInternal(env, "OSD address should never be a UNIX socket"); + return NULL; + //static jmethodID ctor = env->GetMethodID(JniConstants::inetUnixAddressClass, "", "([B)V"); + //return env->NewObject(JniConstants::inetUnixAddressClass, ctor, byteArray.get()); + } + + if (addressLength == 4) { + static jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inetAddressClass, + "getByAddress", "(Ljava/lang/String;[B)Ljava/net/InetAddress;"); + if (getByAddressMethod == NULL) { + return NULL; + } + return env->CallStaticObjectMethod(JniConstants::inetAddressClass, getByAddressMethod, + NULL, byteArray.get()); + } else if (addressLength == 16) { + static jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inet6AddressClass, + "getByAddress", "(Ljava/lang/String;[BI)Ljava/net/Inet6Address;"); + if (getByAddressMethod == NULL) { + return NULL; + } + return env->CallStaticObjectMethod(JniConstants::inet6AddressClass, getByAddressMethod, + NULL, byteArray.get(), scope_id); + } else { + abort(); + return NULL; + } +} + +/* + * Class: com_ceph_fs_CephMount + * Method: native_ceph_get_osd_addr + * Signature: (JI)Ljava/net/InetAddress; + */ +JNIEXPORT jobject JNICALL Java_com_ceph_fs_CephMount_native_1ceph_1get_1osd_1addr + (JNIEnv *env, jclass clz, jlong j_mntp, jint osd) +{ + struct ceph_mount_info *cmount = get_ceph_mount(j_mntp); + CephContext *cct = ceph_get_mount_context(cmount); + struct sockaddr_storage addr; + int ret; + + CHECK_MOUNTED(cmount, NULL); + + ldout(cct, 10) << "jni: get_osd_addr: osd " << osd << dendl; + + ret = ceph_get_osd_addr(cmount, osd, &addr); + + ldout(cct, 10) << "jni: get_osd_addr: ret " << ret << dendl; + + if (ret < 0) { + handle_error(env, ret); + return NULL; + } + + return sockaddrToInetAddress(env, addr, NULL); +} diff --git a/src/java/test/com/ceph/fs/CephMountTest.java b/src/java/test/com/ceph/fs/CephMountTest.java index 2922f589eba92..d218d2fe14b5d 100644 --- a/src/java/test/com/ceph/fs/CephMountTest.java +++ b/src/java/test/com/ceph/fs/CephMountTest.java @@ -22,6 +22,7 @@ package com.ceph.fs; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.InetAddress; import java.util.UUID; import org.junit.*; import static org.junit.Assert.*; @@ -993,4 +994,10 @@ public class CephMountTest { assertTrue(b.getName().length() > 0); } } + + @Test + public void test_get_osd_address() throws Exception { + InetAddress addr = mount.get_osd_address(0); + assertTrue(addr.getHostAddress().length() > 0); + } } -- 2.39.5