From 38a6c0df9443b6aa3a0afbe94b19cb3bc9ed959e Mon Sep 17 00:00:00 2001 From: Tim Rupp Date: Thu, 14 Sep 2017 11:24:32 -0700 Subject: [PATCH] Adds vcmp disk api Issues: Fixes #1283 Problem: The api did not exist Analysis: this adds it Tests: functional unit --- f5/bigip/resource.py | 4 + f5/bigip/tm/vcmp/__init__.py | 6 +- .../vcmp/test/functional/test_virtual_disk.py | 126 +++++++++++++++++ .../tm/vcmp/test/unit/test_virtual_disk.py | 44 ++++++ f5/bigip/tm/vcmp/virtual_disk.py | 132 ++++++++++++++++++ 5 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 f5/bigip/tm/vcmp/test/functional/test_virtual_disk.py create mode 100644 f5/bigip/tm/vcmp/test/unit/test_virtual_disk.py create mode 100644 f5/bigip/tm/vcmp/virtual_disk.py diff --git a/f5/bigip/resource.py b/f5/bigip/resource.py index c0b764a8a..bbf4f310f 100644 --- a/f5/bigip/resource.py +++ b/f5/bigip/resource.py @@ -1105,6 +1105,10 @@ def exists(self, **kwargs): :raises: :exc:`requests.HTTPError`, Any HTTP error that was not status code 404. """ + return self._exists(**kwargs) + + def _exists(self, **kwargs): + """wrapped with exists, override that in a subclass to customize """ requests_params = self._handle_requests_params(kwargs) self._check_load_parameters(**kwargs) kwargs['uri_as_parts'] = True diff --git a/f5/bigip/tm/vcmp/__init__.py b/f5/bigip/tm/vcmp/__init__.py index 96d362389..bbb9a4929 100644 --- a/f5/bigip/tm/vcmp/__init__.py +++ b/f5/bigip/tm/vcmp/__init__.py @@ -29,9 +29,13 @@ from f5.bigip.resource import OrganizingCollection from f5.bigip.tm.vcmp.guest import Guests +from f5.bigip.tm.vcmp.virtual_disk import Virtual_Disks class Vcmp(OrganizingCollection): def __init__(self, tm): super(Vcmp, self).__init__(tm) - self._meta_data['allowed_lazy_attributes'] = [Guests] + self._meta_data['allowed_lazy_attributes'] = [ + Guests, + Virtual_Disks + ] diff --git a/f5/bigip/tm/vcmp/test/functional/test_virtual_disk.py b/f5/bigip/tm/vcmp/test/functional/test_virtual_disk.py new file mode 100644 index 000000000..f4ae8536d --- /dev/null +++ b/f5/bigip/tm/vcmp/test/functional/test_virtual_disk.py @@ -0,0 +1,126 @@ +# Copyright 2016 F5 Networks Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import pytest +import tempfile +import time + +from f5.utils.responses.handlers import Stats + + +try: + vcmp_host = pytest.config.getoption('--vcmp-host') +except Exception as ex: + vcmp_host = None + + +@pytest.fixture +def software_images(vcmp_host): + collection = vcmp_host.tm.sys.software.images.get_collection() + result = sorted([x.name.split('/')[0] for x in collection]) + return result + + +@pytest.fixture +def vcmp_guest(vcmp_host, software_images): + file = tempfile.NamedTemporaryFile() + name = os.path.basename(file.name) + resource = vcmp_host.tm.vcmp.guests.guest.create( + name=name, + initialImage=software_images[0], + state='provisioned', + managementGw='1.1.1.254', + managementIp='1.1.1.1/24', + managementNetwork='bridged' + ) + yield resource + + +@pytest.mark.skipif( + vcmp_host is None, + reason='Provide --vcmp-host to run vcmp tests.' +) +class TestGuest(object): + def test_delete(self, vcmp_host, software_images): + file = tempfile.NamedTemporaryFile() + name = os.path.basename(file.name) + resource = vcmp_host.tm.vcmp.guests.guest.create( + name=name, + initialImage=software_images[0], + state='provisioned', + managementGw='1.1.1.254', + managementIp='1.1.1.1/24', + managementNetwork='bridged' + ) + self._wait_for_provisioned(vcmp_host, resource.name) + + disk = resource.virtualDisk + slots = resource.assignedSlots + + resource.delete() + + for slot in slots: + self._wait_for_virtual_disk_ready(vcmp_host, disk, slot) + vdisk = vcmp_host.tm.vcmp.virtual_disks.virtual_disk.load( + name=disk, slot=slot + ) + vdisk.delete() + + assert vcmp_host.tm.vcmp.guests.guest.exists(name=name) is False + for slot in slots: + exists = vcmp_host.tm.vcmp.virtual_disks.virtual_disk.exists( + name=disk, slot=slot + ) + assert exists is False + + def _wait_for_provisioned(self, vcmp_host, name): + resource = vcmp_host.tm.vcmp.guests.guest.load(name=name) + nops = 0 + time.sleep(5) + while nops < 3: + try: + stats = Stats(resource.stats.load()) + requested_state = stats.stat['requestedState']['description'] + vm_status = stats.stat['vmStatus']['description'] + + if requested_state == 'provisioned' and vm_status == 'stopped': + nops += 1 + else: + nops = 0 + except Exception: + # This can be caused by restjavad restarting. + pass + time.sleep(10) + + def _wait_for_virtual_disk_ready(self, vcmp_host, disk, slot): + resource = vcmp_host.tm.vcmp.virtual_disks.virtual_disk.load( + name=disk, slot=slot + ) + nops = 0 + time.sleep(5) + while nops < 3: + try: + stats = Stats(resource.stats.load()) + status = stats.stat['status']['description'] + + if status == 'ready': + nops += 1 + else: + nops = 0 + except Exception: + # This can be caused by restjavad restarting. + pass + time.sleep(10) diff --git a/f5/bigip/tm/vcmp/test/unit/test_virtual_disk.py b/f5/bigip/tm/vcmp/test/unit/test_virtual_disk.py new file mode 100644 index 000000000..545c03947 --- /dev/null +++ b/f5/bigip/tm/vcmp/test/unit/test_virtual_disk.py @@ -0,0 +1,44 @@ +# Copyright 2017 F5 Networks Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import mock +import pytest + +from f5.bigip.tm.vcmp.virtual_disk import Virtual_Disk +from f5.sdk_exception import UnsupportedMethod + + +@pytest.fixture +def FakeResource(): + mo = mock.MagicMock() + return Virtual_Disk(mo) + + +def test_create(FakeResource): + with pytest.raises(UnsupportedMethod) as ex: + FakeResource.create() + assert "does not support the create method" in str(ex.value) + + +def test_update(FakeResource): + with pytest.raises(UnsupportedMethod) as ex: + FakeResource.update() + assert "does not support the update method" in str(ex.value) + + +def test_modify(FakeResource): + with pytest.raises(UnsupportedMethod) as ex: + FakeResource.modify() + assert "does not support the modify method" in str(ex.value) diff --git a/f5/bigip/tm/vcmp/virtual_disk.py b/f5/bigip/tm/vcmp/virtual_disk.py new file mode 100644 index 000000000..0b8c92705 --- /dev/null +++ b/f5/bigip/tm/vcmp/virtual_disk.py @@ -0,0 +1,132 @@ +# coding=utf-8 +# +# Copyright 2016 F5 Networks Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""BIG-IPĀ® Guest (vcmp) module + +REST URI + ``http://localhost/mgmt/tm/vcmp/virtual-disk/`` + +GUI Path + ``Virtual Disk List`` + +REST Kind + ``tm:vcmp:virtual-disk:*`` +""" + +from f5.bigip.resource import Collection +from f5.bigip.resource import Resource +from f5.sdk_exception import DisallowedCreationParameter +from f5.sdk_exception import DisallowedReadParameter +from f5.sdk_exception import F5SDKError +from f5.sdk_exception import UnsupportedMethod + + +class Virtual_Disks(Collection): + def __init__(self, vcmp): + super(Virtual_Disks, self).__init__(vcmp) + self._meta_data['allowed_lazy_attributes'] = [Virtual_Disk] + self._meta_data['required_json_kind'] = 'tm:vcmp:virtual-disk:virtual-diskcollectionstate' + self._meta_data['attribute_registry'] = { + 'tm:vcmp:virtual-disk:virtual-diskstate': Virtual_Disk + } + + +class Virtual_Disk(Resource): + def __init__(self, collection): + super(Virtual_Disk, self).__init__(collection) + self._meta_data['required_json_kind'] = 'tm:vcmp:virtual-disk:virtual-diskstate' + + def load(self, **kwargs): + """Loads a given resource + + Loads a given resource provided a 'name' and an optional 'slot' + parameter. The 'slot' parameter is not a required load parameter + because it is provided as an optional way of constructing the + correct 'name' of the vCMP resource. + + :param kwargs: + :return: + """ + kwargs['transform_name'] = True + kwargs = self._mutate_name(kwargs) + return self._load(**kwargs) + + def exists(self, **kwargs): + kwargs['transform_name'] = True + kwargs = self._mutate_name(kwargs) + return self._exists(**kwargs) + + def delete(self, **kwargs): + kwargs['transform_name'] = True + kwargs = self._mutate_name(kwargs) + return self._delete(**kwargs) + + def modify(self, **kwargs): + raise UnsupportedMethod( + "%s does not support the modify method" % self.__class__.__name__ + ) + + def create(self, **kwargs): + raise UnsupportedMethod( + "%s does not support the create method" % self.__class__.__name__ + ) + + def update(self, **kwargs): + raise UnsupportedMethod( + "%s does not support the update method" % self.__class__.__name__ + ) + + def _check_load_parameters(self, **kwargs): + """Override method for one in resource.py to check partition + + The partition cannot be included as a parameter to load a guest. + Raise an exception if a consumer gives the partition parameter. + + :raises: DisallowedReadParameter + """ + + if 'partition' in kwargs: + msg = "'partition' is not allowed as a load parameter. Vcmp " \ + "guests are accessed by name." + raise DisallowedReadParameter(msg) + super(Virtual_Disk, self)._check_load_parameters(**kwargs) + + def _check_create_parameters(self, **kwargs): + """Override method for one in resource.py to check partition + + The partition cannot be included as a parameter to create a guest. + Raise an exception if a consumer gives the partition parameter. + + :raises: DisallowedCreationParameter + """ + + if 'partition' in kwargs: + msg = "'partition' is not allowed as a create parameter. Vcmp " \ + "guests are created with the 'name' at least." + raise DisallowedCreationParameter(msg) + super(Virtual_Disk, self)._check_create_parameters(**kwargs) + + def _mutate_name(self, kwargs): + slot = kwargs.pop('slot', None) + if slot is not None: + try: + kwargs['name'] = '{0}/{1}'.format(kwargs['name'], int(slot)) + except ValueError: + raise F5SDKError( + "The provided 'slot' must be a number" + ) + return kwargs