diff --git a/src/api-service/__app__/onefuzzlib/azure/nsg.py b/src/api-service/__app__/onefuzzlib/azure/nsg.py index 87aa6ce6c..9d4e0bbc8 100644 --- a/src/api-service/__app__/onefuzzlib/azure/nsg.py +++ b/src/api-service/__app__/onefuzzlib/azure/nsg.py @@ -5,7 +5,7 @@ import logging import os -from typing import Dict, List, Optional, Union, cast +from typing import Dict, List, Optional, Set, Union, cast from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from azure.mgmt.network.models import ( @@ -72,6 +72,12 @@ def create_nsg(name: str, location: Region) -> Union[None, Error]: return None +def list_nsgs() -> List[NetworkSecurityGroup]: + resource_group = get_base_resource_group() + network_client = get_network_client() + return list(network_client.network_security_groups.list(resource_group)) + + def update_nsg(nsg: NetworkSecurityGroup) -> Union[None, Error]: resource_group = get_base_resource_group() @@ -95,6 +101,10 @@ def update_nsg(nsg: NetworkSecurityGroup) -> Union[None, Error]: return None +def ok_to_delete(active_regions: Set[Region], nsg_region: str, nsg_name: str) -> bool: + return nsg_region not in active_regions and nsg_region == nsg_name + + def delete_nsg(name: str) -> bool: # NSG can be only deleted if no other resource is associated with it resource_group = get_base_resource_group() diff --git a/src/api-service/__app__/timer_proxy/__init__.py b/src/api-service/__app__/timer_proxy/__init__.py index 12a7d9e9c..56bab941a 100644 --- a/src/api-service/__app__/timer_proxy/__init__.py +++ b/src/api-service/__app__/timer_proxy/__init__.py @@ -8,6 +8,7 @@ import logging import azure.functions as func from onefuzztypes.enums import VmState +from ..onefuzzlib.azure.nsg import delete_nsg, list_nsgs, ok_to_delete from ..onefuzzlib.orm import process_state_updates from ..onefuzzlib.proxy import PROXY_LOG_PREFIX, Proxy from ..onefuzzlib.workers.scalesets import Scaleset @@ -50,3 +51,10 @@ def main(mytimer: func.TimerRequest) -> None: # noqa: F841 for region in regions: if all(x.outdated for x in proxies if x.region == region): Proxy.get_or_create(region) + + # if there are NSGs with name same as the region that they are allocated + # and have no NIC associated with it then delete the NSG + for nsg in list_nsgs(): + if ok_to_delete(regions, nsg.location, nsg.name): + if nsg.network_interfaces is None: + delete_nsg(nsg.name) diff --git a/src/api-service/tests/test_nsg.py b/src/api-service/tests/test_nsg.py new file mode 100644 index 000000000..d0037f346 --- /dev/null +++ b/src/api-service/tests/test_nsg.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from typing import Set, Tuple + +import pytest +from onefuzztypes.primitives import Region + +from __app__.onefuzzlib.azure.nsg import ok_to_delete + +# Active regions, NSG region, NSG name, expected result +NsgOkToDeleteTestCase = Tuple[Set[Region], str, str, bool] + +NSG_OK_TO_DELETE_TEST_CASES = [ + # OK to delete + (set([Region("def"), Region("ghk")]), "abc", "abc", True), + # Not OK to delete + # region set has same region as NSG + (set([Region("abc"), Region("def"), Region("ghk")]), "abc", "abc", False), + # NSG region does not match it's name + (set([Region("abc"), Region("def"), Region("ghk")]), "abc", "cba", False), + (set([Region("def"), Region("ghk")]), "abc", "cba", False), +] + + +@pytest.mark.parametrize("nsg_ok_to_delete_test_case", NSG_OK_TO_DELETE_TEST_CASES) +def test_is_ok_to_delete_nsg(nsg_ok_to_delete_test_case: NsgOkToDeleteTestCase) -> None: + regions, nsg_location, nsg_name, expected = nsg_ok_to_delete_test_case + result = ok_to_delete(regions, nsg_location, nsg_name) + assert result == expected