HBASE-11344 Hide row keys and such from the web UIs
This commit is contained in:
parent
15831cefd5
commit
9f8d1876a0
|
@ -261,4 +261,13 @@
|
||||||
<Bug pattern="FE_FLOATING_POINT_EQUALITY"/>
|
<Bug pattern="FE_FLOATING_POINT_EQUALITY"/>
|
||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
|
<Match>
|
||||||
|
<Class name="org.apache.hadoop.hbase.HRegionInfo"/>
|
||||||
|
<Or>
|
||||||
|
<Method name="getEndKeyForDisplay"/>
|
||||||
|
<Method name="getStartKeyForDisplay"/>
|
||||||
|
</Or>
|
||||||
|
<Bug pattern="MS_EXPOSE_REP"/>
|
||||||
|
</Match>
|
||||||
|
|
||||||
</FindBugsFilter>
|
</FindBugsFilter>
|
||||||
|
|
|
@ -33,9 +33,11 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.classification.InterfaceStability;
|
import org.apache.hadoop.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.hbase.KeyValue.KVComparator;
|
import org.apache.hadoop.hbase.KeyValue.KVComparator;
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
import org.apache.hadoop.hbase.client.Result;
|
||||||
import org.apache.hadoop.hbase.exceptions.DeserializationException;
|
import org.apache.hadoop.hbase.exceptions.DeserializationException;
|
||||||
|
import org.apache.hadoop.hbase.master.RegionState;
|
||||||
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
|
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
|
||||||
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
|
||||||
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
|
||||||
|
@ -220,6 +222,9 @@ public class HRegionInfo implements Comparable<HRegionInfo> {
|
||||||
|
|
||||||
// Current TableName
|
// Current TableName
|
||||||
private TableName tableName = null;
|
private TableName tableName = null;
|
||||||
|
final static String DISPLAY_KEYS_KEY = "hbase.display.keys";
|
||||||
|
public final static byte[] HIDDEN_END_KEY = Bytes.toBytes("hidden-end-key");
|
||||||
|
public final static byte[] HIDDEN_START_KEY = Bytes.toBytes("hidden-start-key");
|
||||||
|
|
||||||
/** HRegionInfo for first meta region */
|
/** HRegionInfo for first meta region */
|
||||||
public static final HRegionInfo FIRST_META_REGIONINFO =
|
public static final HRegionInfo FIRST_META_REGIONINFO =
|
||||||
|
@ -1122,6 +1127,104 @@ public class HRegionInfo implements Comparable<HRegionInfo> {
|
||||||
return ProtobufUtil.toDelimitedByteArray(convert());
|
return ProtobufUtil.toDelimitedByteArray(convert());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the descriptive name as {@link RegionState} does it but with hidden
|
||||||
|
* startkey optionally
|
||||||
|
* @param state
|
||||||
|
* @param conf
|
||||||
|
* @return descriptive string
|
||||||
|
*/
|
||||||
|
public static String getDescriptiveNameFromRegionStateForDisplay(RegionState state,
|
||||||
|
Configuration conf) {
|
||||||
|
if (conf.getBoolean(DISPLAY_KEYS_KEY, true)) return state.toDescriptiveString();
|
||||||
|
String descriptiveStringFromState = state.toDescriptiveString();
|
||||||
|
int idx = descriptiveStringFromState.lastIndexOf(" state=");
|
||||||
|
String regionName = getRegionNameAsStringForDisplay(state.getRegion(), conf);
|
||||||
|
return regionName + descriptiveStringFromState.substring(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the end key for display. Optionally hide the real end key.
|
||||||
|
* @param hri
|
||||||
|
* @param conf
|
||||||
|
* @return the endkey
|
||||||
|
*/
|
||||||
|
public static byte[] getEndKeyForDisplay(HRegionInfo hri, Configuration conf) {
|
||||||
|
boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true);
|
||||||
|
if (displayKey) return hri.getEndKey();
|
||||||
|
return HIDDEN_END_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the start key for display. Optionally hide the real start key.
|
||||||
|
* @param hri
|
||||||
|
* @param conf
|
||||||
|
* @return the startkey
|
||||||
|
*/
|
||||||
|
public static byte[] getStartKeyForDisplay(HRegionInfo hri, Configuration conf) {
|
||||||
|
boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true);
|
||||||
|
if (displayKey) return hri.getStartKey();
|
||||||
|
return HIDDEN_START_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the region name for display. Optionally hide the start key.
|
||||||
|
* @param hri
|
||||||
|
* @param conf
|
||||||
|
* @return region name as String
|
||||||
|
*/
|
||||||
|
public static String getRegionNameAsStringForDisplay(HRegionInfo hri, Configuration conf) {
|
||||||
|
return Bytes.toStringBinary(getRegionNameForDisplay(hri, conf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the region name for display. Optionally hide the start key.
|
||||||
|
* @param hri
|
||||||
|
* @param conf
|
||||||
|
* @return region name bytes
|
||||||
|
*/
|
||||||
|
public static byte[] getRegionNameForDisplay(HRegionInfo hri, Configuration conf) {
|
||||||
|
boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true);
|
||||||
|
if (displayKey || hri.getTable().equals(TableName.META_TABLE_NAME)) {
|
||||||
|
return hri.getRegionName();
|
||||||
|
} else {
|
||||||
|
// create a modified regionname with the startkey replaced but preserving
|
||||||
|
// the other parts including the encodedname.
|
||||||
|
try {
|
||||||
|
byte[][]regionNameParts = parseRegionName(hri.getRegionName());
|
||||||
|
regionNameParts[1] = HIDDEN_START_KEY; //replace the real startkey
|
||||||
|
int len = 0;
|
||||||
|
// get the total length
|
||||||
|
for (byte[] b : regionNameParts) {
|
||||||
|
len += b.length;
|
||||||
|
}
|
||||||
|
byte[] encodedRegionName =
|
||||||
|
Bytes.toBytes(encodeRegionName(hri.getRegionName()));
|
||||||
|
len += encodedRegionName.length;
|
||||||
|
//allocate some extra bytes for the delimiters and the last '.'
|
||||||
|
byte[] modifiedName = new byte[len + regionNameParts.length + 1];
|
||||||
|
int lengthSoFar = 0;
|
||||||
|
int loopCount = 0;
|
||||||
|
for (byte[] b : regionNameParts) {
|
||||||
|
System.arraycopy(b, 0, modifiedName, lengthSoFar, b.length);
|
||||||
|
lengthSoFar += b.length;
|
||||||
|
if (loopCount++ == 2) modifiedName[lengthSoFar++] = REPLICA_ID_DELIMITER;
|
||||||
|
else modifiedName[lengthSoFar++] = HConstants.DELIMITER;
|
||||||
|
}
|
||||||
|
// replace the last comma with '.'
|
||||||
|
modifiedName[lengthSoFar - 1] = ENC_SEPARATOR;
|
||||||
|
System.arraycopy(encodedRegionName, 0, modifiedName, lengthSoFar,
|
||||||
|
encodedRegionName.length);
|
||||||
|
lengthSoFar += encodedRegionName.length;
|
||||||
|
modifiedName[lengthSoFar] = ENC_SEPARATOR;
|
||||||
|
return modifiedName;
|
||||||
|
} catch (IOException e) {
|
||||||
|
//LOG.warn("Encountered exception " + e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a HRegionInfo and ServerName from catalog table {@link Result}.
|
* Extract a HRegionInfo and ServerName from catalog table {@link Result}.
|
||||||
* @param r Result to pull from
|
* @param r Result to pull from
|
||||||
|
|
|
@ -836,6 +836,13 @@ possible configurations would overwhelm and obscure the important.
|
||||||
When false (the default), the client will not allow the fallback to SIMPLE
|
When false (the default), the client will not allow the fallback to SIMPLE
|
||||||
authentication, and will abort the connection.</description>
|
authentication, and will abort the connection.</description>
|
||||||
</property>
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>hbase.display.keys</name>
|
||||||
|
<value>true</value>
|
||||||
|
<description>When this is set to true the webUI and such will display all start/end keys
|
||||||
|
as part of the table details, region names, etc. When this is set to false,
|
||||||
|
the keys are hidden.</description>
|
||||||
|
</property>
|
||||||
<property>
|
<property>
|
||||||
<name>hbase.coprocessor.region.classes</name>
|
<name>hbase.coprocessor.region.classes</name>
|
||||||
<value></value>
|
<value></value>
|
||||||
|
|
|
@ -85,7 +85,9 @@ if (toRemove > 0) {
|
||||||
<%else>
|
<%else>
|
||||||
<tr>
|
<tr>
|
||||||
</%if>
|
</%if>
|
||||||
<td><% entry.getKey() %></td><td><% entry.getValue().toDescriptiveString() %></td>
|
<td><% entry.getKey() %></td><td>
|
||||||
|
<% HRegionInfo.getDescriptiveNameFromRegionStateForDisplay(
|
||||||
|
entry.getValue(), conf) %></td>
|
||||||
<td><% (currentTime - entry.getValue().getStamp()) %> </td></tr>
|
<td><% (currentTime - entry.getValue().getStamp()) %> </td></tr>
|
||||||
</%for>
|
</%for>
|
||||||
<tr BGCOLOR="#D7DF01"> <td>Total number of Regions in Transition for more than <% ritThreshold %> milliseconds</td><td> <% numOfRITOverThreshold %></td><td></td>
|
<tr BGCOLOR="#D7DF01"> <td>Total number of Regions in Transition for more than <% ritThreshold %> milliseconds</td><td> <% numOfRITOverThreshold %></td><td></td>
|
||||||
|
|
|
@ -93,9 +93,12 @@
|
||||||
|
|
||||||
<%for HRegionInfo r: onlineRegions %>
|
<%for HRegionInfo r: onlineRegions %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><% r.getRegionNameAsString() %></td>
|
<td><% HRegionInfo.getRegionNameAsStringForDisplay(r,
|
||||||
<td><% Bytes.toStringBinary(r.getStartKey()) %></td>
|
regionServer.getConfiguration()) %></td>
|
||||||
<td><% Bytes.toStringBinary(r.getEndKey()) %></td>
|
<td><% Bytes.toStringBinary(HRegionInfo.getStartKeyForDisplay(r,
|
||||||
|
regionServer.getConfiguration())) %></td>
|
||||||
|
<td><% Bytes.toStringBinary(HRegionInfo.getEndKeyForDisplay(r,
|
||||||
|
regionServer.getConfiguration())) %></td>
|
||||||
<td><% r.getReplicaId() %></td>
|
<td><% r.getReplicaId() %></td>
|
||||||
</tr>
|
</tr>
|
||||||
</%for>
|
</%for>
|
||||||
|
@ -119,7 +122,8 @@
|
||||||
<%java>
|
<%java>
|
||||||
RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
|
RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
|
||||||
</%java>
|
</%java>
|
||||||
<td><% r.getRegionNameAsString() %></td>
|
<td><% HRegionInfo.getRegionNameAsStringForDisplay(r,
|
||||||
|
regionServer.getConfiguration()) %></td>
|
||||||
<%if load != null %>
|
<%if load != null %>
|
||||||
<td><% load.getReadRequestsCount() %></td>
|
<td><% load.getReadRequestsCount() %></td>
|
||||||
<td><% load.getWriteRequestsCount() %></td>
|
<td><% load.getWriteRequestsCount() %></td>
|
||||||
|
@ -151,7 +155,8 @@
|
||||||
<%java>
|
<%java>
|
||||||
RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
|
RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
|
||||||
</%java>
|
</%java>
|
||||||
<td><% r.getRegionNameAsString() %></td>
|
<td><% HRegionInfo.getRegionNameAsStringForDisplay(r,
|
||||||
|
regionServer.getConfiguration()) %></td>
|
||||||
<%if load != null %>
|
<%if load != null %>
|
||||||
<td><% load.getStores() %></td>
|
<td><% load.getStores() %></td>
|
||||||
<td><% load.getStorefiles() %></td>
|
<td><% load.getStorefiles() %></td>
|
||||||
|
@ -189,7 +194,8 @@
|
||||||
((float) load.getCurrentCompactedKVs() / load.getTotalCompactingKVs())) + "%";
|
((float) load.getCurrentCompactedKVs() / load.getTotalCompactingKVs())) + "%";
|
||||||
}
|
}
|
||||||
</%java>
|
</%java>
|
||||||
<td><% r.getRegionNameAsString() %></td>
|
<td><% HRegionInfo.getRegionNameAsStringForDisplay(r,
|
||||||
|
regionServer.getConfiguration()) %></td>
|
||||||
<%if load != null %>
|
<%if load != null %>
|
||||||
<td><% load.getTotalCompactingKVs() %></td>
|
<td><% load.getTotalCompactingKVs() %></td>
|
||||||
<td><% load.getCurrentCompactedKVs() %></td>
|
<td><% load.getCurrentCompactedKVs() %></td>
|
||||||
|
@ -216,7 +222,8 @@
|
||||||
<%java>
|
<%java>
|
||||||
RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
|
RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
|
||||||
</%java>
|
</%java>
|
||||||
<td><% r.getRegionNameAsString() %></td>
|
<td><% HRegionInfo.getRegionNameAsStringForDisplay(r,
|
||||||
|
regionServer.getConfiguration()) %></td>
|
||||||
<%if load != null %>
|
<%if load != null %>
|
||||||
<td><% load.getMemstoreSizeMB() %>m</td>
|
<td><% load.getMemstoreSizeMB() %>m</td>
|
||||||
</%if>
|
</%if>
|
||||||
|
|
|
@ -283,7 +283,8 @@
|
||||||
}
|
}
|
||||||
%>
|
%>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= escapeXml(Bytes.toStringBinary(regionInfo.getRegionName())) %></td>
|
<td><%= escapeXml(Bytes.toStringBinary(HRegionInfo.getRegionNameForDisplay(regionInfo,
|
||||||
|
conf))) %></td>
|
||||||
<%
|
<%
|
||||||
if (addr != null) {
|
if (addr != null) {
|
||||||
String url = "//" + addr.getHostname() + ":" + master.getRegionServerInfoPort(addr) + "/";
|
String url = "//" + addr.getHostname() + ":" + master.getRegionServerInfoPort(addr) + "/";
|
||||||
|
@ -298,8 +299,10 @@
|
||||||
<%
|
<%
|
||||||
}
|
}
|
||||||
%>
|
%>
|
||||||
<td><%= escapeXml(Bytes.toStringBinary(regionInfo.getStartKey())) %></td>
|
<td><%= escapeXml(Bytes.toStringBinary(HRegionInfo.getStartKeyForDisplay(regionInfo,
|
||||||
<td><%= escapeXml(Bytes.toStringBinary(regionInfo.getEndKey())) %></td>
|
conf))) %></td>
|
||||||
|
<td><%= escapeXml(Bytes.toStringBinary(HRegionInfo.getEndKeyForDisplay(regionInfo,
|
||||||
|
conf))) %></td>
|
||||||
<td><%= req%></td>
|
<td><%= req%></td>
|
||||||
<%
|
<%
|
||||||
if (withReplica) {
|
if (withReplica) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.FileStatus;
|
import org.apache.hadoop.fs.FileStatus;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
|
@ -34,10 +35,12 @@ import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
import org.apache.hadoop.hbase.SmallTests;
|
import org.apache.hadoop.hbase.SmallTests;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
import org.apache.hadoop.hbase.TableName;
|
||||||
import org.apache.hadoop.hbase.exceptions.DeserializationException;
|
import org.apache.hadoop.hbase.exceptions.DeserializationException;
|
||||||
|
import org.apache.hadoop.hbase.master.RegionState;
|
||||||
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
|
||||||
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
import org.apache.hadoop.hbase.util.MD5Hash;
|
import org.apache.hadoop.hbase.util.MD5Hash;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@ -254,6 +257,71 @@ public class TestHRegionInfo {
|
||||||
|
|
||||||
assertEquals(expectedHri, convertedHri);
|
assertEquals(expectedHri, convertedHri);
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
public void testRegionDetailsForDisplay() throws IOException {
|
||||||
|
byte[] startKey = new byte[] {0x01, 0x01, 0x02, 0x03};
|
||||||
|
byte[] endKey = new byte[] {0x01, 0x01, 0x02, 0x04};
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
conf.setBoolean("hbase.display.keys", false);
|
||||||
|
HRegionInfo h = new HRegionInfo(TableName.valueOf("foo"), startKey, endKey);
|
||||||
|
checkEquality(h, conf);
|
||||||
|
// check HRIs with non-default replicaId
|
||||||
|
h = new HRegionInfo(TableName.valueOf("foo"), startKey, endKey, false,
|
||||||
|
System.currentTimeMillis(), 1);
|
||||||
|
checkEquality(h, conf);
|
||||||
|
Assert.assertArrayEquals(HRegionInfo.HIDDEN_END_KEY,
|
||||||
|
HRegionInfo.getEndKeyForDisplay(h, conf));
|
||||||
|
Assert.assertArrayEquals(HRegionInfo.HIDDEN_START_KEY,
|
||||||
|
HRegionInfo.getStartKeyForDisplay(h, conf));
|
||||||
|
|
||||||
|
RegionState state = new RegionState(h, RegionState.State.OPEN);
|
||||||
|
String descriptiveNameForDisplay =
|
||||||
|
HRegionInfo.getDescriptiveNameFromRegionStateForDisplay(state, conf);
|
||||||
|
checkDescriptiveNameEquality(descriptiveNameForDisplay,state.toDescriptiveString(), startKey);
|
||||||
|
|
||||||
|
conf.setBoolean("hbase.display.keys", true);
|
||||||
|
Assert.assertArrayEquals(endKey, HRegionInfo.getEndKeyForDisplay(h, conf));
|
||||||
|
Assert.assertArrayEquals(startKey, HRegionInfo.getStartKeyForDisplay(h, conf));
|
||||||
|
Assert.assertEquals(state.toDescriptiveString(),
|
||||||
|
HRegionInfo.getDescriptiveNameFromRegionStateForDisplay(state, conf));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDescriptiveNameEquality(String descriptiveNameForDisplay, String origDesc,
|
||||||
|
byte[] startKey) {
|
||||||
|
// except for the "hidden-start-key" substring everything else should exactly match
|
||||||
|
String firstPart = descriptiveNameForDisplay.substring(0,
|
||||||
|
descriptiveNameForDisplay.indexOf(new String(HRegionInfo.HIDDEN_START_KEY)));
|
||||||
|
String secondPart = descriptiveNameForDisplay.substring(
|
||||||
|
descriptiveNameForDisplay.indexOf(new String(HRegionInfo.HIDDEN_START_KEY)) +
|
||||||
|
HRegionInfo.HIDDEN_START_KEY.length);
|
||||||
|
String firstPartOrig = origDesc.substring(0,
|
||||||
|
origDesc.indexOf(Bytes.toStringBinary(startKey)));
|
||||||
|
String secondPartOrig = origDesc.substring(
|
||||||
|
origDesc.indexOf(Bytes.toStringBinary(startKey)) +
|
||||||
|
Bytes.toStringBinary(startKey).length());
|
||||||
|
assert(firstPart.equals(firstPartOrig));
|
||||||
|
assert(secondPart.equals(secondPartOrig));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEquality(HRegionInfo h, Configuration conf) throws IOException {
|
||||||
|
byte[] modifiedRegionName = HRegionInfo.getRegionNameForDisplay(h, conf);
|
||||||
|
byte[][] modifiedRegionNameParts = HRegionInfo.parseRegionName(modifiedRegionName);
|
||||||
|
byte[][] regionNameParts = HRegionInfo.parseRegionName(h.getRegionName());
|
||||||
|
|
||||||
|
//same number of parts
|
||||||
|
assert(modifiedRegionNameParts.length == regionNameParts.length);
|
||||||
|
|
||||||
|
for (int i = 0; i < regionNameParts.length; i++) {
|
||||||
|
// all parts should match except for [1] where in the modified one,
|
||||||
|
// we should have "hidden_start_key"
|
||||||
|
if (i != 1) {
|
||||||
|
Assert.assertArrayEquals(regionNameParts[i], modifiedRegionNameParts[i]);
|
||||||
|
} else {
|
||||||
|
Assert.assertNotEquals(regionNameParts[i][0], modifiedRegionNameParts[i][0]);
|
||||||
|
Assert.assertArrayEquals(modifiedRegionNameParts[1],
|
||||||
|
HRegionInfo.getStartKeyForDisplay(h, conf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue