mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Finish RPC calling side implementation
This commit is contained in:
parent
225ad3c289
commit
1d75063217
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.
|
||||||
|
*/
|
||||||
|
package org.apache.cloudstack.framework.messaging;
|
||||||
|
|
||||||
|
@OnwireName(name="RpcRequest")
|
||||||
|
public class RpcCallRequestPdu {
|
||||||
|
|
||||||
|
private long requestTag;
|
||||||
|
private long requestStartTick;
|
||||||
|
|
||||||
|
private String command;
|
||||||
|
private String serializedCommandArg;
|
||||||
|
|
||||||
|
public RpcCallRequestPdu() {
|
||||||
|
requestTag = 0;
|
||||||
|
requestStartTick = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestTag() {
|
||||||
|
return requestTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestTag(long requestTag) {
|
||||||
|
this.requestTag = requestTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestStartTick() {
|
||||||
|
return requestStartTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestStartTick(long requestStartTick) {
|
||||||
|
this.requestStartTick = requestStartTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommand(String command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerializedCommandArg() {
|
||||||
|
return serializedCommandArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSerializedCommandArg(String serializedCommandArg) {
|
||||||
|
this.serializedCommandArg = serializedCommandArg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.
|
||||||
|
*/
|
||||||
|
package org.apache.cloudstack.framework.messaging;
|
||||||
|
|
||||||
|
@OnwireName(name="RpcResponse")
|
||||||
|
public class RpcCallResponsePdu {
|
||||||
|
public static final int RESULT_SUCCESSFUL = 0;
|
||||||
|
public static final int RESULT_HANDLER_NOT_EXIST = 1;
|
||||||
|
public static final int RESULT_HANDLER_EXCEPTION = 2;
|
||||||
|
|
||||||
|
private long requestTag;
|
||||||
|
private long requestStartTick;
|
||||||
|
|
||||||
|
private int result;
|
||||||
|
private String command;
|
||||||
|
private String serializedResult;
|
||||||
|
|
||||||
|
public RpcCallResponsePdu() {
|
||||||
|
requestTag = 0;
|
||||||
|
requestStartTick = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestTag() {
|
||||||
|
return requestTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestTag(long requestTag) {
|
||||||
|
this.requestTag = requestTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestStartTick() {
|
||||||
|
return requestStartTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestStartTick(long requestStartTick) {
|
||||||
|
this.requestStartTick = requestStartTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResult(int result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommand(String command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerializedResult() {
|
||||||
|
return serializedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSerializedResult(String serializedResult) {
|
||||||
|
this.serializedResult = serializedResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,8 @@
|
|||||||
package org.apache.cloudstack.framework.messaging;
|
package org.apache.cloudstack.framework.messaging;
|
||||||
|
|
||||||
public interface RpcClientCall {
|
public interface RpcClientCall {
|
||||||
|
final static int DEFAULT_RPC_TIMEOUT = 10000;
|
||||||
|
|
||||||
String getCommand();
|
String getCommand();
|
||||||
RpcClientCall setCommand(String cmd);
|
RpcClientCall setCommand(String cmd);
|
||||||
RpcClientCall setTimeout(int timeoutMilliseconds);
|
RpcClientCall setTimeout(int timeoutMilliseconds);
|
||||||
|
|||||||
@ -18,7 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cloudstack.framework.messaging;
|
package org.apache.cloudstack.framework.messaging;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class RpcClientCallImpl implements RpcClientCall {
|
public class RpcClientCallImpl implements RpcClientCall {
|
||||||
@ -26,17 +28,30 @@ public class RpcClientCallImpl implements RpcClientCall {
|
|||||||
private String _command;
|
private String _command;
|
||||||
private Object _commandArg;
|
private Object _commandArg;
|
||||||
|
|
||||||
private int _timeoutMilliseconds;
|
private int _timeoutMilliseconds = DEFAULT_RPC_TIMEOUT;
|
||||||
|
|
||||||
private Map<String, Object> _contextParams = new HashMap<String, Object>();
|
private Map<String, Object> _contextParams = new HashMap<String, Object>();
|
||||||
|
private boolean _oneway = false;
|
||||||
|
|
||||||
public RpcClientCallImpl() {
|
private List<RpcCallbackListener> _callbackListeners = new ArrayList<RpcCallbackListener>();
|
||||||
|
|
||||||
|
private RpcProvider _rpcProvider;
|
||||||
|
private long _startTickInMs;
|
||||||
|
private long _callTag;
|
||||||
|
private String _sourceAddress;
|
||||||
|
private String _targetAddress;
|
||||||
|
|
||||||
|
private Object _responseLock = new Object();
|
||||||
|
private boolean _responseDone = false;;
|
||||||
|
private Object _responseResult;
|
||||||
|
|
||||||
|
public RpcClientCallImpl(RpcProvider rpcProvider) {
|
||||||
|
assert(rpcProvider != null);
|
||||||
|
_rpcProvider = rpcProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCommand() {
|
public String getCommand() {
|
||||||
// TODO Auto-generated method stub
|
return _command;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -71,37 +86,126 @@ public class RpcClientCallImpl implements RpcClientCall {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getContextParam(String key) {
|
public Object getContextParam(String key) {
|
||||||
// TODO Auto-generated method stub
|
return _contextParams.get(key);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> RpcClientCall addCallbackListener(RpcCallbackListener<T> listener) {
|
public <T> RpcClientCall addCallbackListener(RpcCallbackListener<T> listener) {
|
||||||
// TODO Auto-generated method stub
|
assert(listener != null);
|
||||||
return null;
|
_callbackListeners.add(listener);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RpcClientCall setOneway() {
|
public RpcClientCall setOneway() {
|
||||||
// TODO Auto-generated method stub
|
_oneway = true;
|
||||||
return null;
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceAddress() {
|
||||||
|
return _sourceAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceAddress(String sourceAddress) {
|
||||||
|
_sourceAddress = sourceAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetAddress() {
|
||||||
|
return _targetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetAddress(String targetAddress) {
|
||||||
|
_targetAddress = targetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCallTag() {
|
||||||
|
return _callTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallTag(long callTag) {
|
||||||
|
_callTag = callTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply() {
|
public void apply() {
|
||||||
// TODO Auto-generated method stub
|
// sanity check
|
||||||
|
assert(_sourceAddress != null);
|
||||||
|
assert(_targetAddress != null);
|
||||||
|
|
||||||
|
if(!_oneway)
|
||||||
|
_rpcProvider.registerCall(this);
|
||||||
|
|
||||||
|
RpcCallRequestPdu pdu = new RpcCallRequestPdu();
|
||||||
|
pdu.setCommand(getCommand());
|
||||||
|
if(_commandArg != null)
|
||||||
|
pdu.setSerializedCommandArg(_rpcProvider.getMessageSerializer().serializeTo(_commandArg.getClass(), _commandArg));
|
||||||
|
pdu.setRequestTag(this.getCallTag());
|
||||||
|
|
||||||
|
_rpcProvider.sendRpcPdu(getSourceAddress(), getTargetAddress(),
|
||||||
|
_rpcProvider.getMessageSerializer().serializeTo(RpcCallRequestPdu.class, pdu));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
// TODO Auto-generated method stub
|
_rpcProvider.cancelCall(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T get() {
|
public <T> T get() {
|
||||||
// TODO Auto-generated method stub
|
if(!_oneway) {
|
||||||
|
synchronized(_responseLock) {
|
||||||
|
if(!_responseDone) {
|
||||||
|
long timeToWait = _timeoutMilliseconds - (System.currentTimeMillis() - _startTickInMs);
|
||||||
|
if(timeToWait < 0)
|
||||||
|
timeToWait = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
_responseLock.wait(timeToWait);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RpcTimeoutException("RPC call timed out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(_responseDone);
|
||||||
|
|
||||||
|
if(_responseResult == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if(_responseResult instanceof RpcException)
|
||||||
|
throw (RpcException)_responseResult;
|
||||||
|
|
||||||
|
assert(_rpcProvider.getMessageSerializer() != null);
|
||||||
|
assert(_responseResult instanceof String);
|
||||||
|
return _rpcProvider.getMessageSerializer().serializeFrom((String)_responseResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void complete(String result) {
|
||||||
|
_responseResult = result;
|
||||||
|
|
||||||
|
synchronized(_responseLock) {
|
||||||
|
_responseDone = true;
|
||||||
|
_responseLock.notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(_rpcProvider.getMessageSerializer() != null);
|
||||||
|
Object resultObject = _rpcProvider.getMessageSerializer().serializeFrom(result);
|
||||||
|
for(RpcCallbackListener listener: _callbackListeners)
|
||||||
|
listener.onSuccess(resultObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void complete(RpcException e) {
|
||||||
|
_responseResult = e;
|
||||||
|
|
||||||
|
synchronized(_responseLock) {
|
||||||
|
_responseDone = true;
|
||||||
|
|
||||||
|
_responseLock.notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(RpcCallbackListener listener: _callbackListeners)
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,11 +19,17 @@
|
|||||||
package org.apache.cloudstack.framework.messaging;
|
package org.apache.cloudstack.framework.messaging;
|
||||||
|
|
||||||
public interface RpcProvider extends TransportMultiplexier {
|
public interface RpcProvider extends TransportMultiplexier {
|
||||||
|
final static String RPC_MULTIPLEXIER = "rpc";
|
||||||
|
|
||||||
void setMessageSerializer(MessageSerializer messageSerializer);
|
void setMessageSerializer(MessageSerializer messageSerializer);
|
||||||
MessageSerializer getMessageSerializer();
|
MessageSerializer getMessageSerializer();
|
||||||
|
|
||||||
void registerRpcServiceEndpoint(RpcServiceEndpoint rpcEndpoint);
|
void registerRpcServiceEndpoint(RpcServiceEndpoint rpcEndpoint);
|
||||||
void unregisteRpcServiceEndpoint(RpcServiceEndpoint rpcEndpoint);
|
void unregisteRpcServiceEndpoint(RpcServiceEndpoint rpcEndpoint);
|
||||||
|
|
||||||
RpcClientCall target(String target);
|
RpcClientCall newCall(String sourceAddress, String targetAddress);
|
||||||
|
void registerCall(RpcClientCall call);
|
||||||
|
void cancelCall(RpcClientCall call);
|
||||||
|
|
||||||
|
void sendRpcPdu(String sourceAddress, String targetAddress, String serializedPdu);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,13 +19,18 @@
|
|||||||
package org.apache.cloudstack.framework.messaging;
|
package org.apache.cloudstack.framework.messaging;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class RpcProviderImpl implements RpcProvider {
|
public class RpcProviderImpl implements RpcProvider {
|
||||||
|
|
||||||
|
private TransportProvider _transportProvider;
|
||||||
private MessageSerializer _messageSerializer;
|
private MessageSerializer _messageSerializer;
|
||||||
private List<RpcServiceEndpoint> _serviceEndpoints = new ArrayList<RpcServiceEndpoint>();
|
private List<RpcServiceEndpoint> _serviceEndpoints = new ArrayList<RpcServiceEndpoint>();
|
||||||
private TransportProvider _transportProvider;
|
private Map<Long, RpcClientCall> _outstandingCalls = new HashMap<Long, RpcClientCall>();
|
||||||
|
|
||||||
|
private long _nextCallTag = System.currentTimeMillis();
|
||||||
|
|
||||||
public RpcProviderImpl() {
|
public RpcProviderImpl() {
|
||||||
}
|
}
|
||||||
@ -41,9 +46,16 @@ public class RpcProviderImpl implements RpcProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void onTransportMessage(String senderEndpointAddress,
|
public void onTransportMessage(String senderEndpointAddress,
|
||||||
String targetEndpointAddress, String multiplexer, String message) {
|
String targetEndpointAddress, String multiplexer, String message) {
|
||||||
|
assert(_messageSerializer != null);
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
|
Object pdu = _messageSerializer.serializeFrom(message);
|
||||||
|
if(pdu instanceof RpcCallRequestPdu) {
|
||||||
|
handleCallRequestPdu((RpcCallRequestPdu)pdu);
|
||||||
|
} else if(pdu instanceof RpcCallResponsePdu) {
|
||||||
|
handleCallResponsePdu((RpcCallResponsePdu)pdu);
|
||||||
|
} else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -71,8 +83,76 @@ public class RpcProviderImpl implements RpcProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RpcClientCall target(String target) {
|
public RpcClientCall newCall(String sourceAddress, String targetAddress) {
|
||||||
// TODO Auto-generated method stub
|
long callTag = getNextCallTag();
|
||||||
return null;
|
RpcClientCallImpl call = new RpcClientCallImpl(this);
|
||||||
|
call.setSourceAddress(sourceAddress);
|
||||||
|
call.setTargetAddress(targetAddress);
|
||||||
|
call.setCallTag(callTag);
|
||||||
|
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerCall(RpcClientCall call) {
|
||||||
|
assert(call != null);
|
||||||
|
synchronized(this) {
|
||||||
|
_outstandingCalls.put(((RpcClientCallImpl)call).getCallTag(), call);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelCall(RpcClientCall call) {
|
||||||
|
synchronized(this) {
|
||||||
|
_outstandingCalls.remove(((RpcClientCallImpl)call).getCallTag());
|
||||||
|
}
|
||||||
|
|
||||||
|
((RpcClientCallImpl)call).complete(new RpcException("Call is cancelled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendRpcPdu(String sourceAddress, String targetAddress, String serializedPdu) {
|
||||||
|
assert(_transportProvider != null);
|
||||||
|
_transportProvider.sendMessage(sourceAddress, targetAddress, this.RPC_MULTIPLEXIER, serializedPdu);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized long getNextCallTag() {
|
||||||
|
long tag = _nextCallTag++;
|
||||||
|
if(tag == 0)
|
||||||
|
tag++;
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCallRequestPdu(RpcCallRequestPdu pdu) {
|
||||||
|
// ???
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCallResponsePdu(RpcCallResponsePdu pdu) {
|
||||||
|
RpcClientCallImpl call = null;
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
call = (RpcClientCallImpl)_outstandingCalls.remove(pdu.getRequestTag());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(call != null) {
|
||||||
|
switch(pdu.getResult()) {
|
||||||
|
case RpcCallResponsePdu.RESULT_SUCCESSFUL :
|
||||||
|
call.complete(pdu.getSerializedResult());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RpcCallResponsePdu.RESULT_HANDLER_NOT_EXIST :
|
||||||
|
call.complete(new RpcException("Handler does not exist"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RpcCallResponsePdu.RESULT_HANDLER_EXCEPTION :
|
||||||
|
call.complete(new RpcException("Exception in handler"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default :
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,4 +21,7 @@ package org.apache.cloudstack.framework.messaging;
|
|||||||
public interface TransportProvider {
|
public interface TransportProvider {
|
||||||
void attach(TransportEndpoint endpoint, String predefinedAddress);
|
void attach(TransportEndpoint endpoint, String predefinedAddress);
|
||||||
void detach(TransportEndpoint endpoint);
|
void detach(TransportEndpoint endpoint);
|
||||||
|
|
||||||
|
void sendMessage(String soureEndpointAddress, String targetEndpointAddress,
|
||||||
|
String multiplexier, String message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,4 +32,11 @@ public class ClientTransportProvider implements TransportProvider {
|
|||||||
public void detach(TransportEndpoint endpoint) {
|
public void detach(TransportEndpoint endpoint) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String soureEndpointAddress, String targetEndpointAddress,
|
||||||
|
String multiplexier, String message) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,4 +34,10 @@ public class ServerTransportProvider implements TransportProvider {
|
|||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String soureEndpointAddress, String targetEndpointAddress,
|
||||||
|
String multiplexier, String message) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user