Skip to content

Commit

Permalink
Merge pull request #202 from Azure/resv-specific-cidr
Browse files Browse the repository at this point in the history
Reserve Specific CIDR + Fixes
  • Loading branch information
DCMattyG authored Oct 30, 2023
2 parents d9c5f4e + 8a407f6 commit 895240f
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 12 deletions.
20 changes: 18 additions & 2 deletions engine/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,27 @@ class SpaceCIDRReq(BaseModel):
class BlockCIDRReq(BaseModel):
"""DOCSTRING"""

size: int
size: Optional[int] = None
cidr: Optional[IPv4Network] = None
desc: Optional[str] = None
reverse_search: Optional[bool] = False
smallest_cidr: Optional[bool] = False

@model_validator(mode='before')
@classmethod
def validate_request(cls, data: Any) -> Any:
if isinstance(data, dict):
if 'cidr' in data and any(x in data for x in ['reverse_search', 'smallest_cidr']):
if data['cidr'] is not None:
raise AssertionError("the 'cidr' parameter can only be used in conjuction with 'desc'")
if 'cidr' in data and 'size' in data:
if data['cidr'] is not None:
raise AssertionError("the 'cidr' and 'size' parameters can only be used alternatively")
if 'cidr' not in data and 'size' not in data:
raise AssertionError("it is required to provide either the 'cidr' or 'size' parameter")

return data

class JSONPatch(BaseModel):
"""DOCSTRING"""

Expand Down Expand Up @@ -438,7 +454,7 @@ class Admin(BaseModel):

@model_validator(mode='before')
@classmethod
def check_email(cls, data: Any) -> Any:
def validate_request(cls, data: Any) -> Any:
if isinstance(data, dict):
if 'type' in data and 'email' in data:
if data['type'] == "Principal" and data['email'] is not None:
Expand Down
70 changes: 60 additions & 10 deletions engine/app/routers/space.py
Original file line number Diff line number Diff line change
Expand Up @@ -1796,13 +1796,55 @@ async def create_block_reservation(
Create a CIDR Reservation for the target Block with the following information:
- **size**: Network mask bits
- **cidr**: Specific CIDR to reserve (alternative to 'size')
- **desc**: Description (optional)
- **reverse_search**:
- **true**: New networks will be created as close to the <u>end</u> of the block as possible
- **false (default)**: New networks will be created as close to the <u>beginning</u> of the block as possible
- **smallest_cidr**:
- **true**: New networks will be created using the smallest possible available block (e.g. it will not break up large CIDR blocks when possible)
- **false (default)**: New networks will be created using the first available block, regardless of size
### <u>Usage Examples</u>
#### *Request a new /24:*
```json
{
"size": 24
"desc": "New CIDR for Business Unit 1"
}
```
#### *Request a new /24, searching from the end of the CIDR range:*
```json
{
"size": 24,
"desc": "New CIDR for Business Unit 1",
"reverse_search": true
}
```
#### *Request a new /24, searching from the end of the CIDR range, using the smallest available CIDR block from the available address space:*
```json
{
"size": 24,
"desc": "New CIDR for Business Unit 1",
"reverse_search": true,
"smallest_cidr": true
}
```
#### *Request a specific /24:*
```json
{
"cidr": "10.0.100.0/24",
"desc" "New CIDR for Business Unit 1"
}
```
"""

user_assertion = authorization.split(' ')[1]
Expand Down Expand Up @@ -1839,20 +1881,28 @@ async def create_block_reservation(
reserved_set = IPSet(block_all_cidrs)
available_set = block_set ^ reserved_set

available_slicer = slice(None, None, -1) if req.reverse_search else slice(None)
next_selector = -1 if req.reverse_search else 0
next_cidr = None

if req.cidr is not None:
if IPNetwork(req.cidr) not in available_set:
raise HTTPException(status_code=409, detail="Requested CIDR overlaps existing network(s).")

if req.smallest_cidr:
cidr_list = list(filter(lambda x: x.prefixlen <= req.size, available_set.iter_cidrs()[available_slicer]))
min_mask = max(map(lambda x: x.prefixlen, cidr_list))
available_block = next((net for net in list(filter(lambda network: network.prefixlen == min_mask, cidr_list))), None)
next_cidr = IPNetwork(req.cidr)
else:
available_block = next((net for net in list(available_set.iter_cidrs())[available_slicer] if net.prefixlen <= req.size), None)
available_slicer = slice(None, None, -1) if req.reverse_search else slice(None)
next_selector = -1 if req.reverse_search else 0

if not available_block:
raise HTTPException(status_code=500, detail="Network of requested size unavailable in target block.")
if req.smallest_cidr:
cidr_list = list(filter(lambda x: x.prefixlen <= req.size, available_set.iter_cidrs()[available_slicer]))
min_mask = max(map(lambda x: x.prefixlen, cidr_list))
available_block = next((net for net in list(filter(lambda network: network.prefixlen == min_mask, cidr_list))), None)
else:
available_block = next((net for net in list(available_set.iter_cidrs())[available_slicer] if net.prefixlen <= req.size), None)

next_cidr = list(available_block.subnet(req.size))[next_selector]
if not available_block:
raise HTTPException(status_code=500, detail="Network of requested size unavailable in target block.")

next_cidr = list(available_block.subnet(req.size))[next_selector]

if "preferred_username" in decoded:
creator_id = decoded["preferred_username"]
Expand Down
1 change: 1 addition & 0 deletions lb/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ http {

# Frontend
location / {
add_header X-Frame-Options "DENY";
proxy_pass http://ui;
proxy_intercept_errors on;
proxy_set_header Host $http_host;
Expand Down
1 change: 1 addition & 0 deletions ui/src/features/configure/block/Utils/editReservations.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ export default function EditReservations(props) {
function onClose() {
handleClose();
setFilterActive(true);
setSelectionModel([]);
}

function onSubmit() {
Expand Down

0 comments on commit 895240f

Please sign in to comment.