Commit a019cd16 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3171] Changes after review:

 - allocateLease6 => allocateLeases6()
 - consts added
 - consts removed
 - comments added, expanded, clarified, capitalized
parent b4810612
......@@ -1223,13 +1223,13 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// will try to honour the hint, but it is just a hint - some other address
// may be used instead. If fake_allocation is set to false, the lease will
// be inserted into the LeaseMgr as well.
Lease6Collection leases = alloc_engine_->allocateLease6(subnet, duid,
ia->getIAID(),
hint, Lease::TYPE_NA,
do_fwd, do_rev,
hostname,
fake_allocation,
callout_handle);
Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
ia->getIAID(),
hint, Lease::TYPE_NA,
do_fwd, do_rev,
hostname,
fake_allocation,
callout_handle);
/// @todo: Handle more than one lease
Lease6Ptr lease;
if (!leases.empty()) {
......
......@@ -58,7 +58,7 @@ AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
}
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) const {
AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
// Get a buffer holding an address.
const std::vector<uint8_t>& vec = addr.toBytes();
// Get the address length.
......@@ -87,7 +87,7 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len) const {
const uint8_t prefix_len) {
if (!prefix.isV6()) {
isc_throw(BadValue, "Prefix operations are for IPv6 only (attempted to "
"increase prefix " << prefix.toText() << ")");
......@@ -193,7 +193,7 @@ AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
<< " is not Pool6");
}
// Get the next prefix
next = increasePrefix(last, (pool6)->getLength());
next = increasePrefix(last, pool6->getLength());
}
if ((*it)->inRange(next)) {
// the next one is in the pool as well, so we haven't hit pool boundary yet
......@@ -293,12 +293,12 @@ AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts,
}
Lease6Collection
AllocEngine::allocateLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
uint32_t iaid, const IOAddress& hint,
Lease::Type type, const bool fwd_dns_update,
const bool rev_dns_update,
const std::string& hostname, bool fake_allocation,
const isc::hooks::CalloutHandlePtr& callout_handle) {
AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
uint32_t iaid, const IOAddress& hint,
Lease::Type type, const bool fwd_dns_update,
const bool rev_dns_update,
const std::string& hostname, bool fake_allocation,
const isc::hooks::CalloutHandlePtr& callout_handle) {
try {
AllocatorPtr allocator = getAllocator(type);
......@@ -696,7 +696,7 @@ Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const uint32_t iaid,
uint8_t prefix_len,
const bool fwd_dns_update,
const bool rev_dns_update,
......@@ -859,16 +859,20 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const uint32_t iaid,
const IOAddress& addr,
uint8_t prefix_len,
Lease::Type type,
const Lease::Type type,
const bool fwd_dns_update,
const bool rev_dns_update,
const std::string& hostname,
const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /*= false */ ) {
if (type != Lease::TYPE_PD) {
prefix_len = 128; // non-PD lease types must be always /128
}
Lease6Ptr lease(new Lease6(type, addr, duid, iaid,
subnet->getPreferred(), subnet->getValid(),
subnet->getT1(), subnet->getT2(), subnet->getID(),
......
......@@ -70,6 +70,14 @@ protected:
/// again if necessary. The number of times this method is called will
/// increase as the number of available leases will decrease.
///
/// This method can also be used to pick a prefix. We should not rename
/// it to pickLease(), because at this early stage there is no concept
/// of a lease yet. Here it is a matter of selecting one address or
/// prefix from the defined pool, without going into details who it is
/// for or who uses it. I thought that pickAddress() is less confusing
/// than pickResource(), because nobody would immediately know what the
/// resource means in this context.
///
/// @param subnet next address will be returned from pool of that subnet
/// @param duid Client's DUID
/// @param hint client's hint
......@@ -126,18 +134,19 @@ protected:
const isc::asiolink::IOAddress& hint);
protected:
/// @brief returns an address increased by one
/// @brief Returns an address increased by one
///
/// This method works for both IPv4 and IPv6 addresses.
/// This method works for both IPv4 and IPv6 addresses. For example,
/// increase 192.0.2.255 will become 192.0.3.0.
///
/// @param addr address to be increased
/// @return address increased by one
isc::asiolink::IOAddress
increaseAddress(const isc::asiolink::IOAddress& addr) const;
static isc::asiolink::IOAddress
increaseAddress(const isc::asiolink::IOAddress& addr);
/// @brief returns the next prefix
/// @brief Returns the next prefix
///
/// This method works for IPv6 addresses only. It increase
/// This method works for IPv6 addresses only. It increases
/// specified prefix by a given prefix_len. For example, 2001:db8::
/// increased by prefix length /32 will become 2001:db9::. This method
/// is used to iterate over IPv6 prefix pools
......@@ -145,9 +154,9 @@ protected:
/// @param prefix prefix to be increased
/// @param prefix_len length of the prefix to be increased
/// @return result prefix
isc::asiolink::IOAddress
static isc::asiolink::IOAddress
increasePrefix(const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len) const;
const uint8_t prefix_len);
};
/// @brief Address/prefix allocator that gets an address based on a hash
......@@ -332,11 +341,11 @@ protected:
///
/// @return Allocated IPv6 leases (may be empty if allocation failed)
Lease6Collection
allocateLease6(const Subnet6Ptr& subnet, const DuidPtr& duid, uint32_t iaid,
const isc::asiolink::IOAddress& hint, Lease::Type type,
const bool fwd_dns_update, const bool rev_dns_update,
const std::string& hostname, bool fake_allocation,
const isc::hooks::CalloutHandlePtr& callout_handle);
allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid, uint32_t iaid,
const isc::asiolink::IOAddress& hint, Lease::Type type,
const bool fwd_dns_update, const bool rev_dns_update,
const std::string& hostname, bool fake_allocation,
const isc::hooks::CalloutHandlePtr& callout_handle);
/// @brief returns allocator for a given pool type
/// @param type type of pool (V4, IA, TA or PD)
......@@ -391,6 +400,7 @@ private:
/// @param addr an address that was selected and is confirmed to be
/// available
/// @param prefix_len lenght of the prefix (for PD only)
/// should be 128 for other lease types
/// @param type lease type (IA, TA or PD)
/// @param fwd_dns_update A boolean value which indicates that server takes
/// responsibility for the forward DNS Update for this lease
......@@ -407,8 +417,8 @@ private:
/// @return allocated lease (or NULL in the unlikely case of the lease just
/// became unavailable)
Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
uint32_t iaid, const isc::asiolink::IOAddress& addr,
uint8_t prefix_len, Lease::Type type,
const uint32_t iaid, const isc::asiolink::IOAddress& addr,
const uint8_t prefix_len, const Lease::Type type,
const bool fwd_dns_update, const bool rev_dns_update,
const std::string& hostname,
const isc::hooks::CalloutHandlePtr& callout_handle,
......@@ -456,6 +466,7 @@ private:
/// @param duid client's DUID
/// @param iaid IAID from the IA_NA container the client sent to us
/// @param prefix_len prefix length (for PD leases)
/// Should be 128 for other lease types
/// @param fwd_dns_update A boolean value which indicates that server takes
/// responsibility for the forward DNS Update for this lease
/// (if true).
......@@ -470,8 +481,9 @@ private:
/// @return refreshed lease
/// @throw BadValue if trying to recycle lease that is still valid
Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
const DuidPtr& duid, uint32_t iaid,
uint8_t prefix_len, const bool fwd_dns_update,
const DuidPtr& duid, const uint32_t iaid,
uint8_t prefix_len,
const bool fwd_dns_update,
const bool rev_dns_update,
const std::string& hostname,
const isc::hooks::CalloutHandlePtr& callout_handle,
......
......@@ -61,10 +61,10 @@ separate module, called allocator. Its sole purpose is to pick an address from
a pool. Allocation engine will then check if the picked address is free and if
it is not, then will ask allocator to pick again.
At lease 3 allocators will be implemented:
At least 3 allocators will be implemented:
- Iterative - it iterates over all resources (addresses or prefixes) in
available pools, one by one. The advantages of this approach are speed
available pools, one by one. The advantages of this approach are: speed
(typically it only needs to increase address just one), the guarantee to cover
all addresses and predictability. This allocator behaves reasonably good in
case of nearing depletion. Even when pools are almost completely allocated, it
......@@ -108,8 +108,9 @@ TYPE_TA is partial. Some routines are able to handle it, while other are
not. The major missing piece is the RandomAllocator, so there is no way to randomly
generate an address. This defeats the purpose of using temporary addresses.
Prefixes are supported. For a prefix pool, the iterative allocator "walks over"
the every available pool. It is similar to how it iterates over address pool,
The Allocation Engine supports allocation of the IPv6 addresses and prefixes.
For a prefix pool, the iterative allocator "walks over"
every available pool. It is similar to how it iterates over address pool,
but instead of increasing address by just one, it walks over the whole delegated
prefix length in one step. This is implemented in
isc::dhcp::AllocEngine::IterativeAllocator::increasePrefix(). Functionally the
......
......@@ -164,7 +164,8 @@ const PoolCollection& Subnet::getPools(Lease::Type type) const {
case Lease::TYPE_PD:
return (pools_pd_);
default:
isc_throw(BadValue, "Unsupported pool type: " << type);
isc_throw(BadValue, "Unsupported pool type: "
<< static_cast<int>(type));
}
}
......@@ -186,8 +187,8 @@ PoolCollection& Subnet::getPools(Lease::Type type) {
}
}
PoolPtr Subnet::getPool(Lease::Type type, isc::asiolink::IOAddress hint,
bool anypool /* true */) {
const PoolPtr Subnet::getPool(Lease::Type type, const isc::asiolink::IOAddress& hint,
bool anypool /* true */) const {
// check if the type is valid (and throw if it isn't)
checkType(type);
......
......@@ -192,23 +192,23 @@ public:
/// is not always true. For the given example, 2001::1234:abcd would return
/// true for inSubnet(), but false for inPool() check.
///
/// @param type pool types to iterate over
/// @param type type of pools to iterate over
/// @param addr this address will be checked if it belongs to any pools in
/// that subnet
/// @return true if the address is in any of the pools
bool inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const;
/// @brief return valid-lifetime for addresses in that prefix
/// @brief Return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
return (valid_);
}
/// @brief returns T1 (renew timer), expressed in seconds
/// @brief Returns T1 (renew timer), expressed in seconds
Triplet<uint32_t> getT1() const {
return (t1_);
}
/// @brief returns T2 (rebind timer), expressed in seconds
/// @brief Returns T2 (rebind timer), expressed in seconds
Triplet<uint32_t> getT2() const {
return (t2_);
}
......@@ -258,11 +258,11 @@ public:
void setLastAllocated(Lease::Type type,
const isc::asiolink::IOAddress& addr);
/// @brief returns unique ID for that subnet
/// @brief Returns unique ID for that subnet
/// @return unique ID for that subnet
SubnetID getID() const { return (id_); }
/// @brief returns subnet parameters (prefix and prefix length)
/// @brief Returns subnet parameters (prefix and prefix length)
///
/// @return (prefix, prefix length) pair
std::pair<isc::asiolink::IOAddress, uint8_t> get() const {
......@@ -294,8 +294,8 @@ public:
/// @param anypool other pool may be returned as well, not only the one
/// that addr belongs to
/// @return found pool (or NULL)
PoolPtr getPool(Lease::Type type, isc::asiolink::IOAddress addr,
bool anypool = true);
const PoolPtr getPool(Lease::Type type, const isc::asiolink::IOAddress& addr,
bool anypool = true) const;
/// @brief Returns a pool without any address specified
///
......@@ -311,7 +311,7 @@ public:
/// and 0.0.0.0 for Subnet4)
virtual isc::asiolink::IOAddress default_pool() const = 0;
/// @brief returns all pools (const variant)
/// @brief Returns all pools (const variant)
///
/// The reference is only valid as long as the object that returned it.
///
......@@ -319,7 +319,7 @@ public:
/// @return a collection of all pools
const PoolCollection& getPools(Lease::Type type) const;
/// @brief returns all pools (variable variant)
/// @brief Returns all pools (variable variant)
///
/// The reference is only valid as long as the object that returned it.
///
......@@ -327,24 +327,24 @@ public:
/// @return a collection of all pools
PoolCollection& getPools(Lease::Type type);
/// @brief sets name of the network interface for directly attached networks
/// @brief Sets name of the network interface for directly attached networks
///
/// @param iface_name name of the interface
void setIface(const std::string& iface_name);
/// @brief network interface name used to reach subnet (or "" for remote
/// @brief Network interface name used to reach subnet (or "" for remote
/// subnets)
/// @return network interface name for directly attached subnets or ""
std::string getIface() const;
/// @brief returns textual representation of the subnet (e.g.
/// @brief Returns textual representation of the subnet (e.g.
/// "2001:db8::/64")
///
/// @return textual representation
virtual std::string toText() const;
protected:
/// @brief protected constructor
/// @brief Protected constructor
//
/// By making the constructor protected, we make sure that noone will
/// ever instantiate that class. Pool4 and Pool6 should be used instead.
......
......@@ -66,8 +66,12 @@ public:
using AllocEngine::IterativeAllocator;
using AllocEngine::getAllocator;
/// @brief IterativeAllocator with internal methods exposed
class NakedIterativeAllocator: public AllocEngine::IterativeAllocator {
public:
/// @brief constructor
/// @param type pool types that will be interated
NakedIterativeAllocator(Lease::Type type)
:IterativeAllocator(type) {
}
......@@ -146,14 +150,17 @@ public:
EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_);
EXPECT_EQ(subnet_->getT1(), lease->t1_);
EXPECT_EQ(subnet_->getT2(), lease->t2_);
EXPECT_EQ(exp_pd_len, lease->prefixlen_); // this is IA_NA, not IA_PD
EXPECT_EQ(exp_pd_len, lease->prefixlen_);
EXPECT_TRUE(false == lease->fqdn_fwd_);
EXPECT_TRUE(false == lease->fqdn_rev_);
EXPECT_TRUE(*lease->duid_ == *duid_);
// @todo: check cltt
}
/// @brief checks if specified address is increased properly
/// @brief Checks if specified address is increased properly
///
/// Method uses gtest macros to mark check failure.
///
/// @param alloc IterativeAllocator that is tested
/// @param input address to be increased
/// @param exp_output expected address after increase
......@@ -163,6 +170,14 @@ public:
EXPECT_EQ(exp_output, alloc.increaseAddress(IOAddress(input)).toText());
}
/// @brief Checks if increasePrefix() works as expected
///
/// Method uses gtest macros to mark check failure.
///
/// @param alloc allocator to be tested
/// @param input IPv6 prefix (as a string)
/// @param prefix_len prefix len
/// @param exp_output expected output (string)
void
checkPrefixIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
std::string input, uint8_t prefix_len,
......@@ -171,7 +186,7 @@ public:
.toText());
}
/// checks if the simple allocation can succeed
/// @brief Checks if the simple allocation can succeed
///
/// The type of lease is determined by pool type (pool->getType()
///
......@@ -194,7 +209,7 @@ public:
}
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, hint, type, false, false,
"", fake, CalloutHandlePtr())));
......@@ -230,6 +245,16 @@ public:
return (lease);
}
/// @brief Checks if the address allocation with a hint that is in range,
/// in pool, but is currently used, can succeed
///
/// Method uses gtest macros to mark check failure.
///
/// @param type lease type
/// @param used_addr address should be preallocated (simulates prior
/// allocation by some other user)
/// @param requested address requested by the client
/// @param expected_pd_len expected PD len (128 for addresses)
void allocWithUsedHintTest(Lease::Type type, IOAddress used_addr,
IOAddress requested, uint8_t expected_pd_len) {
boost::scoped_ptr<AllocEngine> engine;
......@@ -248,7 +273,7 @@ public:
// unfortunately it is used already. The same address must not be allocated
// twice.
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, requested, type, false, false, "", false,
CalloutHandlePtr())));
......@@ -292,7 +317,7 @@ public:
// supported lease. Allocation engine should ignore it and carry on
// with the normal allocation
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, hint, type, false,
false, "", false, CalloutHandlePtr())));
......@@ -494,13 +519,13 @@ TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
// Allocations without subnet are not allowed
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(
Subnet6Ptr(), duid_, iaid_, IOAddress("::"), Lease::TYPE_NA,
false, false, "", false, CalloutHandlePtr())));
ASSERT_FALSE(lease);
// Allocations without DUID are not allowed either
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
DuidPtr(), iaid_, IOAddress("::"), Lease::TYPE_NA, false,
false, "", false, CalloutHandlePtr())));
ASSERT_FALSE(lease);
......@@ -674,9 +699,10 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixIncrease) {
checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 60, "2001:db8:1:abdd::");
checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 56, "2001:db8:1:accd::");
checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 52, "2001:db8:1:bbcd::");
}
// And now let's try something over the top
checkPrefixIncrease(alloc, "::", 1, "8000::");
}
// This test verifies that the iterative allocator really walks over all addresses
// in all pools in specified subnet. It also must not pick the same address twice
......@@ -749,7 +775,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
cfg_mgr.addSubnet6(subnet_);
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
"", false, CalloutHandlePtr())));
......@@ -798,7 +824,7 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
// There is just a single address in the pool and allocated it to someone
// else, so the allocation should fail
Lease6Ptr lease2;
EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
"", false, CalloutHandlePtr())));
EXPECT_FALSE(lease2);
......@@ -833,7 +859,7 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
ASSERT_TRUE(lease->expired());
// CASE 1: Asking for any address
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false, "", true,
CalloutHandlePtr())));
// Check that we got that single lease
......@@ -844,7 +870,7 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
checkLease6(lease, Lease::TYPE_NA, 128);
// CASE 2: Asking specifically for this address
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, addr, Lease::TYPE_NA, false, false, "",
true, CalloutHandlePtr())));
......@@ -880,7 +906,7 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// A client comes along, asking specifically for this address
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, addr, Lease::TYPE_NA, false, false, "",
false, CalloutHandlePtr())));
......@@ -1595,7 +1621,7 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
"", false, callout_handle)));
// Check that we got a lease
......@@ -1660,13 +1686,13 @@ TEST_F(HookAllocEngine6Test, change_lease6_select) {
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"lease6_select", lease6_select_different_callout));
// Normally, dhcpv6_srv would passed the handle when calling allocateLease6,
// Normally, dhcpv6_srv would passed the handle when calling allocateLeases6,
// but in tests we need to create it on our own.
CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
// Call allocateLease6. Callouts should be triggered here.
// Call allocateLeases6. Callouts should be triggered here.
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLease6(subnet_,
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
"", false, callout_handle)));
// Check that we got a lease
......
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