diff --git a/CHANGELOG.md b/CHANGELOG.md index 844a7401..209ea9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed +- Reduce the time taken for the call node.get_ssh_command() (Issue [#280](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/280)) +- Allow access to other user's slices in a project (Issue [#279](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/279)) + ## [1.6.3] - 2024-01-26 ### Fixed diff --git a/fabrictestbed_extensions/editors/abc_topology_editor.py b/fabrictestbed_extensions/editors/abc_topology_editor.py index 5ca35562..bd15bcb0 100644 --- a/fabrictestbed_extensions/editors/abc_topology_editor.py +++ b/fabrictestbed_extensions/editors/abc_topology_editor.py @@ -121,7 +121,7 @@ def create_experiment(self, slice_name): { "slice_name": slice_name, "editor_slice_state": self.EXPERIMENT_STATE_UNSUBMITTED, - "topology": ExperimentTopology() + "topology": ExperimentTopology(), # ssh_keys # detail_levels {'detail': HIGH, {'node1': LOW, 'node2': MED}} } diff --git a/fabrictestbed_extensions/fablib/fablib.py b/fabrictestbed_extensions/fablib/fablib.py index 08325342..1216e24b 100644 --- a/fabrictestbed_extensions/fablib/fablib.py +++ b/fabrictestbed_extensions/fablib/fablib.py @@ -438,7 +438,8 @@ def get_available_resources(update: bool = False) -> Resources: @staticmethod def get_fim_slice( - excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing] + excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing], + user_only: bool = True, ) -> List[OrchestratorSlice]: """ Not intended for API use. @@ -451,14 +452,20 @@ def get_fim_slice( :param excludes: A list of slice states to exclude from the output list :type excludes: List[SliceState] + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool + :return: a list of slices :rtype: List[Slice] """ - return fablib.get_default_fablib_manager().get_fim_slice(excludes=excludes) + return fablib.get_default_fablib_manager().get_fim_slice( + excludes=excludes, user_only=user_only + ) @staticmethod def get_slices( - excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing] + excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing], + user_only: bool = True, ) -> List[Slice]: """ Gets a list of slices from the slice manager. @@ -469,13 +476,20 @@ def get_slices( :param excludes: A list of slice states to exclude from the output list :type excludes: List[SliceState] + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool + :return: a list of slices :rtype: List[Slice] """ - return fablib.get_default_fablib_manager().get_slices(excludes=excludes) + return fablib.get_default_fablib_manager().get_slices( + excludes=excludes, user_only=user_only + ) @staticmethod - def get_slice(name: str = None, slice_id: str = None) -> Slice: + def get_slice( + name: str = None, slice_id: str = None, user_only: bool = True + ) -> Slice: """ Gets a slice by name or slice_id. Dead and Closing slices may have non-unique names and must be queried by slice_id. Slices in all other @@ -488,12 +502,14 @@ def get_slice(name: str = None, slice_id: str = None) -> Slice: :type name: String :param slice_id: The ID of the desired slice :type slice_id: String + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :raises: Exception: if slice name or slice id are not inputted :return: the slice, if found :rtype: Slice """ return fablib.get_default_fablib_manager().get_slice( - name=name, slice_id=slice_id + name=name, slice_id=slice_id, user_only=user_only ) @staticmethod @@ -1512,7 +1528,9 @@ def get_available_resources( return self.resources def get_fim_slices( - self, excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing] + self, + excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing], + user_only: bool = True, ) -> List[OrchestratorSlice]: """ Gets a list of fim slices from the slice manager. @@ -1526,11 +1544,13 @@ def get_fim_slices( :param excludes: A list of slice states to exclude from the output list :type excludes: List[SliceState] + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :return: a list of fim models of slices :rtype: List[Slice] """ return_status, slices = self.get_slice_manager().slices( - excludes=excludes, limit=200 + excludes=excludes, limit=200, user_only=user_only ) return_slices = [] @@ -1549,6 +1569,7 @@ def list_slices( quiet=False, filter_function=None, pretty_names=True, + user_only: bool = True, ): """ Lists all the slices created by a user. @@ -1581,10 +1602,12 @@ def list_slices( :return: table in format specified by output parameter :param pretty_names: pretty_names :type pretty_names: bool + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :rtype: Object """ table = [] - for slice in self.get_slices(excludes=excludes): + for slice in self.get_slices(excludes=excludes, user_only=user_only): table.append(slice.toDict()) if pretty_names: @@ -1610,6 +1633,7 @@ def show_slice( fields=None, quiet=False, pretty_names=True, + user_only: bool = True, ): """ Show a table with all the properties of a specific site @@ -1637,11 +1661,13 @@ def show_slice( :type quiet: bool :param pretty_names: pretty_names :type pretty_names: bool + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :return: table in format specified by output parameter :rtype: Object """ - slice = self.get_slice(name=name, slice_id=id) + slice = self.get_slice(name=name, slice_id=id, user_only=user_only) return slice.show( output=output, fields=fields, quiet=quiet, pretty_names=pretty_names @@ -1652,6 +1678,7 @@ def get_slices( excludes: List[SliceState] = [SliceState.Dead, SliceState.Closing], slice_name: str = None, slice_id: str = None, + user_only: bool = True, ) -> List[Slice]: """ Gets a list of slices from the slice manager. @@ -1664,6 +1691,8 @@ def get_slices( :type excludes: List[SliceState] :param slice_name: :param slice_id: + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :return: a list of slices :rtype: List[Slice] @@ -1674,7 +1703,11 @@ def get_slices( start = time.time() return_status, slices = self.get_slice_manager().slices( - excludes=excludes, name=slice_name, slice_id=slice_id, limit=200 + excludes=excludes, + name=slice_name, + slice_id=slice_id, + limit=200, + user_only=user_only, ) if self.get_log_level() == logging.DEBUG: @@ -1686,12 +1719,16 @@ def get_slices( return_slices = [] if return_status == Status.OK: for slice in slices: - return_slices.append(Slice.get_slice(self, sm_slice=slice)) + return_slices.append( + Slice.get_slice(self, sm_slice=slice, user_only=user_only) + ) else: raise Exception(f"Failed to get slices: {slices}") return return_slices - def get_slice(self, name: str = None, slice_id: str = None) -> Slice: + def get_slice( + self, name: str = None, slice_id: str = None, user_only: bool = True + ) -> Slice: """ Gets a slice by name or slice_id. Dead and Closing slices may have non-unique names and must be queried by slice_id. Slices in all other @@ -1704,6 +1741,8 @@ def get_slice(self, name: str = None, slice_id: str = None) -> Slice: :type name: String :param slice_id: The ID of the desired slice :type slice_id: String + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :raises: Exception: if slice name or slice id are not inputted :return: the slice, if found :rtype: Slice @@ -1711,7 +1750,9 @@ def get_slice(self, name: str = None, slice_id: str = None) -> Slice: # Get the appropriate slices list if slice_id: # if getting by slice_id consider all slices - slices = self.get_slices(excludes=[], slice_id=slice_id) + slices = self.get_slices( + excludes=[], slice_id=slice_id, user_only=user_only + ) if len(slices) == 1: return slices[0] @@ -1720,7 +1761,9 @@ def get_slice(self, name: str = None, slice_id: str = None) -> Slice: elif name: # if getting by name then only consider active slices slices = self.get_slices( - excludes=[SliceState.Dead, SliceState.Closing], slice_name=name + excludes=[SliceState.Dead, SliceState.Closing], + slice_name=name, + user_only=user_only, ) if len(slices) > 0: diff --git a/fabrictestbed_extensions/fablib/interface.py b/fabrictestbed_extensions/fablib/interface.py index 22237514..4a5750bd 100644 --- a/fabrictestbed_extensions/fablib/interface.py +++ b/fabrictestbed_extensions/fablib/interface.py @@ -182,9 +182,9 @@ def toDict(self, skip=[]): "network": str(network_name), "bandwidth": str(self.get_bandwidth()), "mode": str(self.get_mode()), - "vlan": str(self.get_vlan()) - if self.get_vlan() - else "", # str(self.get_vlan()), + "vlan": ( + str(self.get_vlan()) if self.get_vlan() else "" + ), # str(self.get_vlan()), "mac": mac, "physical_dev": physical_dev, "dev": dev, diff --git a/fabrictestbed_extensions/fablib/node.py b/fabrictestbed_extensions/fablib/node.py index 1e38c853..92211520 100644 --- a/fabrictestbed_extensions/fablib/node.py +++ b/fabrictestbed_extensions/fablib/node.py @@ -311,14 +311,14 @@ def generate_template_context(self): return context - def get_template_context(self): - return self.get_slice().get_template_context(self, skip=["ssh_command"]) + def get_template_context(self, skip: List[str] = ["ssh_command"]): + return self.get_slice().get_template_context(self, skip=skip) - def render_template(self, input_string): + def render_template(self, input_string, skip: List[str] = ["ssh_command"]): environment = jinja2.Environment() # environment.json_encoder = json.JSONEncoder(ensure_ascii=False) template = environment.from_string(input_string) - output_string = template.render(self.get_template_context()) + output_string = template.render(self.get_template_context(skip=skip)) return output_string @@ -1169,7 +1169,8 @@ def get_ssh_command(self) -> str: try: return self.render_template( - self.get_fablib_manager().get_ssh_command_line() + self.get_fablib_manager().get_ssh_command_line(), + skip=["ssh_command", "interfaces"], ) except: return self.get_fablib_manager().get_ssh_command_line() @@ -1557,7 +1558,7 @@ def execute( except Exception as e: logging.warning( - f"Exception in node.execute() (attempt #{attempt} of {retry}): {e}" + f"Exception in node.execute() command: {command} (attempt #{attempt} of {retry}): {e}" ) if attempt + 1 == retry: diff --git a/fabrictestbed_extensions/fablib/resources.py b/fabrictestbed_extensions/fablib/resources.py index 87da2baf..4b0605da 100644 --- a/fabrictestbed_extensions/fablib/resources.py +++ b/fabrictestbed_extensions/fablib/resources.py @@ -1117,15 +1117,21 @@ def __str__(self) -> str: fp.site, iface.node_id, iface.labels.vlan_range if iface.labels else "N/A", - iface.labels.local_name - if iface.labels and iface.labels.local_name - else "N/A", - iface.labels.device_name - if iface.labels and iface.labels.device_name - else "N/A", - iface.labels.region - if iface.labels and iface.labels.region - else "N/A", + ( + iface.labels.local_name + if iface.labels and iface.labels.local_name + else "N/A" + ), + ( + iface.labels.device_name + if iface.labels and iface.labels.device_name + else "N/A" + ), + ( + iface.labels.region + if iface.labels and iface.labels.region + else "N/A" + ), ] ) @@ -1156,15 +1162,19 @@ def fp_to_dict(self, iface: interface.Interface, name: str, site: str) -> dict: "site_name": site, "node_id": iface.node_id, "vlan_range": iface.labels.vlan_range if iface.labels else "N/A", - "local_name": iface.labels.local_name - if iface.labels and iface.labels.local_name - else "N/A", - "device_name": iface.labels.device_name - if iface.labels and iface.labels.device_name - else "N/A", - "region": iface.labels.region - if iface.labels and iface.labels.region - else "N/A", + "local_name": ( + iface.labels.local_name + if iface.labels and iface.labels.local_name + else "N/A" + ), + "device_name": ( + iface.labels.device_name + if iface.labels and iface.labels.device_name + else "N/A" + ), + "region": ( + iface.labels.region if iface.labels and iface.labels.region else "N/A" + ), } def list_facility_ports( diff --git a/fabrictestbed_extensions/fablib/slice.py b/fabrictestbed_extensions/fablib/slice.py index c4194bed..cfceea1f 100644 --- a/fabrictestbed_extensions/fablib/slice.py +++ b/fabrictestbed_extensions/fablib/slice.py @@ -87,7 +87,9 @@ class Slice: - def __init__(self, fablib_manager: FablibManager, name: str = None): + def __init__( + self, fablib_manager: FablibManager, name: str = None, user_only: bool = True + ): """ Create a FABRIC slice, and set its state to be callable. @@ -110,6 +112,7 @@ def __init__(self, fablib_manager: FablibManager, name: str = None): self.update_slivers_count = 0 self.update_slice_count = 0 self.update_count = 0 + self.user_only = user_only def get_fablib_manager(self): return self.fablib_manager @@ -437,13 +440,19 @@ def new_slice(fablib_manager: FablibManager, name: str = None): return slice @staticmethod - def get_slice(fablib_manager: FablibManager, sm_slice: OrchestratorSlice = None): + def get_slice( + fablib_manager: FablibManager, + sm_slice: OrchestratorSlice = None, + user_only: bool = True, + ): """ Not intended for API use. Gets an existing fablib slice using a slice manager slice :param fablib_manager: :param sm_slice: + :param user_only: True indicates return own slices; False indicates return project slices + :type user_only: bool :return: Slice """ logging.info("slice.get_slice()") @@ -452,6 +461,7 @@ def get_slice(fablib_manager: FablibManager, sm_slice: OrchestratorSlice = None) slice.sm_slice = sm_slice slice.slice_id = sm_slice.slice_id slice.slice_name = sm_slice.name + slice.user_only = user_only try: slice.update_topology() @@ -519,25 +529,29 @@ def get_template_context(self, base_object=None, skip=[]): context["slice"] = self.toDict() context["nodes"] = {} - for node in self.get_nodes(): - node_context = node.generate_template_context() - context["nodes"][node.get_name()] = node_context + if "nodes" not in skip: + for node in self.get_nodes(): + node_context = node.generate_template_context() + context["nodes"][node.get_name()] = node_context context["components"] = {} - for component in self.get_components(): - context["components"][ - component.get_name() - ] = component.generate_template_context() + if "components" not in skip: + for component in self.get_components(): + context["components"][ + component.get_name() + ] = component.generate_template_context() context["interfaces"] = {} - for interface in self.get_interfaces(): - context["interfaces"][interface.get_name()] = interface.toDict() + if "interfaces" not in skip: + for interface in self.get_interfaces(): + context["interfaces"][interface.get_name()] = interface.toDict() context["networks"] = {} - for network in self.get_networks(): - context["networks"][ - network.get_name() - ] = network.generate_template_context() + if "networks" not in skip: + for network in self.get_networks(): + context["networks"][ + network.get_name() + ] = network.generate_template_context() return context @@ -571,7 +585,10 @@ def update_slice(self): start = time.time() return_status, slices = self.fablib_manager.get_slice_manager().slices( - excludes=[], slice_id=self.slice_id, name=self.slice_name + excludes=[], + slice_id=self.slice_id, + name=self.slice_name, + user_only=self.user_only, ) if self.fablib_manager.get_log_level() == logging.DEBUG: end = time.time() @@ -612,7 +629,7 @@ def update_topology(self): return_status, new_topo, ) = self.fablib_manager.get_slice_manager().get_slice_topology( - slice_object=self.sm_slice + slice_object=self.sm_slice, user_only=self.user_only ) if return_status != Status.OK: raise Exception( @@ -638,7 +655,7 @@ def update_slivers(self): if self.sm_slice is None: return status, slivers = self.fablib_manager.get_slice_manager().slivers( - slice_object=self.sm_slice + slice_object=self.sm_slice, user_only=self.user_only ) if status == Status.OK: self.slivers = slivers diff --git a/pyproject.toml b/pyproject.toml index f87a89e0..caea56a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "fabrictestbed-extensions" -version = "1.6.3" +version = "1.6.4" description = "FABRIC Python Client Library and CLI Extensions" authors = [ { name = "Paul Ruth", email = "pruth@renci.org" },