From bfcf639c603c9ff07ad67b8f9485fecc2f5b34d4 Mon Sep 17 00:00:00 2001 From: Tomas Mikuska Date: Thu, 26 Sep 2024 21:13:44 +0200 Subject: [PATCH] Dropped PCL support for CML versions below 2.6.0 Dropped topology support for versions below 0.1.0 --- .github/workflows/github-actions.yml | 1 + Makefile | 8 +- pytest.ini | 2 + test-requirements.txt | 5 +- tests/v2/__init__.py | 12 +- tests/v2/cluster.py | 11 +- tests/v2/console.py | 6 +- tests/v2/mocks/api.py | 299 ++++++++++++++---------- tests/v2/static/fake_repo_topology.yaml | 61 ++--- tests/v2/tmux.py | 14 +- tests/v2/up.py | 14 +- 11 files changed, 241 insertions(+), 192 deletions(-) create mode 100644 pytest.ini diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 482891c..05fb016 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -25,6 +25,7 @@ jobs: pip install -U pip - name: Install dependencies run: | + pip install -r requirements.txt pip install -r test-requirements.txt - name: Execute tests run: | diff --git a/Makefile b/Makefile index 62c7464..da25c52 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,13 @@ clean-test: ## remove test and coverage artifacts rm -f .coverage rm -fr htmlcov/ rm -f 5f0d96.yaml + rm -f 5f0d96_inventory.ini + rm -f 5f0d96_inventory.yaml + rm -f 5f0d96_testbed.yaml rm -f default_inventory.ini rm -f default_inventory.yaml rm -f default_testbed.yaml + rm -f topology.yaml rm -f .virl/cached_cml_labs/* rm -f .virl/current_cml_lab @@ -32,7 +36,7 @@ lint: ## check style with flake8 flake8 coverage: - PYTHONWARNINGS="ignore::DeprecationWarning" coverage run --source=virl setup.py test + coverage run --source=virl -m pytest tests/v2/* report: coverage coverage html @@ -40,7 +44,7 @@ report: coverage open htmlcov/index.html test: ## run tests quickly with the default Python - python -W ignore::DeprecationWarning setup.py test + pytest tests/v2/* release: dist ## package and upload a release @echo "*** Uploading virlutils... ***" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..0102b0a --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_default_fixture_loop_scope = function diff --git a/test-requirements.txt b/test-requirements.txt index ba89649..3e633b7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,7 @@ coverage coveralls +flake8 +pytest requests_mock respx -flake8 -setuptools \ No newline at end of file +setuptools diff --git a/tests/v2/__init__.py b/tests/v2/__init__.py index 079496c..e881af5 100644 --- a/tests/v2/__init__.py +++ b/tests/v2/__init__.py @@ -103,7 +103,7 @@ def get_alt_id(self): def get_alt_title(self): return "Other Lab" - def get_cml23_id(self): + def get_cml24_id(self): return "88119b68-9d08-40c4-90f5-6dc533fd0254" def prep_respx(self, **kwargs): @@ -138,22 +138,22 @@ def setup_mocks(self, m): "populate_lab_tiles": MockCMLServer.get_lab_tiles, "labs/{}/topology".format(self.get_test_id()): MockCMLServer.get_topology, "labs/{}/topology".format(self.get_alt_id()): MockCMLServer.get_alt_topology, - "labs/{}/topology".format(self.get_cml23_id()): MockCMLServer.get_topology_23, + "labs/{}/topology".format(self.get_cml24_id()): MockCMLServer.get_topology_24, "labs/{}/lab_element_state".format(self.get_test_id()): MockCMLServer.get_lab_element_state, - "labs/{}/lab_element_state".format(self.get_cml23_id()): MockCMLServer.get_lab_element_state_23, + "labs/{}/lab_element_state".format(self.get_cml24_id()): MockCMLServer.get_lab_element_state_24, "system_information": MockCMLServer.get_sys_info, "labs/{}/state".format(self.get_test_id()): "STARTED", - "labs/{}/state".format(self.get_cml23_id()): "STARTED", + "labs/{}/state".format(self.get_cml24_id()): "STARTED", "labs/{}/state".format(self.get_alt_id()): "STOPPED", "labs/{}/check_if_converged".format(self.get_test_id()): True, - "labs/{}/check_if_converged".format(self.get_cml23_id()): True, + "labs/{}/check_if_converged".format(self.get_cml24_id()): True, "labs/{}/nodes/n1/check_if_converged".format(self.get_test_id()): True, "users": MockCMLServer.get_users, } text_dict = { "labs/{}/download".format(self.get_test_id()): MockCMLServer.download_lab, - "labs/{}/download".format(self.get_cml23_id()): MockCMLServer.download_lab_23, + "labs/{}/download".format(self.get_cml24_id()): MockCMLServer.download_lab_24, "labs/{}/download".format(self.get_alt_id()): MockCMLServer.download_alt_lab, "labs/{}/pyats_testbed".format(self.get_test_id()): MockCMLServer.get_pyats_testbed, "authok": MockCMLServer.auth_ok, diff --git a/tests/v2/cluster.py b/tests/v2/cluster.py index 2c40caf..7aee1b1 100644 --- a/tests/v2/cluster.py +++ b/tests/v2/cluster.py @@ -1,17 +1,8 @@ -import unittest - from click.testing import CliRunner -from . import CLIENT_VERSION, BaseCMLTest -from .mocks.github import MockGitHub # noqa - -try: - from unittest.mock import patch -except ImportError: - from mock import patch # noqa +from . import BaseCMLTest -@unittest.skipIf(CLIENT_VERSION < CLIENT_VERSION.__class__("2.4.0"), "supported since 2.4.0") class TestCMLCluster(BaseCMLTest): def setup_mocks(self, m): super().setup_mocks(m) diff --git a/tests/v2/console.py b/tests/v2/console.py index e614954..3551667 100644 --- a/tests/v2/console.py +++ b/tests/v2/console.py @@ -29,12 +29,12 @@ def test_cml_console_connect(self, call_mock): call_mock.assert_called_once_with(["ssh", "-t", "admin@localhost", "open", "/5f0d96/n1/0"]) @patch("virl.cli.console.commands.call", autospec=False) - def test_cml_console_connect_23(self, call_mock): + def test_cml_console_connect_24(self, call_mock): with self.get_context() as m: # Mock the request to return what we expect from the API. self.setup_mocks(m) virl = self.get_virl() runner = CliRunner() - runner.invoke(virl, ["use", "--id", self.get_cml23_id()]) + runner.invoke(virl, ["use", "--id", self.get_cml24_id()]) runner.invoke(virl, ["console", "rtr-1"]) - call_mock.assert_called_once_with(["ssh", "-t", "admin@localhost", "open", "/Mock", "Test", "2.3/rtr-1/0"]) + call_mock.assert_called_once_with(["ssh", "-t", "admin@localhost", "open", "/Mock", "Test", "2.4/rtr-1/0"]) diff --git a/tests/v2/mocks/api.py b/tests/v2/mocks/api.py index 298303b..8e936a1 100644 --- a/tests/v2/mocks/api.py +++ b/tests/v2/mocks/api.py @@ -235,27 +235,35 @@ def download_lab(req, ctx=None): lab: description: '' notes: '' - timestamp: 1597805276.8213837 title: Mock Test - version: 0.0.3 + version: 0.1.0 + links: + - id: l0 + n1: n1 + n2: n0 + i1: i1 + i2: i0 + label: rtr-1-MgmtEth0/RP0/CPU0/0<->Lab Net-port nodes: - - id: n0 + - boot_disk_size: 0 + configuration: bridge0 + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n0 label: Lab Net node_definition: external_connector + ram: 0 + tags: [] x: -400 y: 0 - configuration: bridge0 - tags: [] interfaces: - id: i0 - slot: 0 label: port + slot: 0 type: physical - - id: n1 - label: rtr-1 - node_definition: iosxrv9000 - x: -200 - y: -50 + - boot_disk_size: 0 configuration: |- hostname changeme username cisco @@ -274,33 +282,39 @@ def download_lab(req, ctx=None): password lab ! end + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n1 image_definition: iosxrv9000-6-6-2 + label: rtr-1 + node_definition: iosxrv9000 + ram: 0 tags: [] + x: -200 + y: -50 interfaces: - id: i0 label: Loopback0 type: loopback - id: i1 - slot: 0 label: MgmtEth0/RP0/CPU0/0 + slot: 0 type: physical - id: i2 - slot: 1 label: donotuse1 + slot: 1 type: physical - id: i3 - slot: 2 label: donotuse2 + slot: 2 type: physical - id: i4 - slot: 3 label: GigabitEthernet0/0/0/0 + slot: 3 type: physical - - id: n2 - label: rtr-2 - node_definition: iosxrv9000 - x: -200 - y: -50 + - boot_disk_size: 0 configuration: |- hostname changeme username cisco @@ -319,64 +333,76 @@ def download_lab(req, ctx=None): password lab ! end + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n2 image_definition: iosxrv9000-6-6-2 + label: rtr-2 + node_definition: iosxrv9000 + ram: 0 tags: [] + x: -300 + y: 50 interfaces: - id: i0 label: Loopback0 type: loopback - id: i1 - slot: 0 label: MgmtEth0/RP0/CPU0/0 + slot: 0 type: physical - id: i2 - slot: 1 label: donotuse1 + slot: 1 type: physical - id: i3 - slot: 2 label: donotuse2 + slot: 2 type: physical - id: i4 - slot: 3 label: GigabitEthernet0/0/0/0 + slot: 3 type: physical - links: - - id: l0 - i1: i1 - n1: n1 - i2: i0 - n2: n0 """ return response @staticmethod - def download_lab_23(req, ctx=None): + def download_lab_24(req, ctx=None): response = """ lab: description: '' notes: '' - timestamp: 1597805276.8213837 - title: Mock Test 2.3 - version: 0.0.3 + title: Mock Test 2.4 + version: 0.1.0 + links: + - id: l0 + n1: n1 + n2: n0 + i1: i1 + i2: i0 + label: rtr-1-MgmtEth0/RP0/CPU0/0<->Lab Net-port nodes: - - id: 88119b68-9d08-40c4-90f5-6dc533fd0255 + - boot_disk_size: 0 + configuration: bridge0 + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n0 label: Lab Net node_definition: external_connector + ram: 0 + tags: [] x: -400 y: 0 - configuration: bridge0 - tags: [] interfaces: - id: i0 - slot: 0 label: port + slot: 0 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd0256 - label: rtr-1 - node_definition: iosxrv9000 - x: -200 - y: -50 + - boot_disk_size: 0 configuration: |- hostname changeme username cisco @@ -395,33 +421,38 @@ def download_lab_23(req, ctx=None): password lab ! end - image_definition: iosxrv9000-6-6-2 + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n1 + label: rtr-1 + node_definition: iosxrv9000 + ram: 0 tags: [] + x: -200 + y: -50 interfaces: - - id: 88119b68-9d08-40c4-90f5-6dc533fd020a + - id: i0 label: Loopback0 type: loopback - - id: 88119b68-9d08-40c4-90f5-6dc533fd020b - slot: 0 + - id: i1 label: MgmtEth0/RP0/CPU0/0 + slot: 0 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd020c - slot: 1 + - id: i2 label: donotuse1 + slot: 1 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd020d - slot: 2 + - id: i3 label: donotuse2 + slot: 2 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd020e - slot: 3 + - id: i4 label: GigabitEthernet0/0/0/0 + slot: 3 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd0257 - label: rtr-2 - node_definition: iosxrv9000 - x: -200 - y: -50 + - boot_disk_size: 0 configuration: |- hostname changeme username cisco @@ -440,34 +471,37 @@ def download_lab_23(req, ctx=None): password lab ! end - image_definition: iosxrv9000-6-6-2 + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n2 + label: rtr-2 + node_definition: iosxrv9000 + ram: 0 tags: [] + x: -300 + y: 50 interfaces: - - id: 88119b68-9d08-40c4-90f5-6dc533fd021a + - id: i0 label: Loopback0 type: loopback - - id: 88119b68-9d08-40c4-90f5-6dc533fd021b - slot: 0 + - id: i1 label: MgmtEth0/RP0/CPU0/0 + slot: 0 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd021c - slot: 1 + - id: i2 label: donotuse1 + slot: 1 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd021d - slot: 2 + - id: i3 label: donotuse2 + slot: 2 type: physical - - id: 88119b68-9d08-40c4-90f5-6dc533fd021e - slot: 3 + - id: i4 label: GigabitEthernet0/0/0/0 + slot: 3 type: physical - links: - - id: l0 - i1: i1 - n1: n1 - i2: i0 - n2: n0 """ return response @@ -477,43 +511,51 @@ def download_alt_lab(req, ctx=None): lab: description: '' notes: '' - timestamp: 1595337039.0416706 title: Other Lab - version: 0.0.3 + version: 0.1.0 + links: + - id: l0 + n1: n0 + n2: n1 + i1: i1 + i2: i1 + label: nxos9000-0-mgmt0<->xr9kv-0-MgmtEth0/RP0/CPU0/0 nodes: - - id: n0 + - boot_disk_size: 0 + configuration: hostname inserthostname_here + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n0 + image_definition: nxosv9000-9-2-3 label: nxos9000-0 node_definition: nxosv9000 + ram: 0 + tags: [] x: -450 y: -50 - configuration: hostname inserthostname_here - image_definition: nxosv9000-9-2-3 - tags: [] interfaces: - id: i0 label: Loopback0 type: loopback - id: i1 - slot: 0 label: mgmt0 + slot: 0 type: physical - id: i2 - slot: 1 label: Ethernet1/1 + slot: 1 type: physical - id: i3 - slot: 2 label: Ethernet1/2 + slot: 2 type: physical - id: i4 - slot: 3 label: Ethernet1/3 + slot: 3 type: physical - - id: n1 - label: xr9kv-0 - node_definition: iosxrv9000 - x: -150 - y: -50 + - boot_disk_size: 0 configuration: |- hostname changeme username cisco @@ -532,40 +574,44 @@ def download_alt_lab(req, ctx=None): password lab ! end + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n1 image_definition: iosxrv9000-6-6-2 + label: xr9kv-0 + node_definition: iosxrv9000 + ram: 0 tags: [] + x: -150 + y: -50 interfaces: - id: i0 label: Loopback0 type: loopback - id: i1 - slot: 0 label: MgmtEth0/RP0/CPU0/0 + slot: 0 type: physical - id: i2 - slot: 1 label: donotuse1 + slot: 1 type: physical - id: i3 - slot: 2 label: donotuse2 + slot: 2 type: physical - id: i4 - slot: 3 label: GigabitEthernet0/0/0/0 + slot: 3 type: physical - links: - - id: l0 - i1: i1 - n1: n0 - i2: i1 - n2: n1 """ return response @staticmethod def get_sys_info(req, ctx=None): - response = {"version": "2.4.0+build.3", "ready": True} + response = {"version": "2.6.0+build.5", "ready": True} return response @staticmethod @@ -587,8 +633,8 @@ def get_lab_element_state(req, ctx=None): return MockCMLServer._get_lab_element_state(req, ctx) @staticmethod - def get_lab_element_state_23(req, ctx=None): - return MockCMLServer._get_lab_element_state_23(req, ctx) + def get_lab_element_state_24(req, ctx=None): + return MockCMLServer._get_lab_element_state_24(req, ctx) @staticmethod def get_lab_element_state_down(req, ctx=None): @@ -604,7 +650,7 @@ def _get_lab_element_state(req, ctx=None, n2_state="BOOTED"): return response @staticmethod - def _get_lab_element_state_23(req, ctx=None, n2_state="BOOTED"): + def _get_lab_element_state_24(req, ctx=None, n2_state="BOOTED"): response = { "nodes": { "88119b68-9d08-40c4-90f5-6dc533fd0255": "BOOTED", @@ -694,14 +740,13 @@ def get_topology(req, ctx=None): {"id": "i4", "node": "n1", "data": {"label": "donotuse2", "slot": 2, "state": "STARTED", "type": "physical"}}, {"id": "i5", "node": "n1", "data": {"label": "GigabitEthernet0/0/0/0", "slot": 3, "state": "STARTED", "type": "physical"}}, ], - "lab_notes": "", - "lab_title": "Mock Test", - "lab_description": "", - "lab_owner": "admin", - "state": "STARTED", - "created_timestamp": 1597805276.8213837, - "cluster_id": "cluster_1", - "version": "0.0.3", + "lab": { + "description": "", + "notes": "", + "owner": "00000000-0000-4000-a000-000000000000", + "title": "Mock Test", + "version": "0.1.0", + }, } return response @@ -761,19 +806,18 @@ def get_alt_topology(req, ctx=None): {"id": "i8", "node": "n1", "data": {"label": "donotuse2", "slot": 2, "state": "STOPPED", "type": "physical"}}, {"id": "i9", "node": "n1", "data": {"label": "GigabitEthernet0/0/0/0", "slot": 3, "state": "STOPPED", "type": "physical"}}, ], - "lab_notes": "", - "lab_title": "Other Lab", - "lab_description": "", - "lab_owner": "admin", - "state": "STOPPED", - "created_timestamp": 1595337039.0416706, - "cluster_id": "cluster_1", - "version": "0.0.3", + "lab": { + "description": "", + "notes": "", + "owner": "00000000-0000-4000-a000-000000000000", + "title": "Other Lab", + "version": "0.1.0", + }, } return response @staticmethod - def get_topology_23(req, ctx=None): + def get_topology_24(req, ctx=None): response = { "nodes": [ { @@ -874,14 +918,13 @@ def get_topology_23(req, ctx=None): "data": {"label": "GigabitEthernet0/0/0/0", "slot": 3, "state": "STARTED", "type": "physical"}, }, ], - "lab_notes": "", - "lab_title": "Mock Test 2.3", - "lab_description": "", - "lab_owner": "admin", - "state": "STARTED", - "created_timestamp": 1597805276.8213837, - "cluster_id": "cluster_1", - "version": "0.0.3", + "lab": { + "description": "", + "notes": "", + "owner": "00000000-0000-4000-a000-000000000000", + "title": "Mock Test 2.4", + "version": "0.1.0", + }, } return response diff --git a/tests/v2/static/fake_repo_topology.yaml b/tests/v2/static/fake_repo_topology.yaml index dad1609..21135b1 100644 --- a/tests/v2/static/fake_repo_topology.yaml +++ b/tests/v2/static/fake_repo_topology.yaml @@ -1,53 +1,62 @@ lab: description: '' notes: '' - timestamp: 1589294717.9075089 title: Fake Lab - version: 0.0.3 + version: 0.1.0 +links: + - id: l0 + n1: n0 + n2: n1 + i1: i0 + i2: i0 + label: test-sw-mgmt0<->ext-conn-0-port nodes: - - id: n0 + - boot_disk_size: 0 + configuration: hostname inserthostname_here + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n0 + image_definition: nxosv-7-3-0 label: test-sw node_definition: nxosv + ram: 0 + tags: [] x: -350 y: 0 - configuration: hostname inserthostname_here - image_definition: nxosv-7-3-0 - tags: [] interfaces: - id: i0 - label: Loopback0 - type: loopback - - id: i1 - slot: 0 label: mgmt0 + slot: 0 type: physical - - id: i2 - slot: 1 + - id: i1 label: Ethernet2/1 + slot: 1 type: physical - - id: i3 - slot: 2 + - id: i2 label: Ethernet2/2 + slot: 2 type: physical - - id: i4 - slot: 3 + - id: i3 label: Ethernet2/3 + slot: 3 type: physical - - id: n1 + - boot_disk_size: 0 + configuration: bridge0 + cpu_limit: 100 + cpus: 0 + data_volume: 0 + hide_links: false + id: n1 label: ext-conn-0 node_definition: external_connector + ram: 0 + tags: [] x: 0 y: 0 - configuration: bridge0 - tags: [] interfaces: - id: i0 - slot: 0 label: port + slot: 0 type: physical -links: - - id: l0 - i1: i1 - n1: n0 - i2: i0 - n2: n1 diff --git a/tests/v2/tmux.py b/tests/v2/tmux.py index 1060daf..231abfe 100644 --- a/tests/v2/tmux.py +++ b/tests/v2/tmux.py @@ -32,7 +32,7 @@ def test_cml_tmux_panes(self, mock_server): mock_pane.send_keys.assert_has_calls(expected_calls, any_order=True) @patch("virl.cli.tmux.commands.libtmux.server.Server") - def test_cml_tmux_panes23(self, mock_server): + def test_cml_tmux_panes24(self, mock_server): with self.get_context() as m: # Mocking libtmux server mock_session = mock_server.return_value.new_session.return_value @@ -44,19 +44,19 @@ def test_cml_tmux_panes23(self, mock_server): self.setup_mocks(m) virl = self.get_virl() runner = CliRunner() - lab_id = self.get_cml23_id() + lab_id = self.get_cml24_id() runner.invoke(virl, ["use", "--id", lab_id]) runner.invoke(virl, ["tmux"]) mock_server.assert_called_once() - mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test 2_3-8811", kill_session=True) + mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test 2_4-8811", kill_session=True) expected_calls = [ call("printf '\\033]2;%s\\033\\\\' 'rtr-1'", suppress_history=True), - call("ssh -t admin@localhost open /Mock Test 2.3/rtr-1/0", suppress_history=True), + call("ssh -t admin@localhost open /Mock Test 2.4/rtr-1/0", suppress_history=True), ] mock_pane.send_keys.assert_has_calls(expected_calls, any_order=True) @patch("virl.cli.tmux.commands.libtmux.server.Server") - def test_cml_tmux_windows_23(self, mock_server): + def test_cml_tmux_windows_24(self, mock_server): with self.get_context() as m: # Mocking libtmux server mock_session = mock_server.return_value.new_session.return_value @@ -66,11 +66,11 @@ def test_cml_tmux_windows_23(self, mock_server): self.setup_mocks(m) virl = self.get_virl() runner = CliRunner() - lab_id = self.get_cml23_id() + lab_id = self.get_cml24_id() runner.invoke(virl, ["use", "--id", lab_id]) runner.invoke(virl, ["tmux", "--group", "windows"]) mock_server.assert_called_once() - mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test 2_3-8811", kill_session=True) + mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test 2_4-8811", kill_session=True) expected_calls = [ call("rename-window", "rtr-1"), ] diff --git a/tests/v2/up.py b/tests/v2/up.py index c650847..4c89ffe 100644 --- a/tests/v2/up.py +++ b/tests/v2/up.py @@ -88,14 +88,12 @@ def get_fake_topology(req, ctx=None): {"id": "i4", "node": "n0", "data": {"label": "Ethernet2/3", "slot": 3, "state": "STOPPED", "type": "physical"}}, {"id": "i5", "node": "n1", "data": {"label": "port", "slot": 0, "state": "STOPPED", "type": "physical"}}, ], - "lab_notes": "", - "lab_title": "Fake Lab", - "lab_description": "", - "lab_owner": "admin", - "state": "STOPPED", - "created_timestamp": 1589294717.9075089, - "cluster_id": "cluster_1", - "version": "0.0.3", + "lab": { + "notes": "", + "title": "Fake Lab", + "description": "", + "version": "0.1.0", + } } return response