Add CLI commands for Share Networks with multiple Subnets

- Added CLI commands for subnets creation, deletion and exhibition.
- Updated commands for share server manage and added a new
  parameter to share server list.

API microverion has been bumped to 2.51.

Closes-Bug: #1588144
Partially-implements: bp share-network-multiple-subnets
Change-Id: I55c85285cbdc9aaf2c0bab2f12477212b32b799a
Depends-On: Id8814a8b26c9b9dcb1fe71d0d7e9b79e8b8a9210
Co-Authored-By: lseki <luciomitsuru.seki@fit-tecnologia.org.br>
This commit is contained in:
silvacarloss 2019-06-26 17:40:40 -03:00
parent cc401e5333
commit b7d0d0d128
21 changed files with 1068 additions and 102 deletions

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
MAX_VERSION = '2.50' MAX_VERSION = '2.51'
MIN_VERSION = '2.0' MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0' DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {} _VERSIONED_METHOD_MAP = {}

@ -110,6 +110,9 @@ share_opts = [
help="Share network Name or ID, that will be used for shares. " help="Share network Name or ID, that will be used for shares. "
"Some backend drivers require a share network for share " "Some backend drivers require a share network for share "
"creation."), "creation."),
cfg.StrOpt("share_network_subnet",
help="Share network subnet ID. Some backend drivers require a "
"share network for share creation."),
cfg.StrOpt("admin_share_network", cfg.StrOpt("admin_share_network",
help="Share network Name or ID, that will be used for shares " help="Share network Name or ID, that will be used for shares "
"in admin tenant."), "in admin tenant."),

@ -76,7 +76,19 @@ class BaseTestCase(base.ClientTestBase):
if it is not found, assume it was deleted in test itself. if it is not found, assume it was deleted in test itself.
It is expected, that all resources were added as LIFO It is expected, that all resources were added as LIFO
due to restriction of deletion resources, that are in the chain. due to restriction of deletion resources, that are in the chain.
:param resources: dict with keys 'type','id','client' and 'deleted' :param resources: dict with keys 'type','id','client',
'deletion_params' and 'deleted'. Optional 'deletion_params'
contains additional data needed to delete some type of resources.
Ex:
params = {
'type': 'share_network_subnet',
'id': 'share-network-subnet-id',
'client': None,
'deletion_params': {
'share_network': 'share-network-id',
},
'deleted': False,
}
""" """
if resources is None: if resources is None:
@ -89,6 +101,7 @@ class BaseTestCase(base.ClientTestBase):
if not(res["deleted"]): if not(res["deleted"]):
res_id = res["id"] res_id = res["id"]
client = res["client"] client = res["client"]
deletion_params = res.get("deletion_params")
with handle_cleanup_exceptions(): with handle_cleanup_exceptions():
# TODO(vponomaryov): add support for other resources # TODO(vponomaryov): add support for other resources
if res["type"] is "share_type": if res["type"] is "share_type":
@ -101,6 +114,15 @@ class BaseTestCase(base.ClientTestBase):
res_id, microversion=res["microversion"]) res_id, microversion=res["microversion"])
client.wait_for_share_network_deletion( client.wait_for_share_network_deletion(
res_id, microversion=res["microversion"]) res_id, microversion=res["microversion"])
elif res["type"] is "share_network_subnet":
client.delete_share_network_subnet(
share_network_subnet=res_id,
share_network=deletion_params["share_network"],
microversion=res["microversion"])
client.wait_for_share_network_subnet_deletion(
share_network_subnet=res_id,
share_network=deletion_params["share_network"],
microversion=res["microversion"])
elif res["type"] is "share": elif res["type"] is "share":
client.delete_share( client.delete_share(
res_id, microversion=res["microversion"]) res_id, microversion=res["microversion"])
@ -233,7 +255,8 @@ class BaseTestCase(base.ClientTestBase):
@classmethod @classmethod
def create_share_network(cls, name=None, description=None, def create_share_network(cls, name=None, description=None,
neutron_net_id=None, neutron_net_id=None,
neutron_subnet_id=None, client=None, neutron_subnet_id=None,
availability_zone=None, client=None,
cleanup_in_class=True, microversion=None): cleanup_in_class=True, microversion=None):
if client is None: if client is None:
client = cls.get_admin_client() client = cls.get_admin_client()
@ -242,6 +265,7 @@ class BaseTestCase(base.ClientTestBase):
description=description, description=description,
neutron_net_id=neutron_net_id, neutron_net_id=neutron_net_id,
neutron_subnet_id=neutron_subnet_id, neutron_subnet_id=neutron_subnet_id,
availability_zone=availability_zone,
microversion=microversion, microversion=microversion,
) )
resource = { resource = {
@ -256,6 +280,31 @@ class BaseTestCase(base.ClientTestBase):
cls.method_resources.insert(0, resource) cls.method_resources.insert(0, resource)
return share_network return share_network
@classmethod
def add_share_network_subnet(cls, share_network,
neutron_net_id=None, neutron_subnet_id=None,
availability_zone=None, client=None,
cleanup_in_class=True, microversion=None):
if client is None:
client = cls.get_admin_client()
share_network_subnet = client.add_share_network_subnet(
share_network, neutron_net_id, neutron_subnet_id,
availability_zone)
resource = {
"type": "share_network_subnet",
"id": share_network_subnet["id"],
"client": client,
"deletion_params": {
"share_network": share_network,
},
"microversion": microversion,
}
if cleanup_in_class:
cls.class_resources.insert(0, resource)
else:
cls.method_resources.insert(0, resource)
return share_network_subnet
@classmethod @classmethod
def create_share(cls, share_protocol=None, size=None, share_network=None, def create_share(cls, share_protocol=None, size=None, share_network=None,
share_type=None, name=None, description=None, share_type=None, name=None, description=None,

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ast
import re import re
import time import time
@ -33,6 +34,7 @@ MESSAGE = 'message'
SHARE = 'share' SHARE = 'share'
SHARE_TYPE = 'share_type' SHARE_TYPE = 'share_type'
SHARE_NETWORK = 'share_network' SHARE_NETWORK = 'share_network'
SHARE_NETWORK_SUBNET = 'share_network_subnet'
SHARE_SERVER = 'share_server' SHARE_SERVER = 'share_server'
SNAPSHOT = 'snapshot' SNAPSHOT = 'snapshot'
SHARE_REPLICA = 'share_replica' SHARE_REPLICA = 'share_replica'
@ -115,20 +117,21 @@ class ManilaCLIClient(base.CLIClient):
'manila', action, flags, params, fail_ok, merge_stderr) 'manila', action, flags, params, fail_ok, merge_stderr)
def wait_for_resource_deletion(self, res_type, res_id, interval=3, def wait_for_resource_deletion(self, res_type, res_id, interval=3,
timeout=180, microversion=None): timeout=180, microversion=None, **kwargs):
"""Resource deletion waiter. """Resource deletion waiter.
:param res_type: text -- type of resource. Supported only 'share_type'. :param res_type: text -- type of resource
Other types support is TODO.
:param res_id: text -- ID of resource to use for deletion check :param res_id: text -- ID of resource to use for deletion check
:param interval: int -- interval between requests in seconds :param interval: int -- interval between requests in seconds
:param timeout: int -- total time in seconds to wait for deletion :param timeout: int -- total time in seconds to wait for deletion
:param args: dict -- additional keyword arguments for deletion func
""" """
# TODO(vponomaryov): add support for other resource types
if res_type == SHARE_TYPE: if res_type == SHARE_TYPE:
func = self.is_share_type_deleted func = self.is_share_type_deleted
elif res_type == SHARE_NETWORK: elif res_type == SHARE_NETWORK:
func = self.is_share_network_deleted func = self.is_share_network_deleted
elif res_type == SHARE_NETWORK_SUBNET:
func = self.is_share_network_subnet_deleted
elif res_type == SHARE_SERVER: elif res_type == SHARE_SERVER:
func = self.is_share_server_deleted func = self.is_share_server_deleted
elif res_type == SHARE: elif res_type == SHARE:
@ -143,11 +146,11 @@ class ManilaCLIClient(base.CLIClient):
raise exceptions.InvalidResource(message=res_type) raise exceptions.InvalidResource(message=res_type)
end_loop_time = time.time() + timeout end_loop_time = time.time() + timeout
deleted = func(res_id, microversion=microversion) deleted = func(res_id, microversion=microversion, **kwargs)
while not (deleted or time.time() > end_loop_time): while not (deleted or time.time() > end_loop_time):
time.sleep(interval) time.sleep(interval)
deleted = func(res_id, microversion=microversion) deleted = func(res_id, microversion=microversion, **kwargs)
if not deleted: if not deleted:
raise exceptions.ResourceReleaseFailed( raise exceptions.ResourceReleaseFailed(
@ -451,7 +454,8 @@ class ManilaCLIClient(base.CLIClient):
def create_share_network(self, name=None, description=None, def create_share_network(self, name=None, description=None,
nova_net_id=None, neutron_net_id=None, nova_net_id=None, neutron_net_id=None,
neutron_subnet_id=None, microversion=None): neutron_subnet_id=None, availability_zone=None,
microversion=None):
"""Creates share network. """Creates share network.
:param name: text -- desired name of new share network :param name: text -- desired name of new share network
@ -468,7 +472,9 @@ class ManilaCLIClient(base.CLIClient):
description=description, description=description,
nova_net_id=nova_net_id, nova_net_id=nova_net_id,
neutron_net_id=neutron_net_id, neutron_net_id=neutron_net_id,
neutron_subnet_id=neutron_subnet_id) neutron_subnet_id=neutron_subnet_id,
availability_zone=availability_zone
)
share_network_raw = self.manila( share_network_raw = self.manila(
'share-network-create %s' % params, microversion=microversion) 'share-network-create %s' % params, microversion=microversion)
share_network = output_parser.details(share_network_raw) share_network = output_parser.details(share_network_raw)
@ -476,7 +482,8 @@ class ManilaCLIClient(base.CLIClient):
def _combine_share_network_data(self, name=None, description=None, def _combine_share_network_data(self, name=None, description=None,
nova_net_id=None, neutron_net_id=None, nova_net_id=None, neutron_net_id=None,
neutron_subnet_id=None): neutron_subnet_id=None,
availability_zone=None):
"""Combines params for share network operations 'create' and 'update'. """Combines params for share network operations 'create' and 'update'.
:returns: text -- set of CLI parameters :returns: text -- set of CLI parameters
@ -492,9 +499,11 @@ class ManilaCLIClient(base.CLIClient):
data['--neutron_net_id'] = neutron_net_id data['--neutron_net_id'] = neutron_net_id
if neutron_subnet_id is not None: if neutron_subnet_id is not None:
data['--neutron_subnet_id'] = neutron_subnet_id data['--neutron_subnet_id'] = neutron_subnet_id
if availability_zone is not None:
data['--availability_zone'] = availability_zone
cmd = '' cmd = ''
for key, value in data.items(): for key, value in data.items():
cmd += "%(k)s=%(v)s " % dict(k=key, v=value) cmd += "%(k)s=%(v)s " % {'k': key, 'v': value}
return cmd return cmd
@not_found_wrapper @not_found_wrapper
@ -598,6 +607,106 @@ class ManilaCLIClient(base.CLIClient):
SHARE_NETWORK, res_id=share_network, interval=2, timeout=6, SHARE_NETWORK, res_id=share_network, interval=2, timeout=6,
microversion=microversion) microversion=microversion)
# Share Network Subnets
def _combine_share_network_subnet_data(self, neutron_net_id=None,
neutron_subnet_id=None,
availability_zone=None):
"""Combines params for share network subnet 'create' operation.
:returns: text -- set of CLI parameters
"""
data = dict()
if neutron_net_id is not None:
data['--neutron_net_id'] = neutron_net_id
if neutron_subnet_id is not None:
data['--neutron_subnet_id'] = neutron_subnet_id
if availability_zone is not None:
data['--availability_zone'] = availability_zone
cmd = ''
for key, value in data.items():
cmd += "%(k)s=%(v)s " % dict(k=key, v=value)
return cmd
def add_share_network_subnet(self, share_network,
neutron_net_id=None, neutron_subnet_id=None,
availability_zone=None, microversion=None):
"""Create new share network subnet for the given share network."""
params = self._combine_share_network_subnet_data(
neutron_net_id=neutron_net_id,
neutron_subnet_id=neutron_subnet_id,
availability_zone=availability_zone)
share_network_subnet_raw = self.manila(
'share-network-subnet-create %(sn)s %(params)s' %
{'sn': share_network, 'params': params}, microversion=microversion)
share_network_subnet = output_parser.details(share_network_subnet_raw)
return share_network_subnet
def get_share_network_subnet(self, share_network, share_network_subnet,
microversion=None):
"""Returns share network subnet by share network ID and subnet ID."""
share_network_subnet_raw = self.manila(
'share-network-subnet-show %(share_net)s %(share_subnet)s' % {
'share_net': share_network,
'share_subnet': share_network_subnet,
})
share_network_subnet = output_parser.details(share_network_subnet_raw)
return share_network_subnet
def get_share_network_subnets(self, share_network, microversion=None):
share_network = self.get_share_network(share_network,
microversion=microversion)
raw_subnets = share_network.get('share_network_subnets')
subnets = ast.literal_eval(raw_subnets)
# NOTE(lseki): convert literal None to string 'None'
for subnet in subnets:
for k, v in subnet.items():
subnet[k] = str(v) if v is None else v
return subnets
@not_found_wrapper
def delete_share_network_subnet(self, share_network, share_network_subnet,
microversion=None):
"""Delete a share_network."""
self.manila(
'share-network-subnet-delete %(share_net)s %(share_subnet)s' % {
'share_net': share_network,
'share_subnet': share_network_subnet,
}, microversion=microversion)
def is_share_network_subnet_deleted(self, share_network_subnet,
share_network, microversion=None):
# NOTE(lseki): the parameter share_network_subnet comes before
# share_network, because the wrapper method wait_for_resource_deletion
# expects the resource id in first place (subnet ID in this case).
"""Says whether share network subnet is deleted or not.
:param share_network_subnet: text -- Name or ID of share network subnet
:param share_network: text -- Name or ID of share network the subnet
belongs to
"""
subnets = self.get_share_network_subnets(share_network)
return not any(subnet['id'] == share_network_subnet
for subnet in subnets)
def wait_for_share_network_subnet_deletion(self, share_network_subnet,
share_network,
microversion=None):
# NOTE(lseki): the parameter share_network_subnet comes before
# share_network, because the wrapper method wait_for_resource_deletion
# expects the resource id in first place (subnet ID in this case).
"""Wait for share network subnet deletion by its Name or ID.
:param share_network_subnet: text -- Name or ID of share network subnet
:param share_network: text -- Name or ID of share network the subnet
belongs to
"""
args = {'share_network': share_network}
self.wait_for_resource_deletion(
SHARE_NETWORK_SUBNET, res_id=share_network_subnet,
interval=2, timeout=6, microversion=microversion, **args)
# Shares # Shares
def create_share(self, share_protocol, size, share_network=None, def create_share(self, share_protocol, size, share_network=None,

@ -0,0 +1,120 @@
# Copyright 2019 NetApp
# All Rights Reserved.
#
# 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 ddt
from manilaclient.tests.functional import base
from manilaclient.tests.functional import utils
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
@ddt.ddt
@utils.skip_if_microversion_not_supported('2.51')
class ShareNetworkSubnetsReadWriteTest(base.BaseTestCase):
@classmethod
def setUpClass(cls):
super(ShareNetworkSubnetsReadWriteTest, cls).setUpClass()
cls.name = data_utils.rand_name('autotest')
cls.description = 'fake_description'
cls.neutron_net_id = 'fake_neutron_net_id'
cls.neutron_subnet_id = 'fake_neutron_subnet_id'
cls.sn = cls.create_share_network(
name=cls.name,
description=cls.description,
neutron_net_id=cls.neutron_net_id,
neutron_subnet_id=cls.neutron_subnet_id,
)
def test_get_share_network_subnet(self):
default_subnet = utils.get_default_subnet(self.user_client,
self.sn['id'])
subnet = self.user_client.get_share_network_subnet(
self.sn['id'], default_subnet['id'])
self.assertEqual(self.neutron_net_id, subnet['neutron_net_id'])
self.assertEqual(self.neutron_subnet_id, subnet['neutron_subnet_id'])
def test_get_invalid_share_network_subnet(self):
self.assertRaises(
exceptions.CommandFailed,
self.user_client.get_share_network_subnet,
self.sn['id'], 'invalid_subnet_id')
def _get_availability_zone(self):
availability_zones = self.user_client.list_availability_zones()
return availability_zones[0]['Name']
def test_add_share_network_subnet_to_share_network(self):
neutron_net_id = 'new_neutron_net_id'
neutron_subnet_id = 'new_neutron_subnet_id'
availability_zone = self._get_availability_zone()
subnet = self.add_share_network_subnet(
self.sn['id'],
neutron_net_id, neutron_subnet_id,
availability_zone,
cleanup_in_class=False)
self.assertEqual(neutron_net_id, subnet['neutron_net_id'])
self.assertEqual(neutron_subnet_id, subnet['neutron_subnet_id'])
self.assertEqual(availability_zone, subnet['availability_zone'])
@ddt.data(
{'neutron_net_id': None, 'neutron_subnet_id': 'fake_subnet_id'},
{'neutron_net_id': 'fake_net_id', 'neutron_subnet_id': None},
{'availability_zone': 'invalid_availability_zone'},
)
def test_add_invalid_share_network_subnet_to_share_network(self, params):
self.assertRaises(
exceptions.CommandFailed,
self.add_share_network_subnet,
self.sn['id'],
**params)
def test_add_share_network_subnet_to_invalid_share_network(self):
self.assertRaises(
exceptions.CommandFailed,
self.add_share_network_subnet,
'invalid_share_network',
self.neutron_net_id,
self.neutron_subnet_id)
def test_add_delete_share_network_subnet_from_share_network(self):
neutron_net_id = 'new_neutron_net_id'
neutron_subnet_id = 'new_neutron_subnet_id'
availability_zone = self._get_availability_zone()
subnet = self.add_share_network_subnet(
self.sn['id'],
neutron_net_id, neutron_subnet_id,
availability_zone,
cleanup_in_class=False)
self.user_client.delete_share_network_subnet(
share_network_subnet=subnet['id'],
share_network=self.sn['id'])
self.user_client.wait_for_share_network_subnet_deletion(
share_network_subnet=subnet['id'],
share_network=self.sn['id'])
def test_delete_invalid_share_network_subnet(self):
self.assertRaises(
exceptions.NotFound,
self.user_client.delete_share_network_subnet,
share_network_subnet='invalid_subnet_id',
share_network=self.sn['id'])

@ -14,11 +14,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ast
import ddt import ddt
from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as tempest_lib_exc from tempest.lib import exceptions as tempest_lib_exc
from manilaclient.tests.functional import base from manilaclient.tests.functional import base
from manilaclient.tests.functional import utils
@ddt.ddt @ddt.ddt
@ -46,7 +48,14 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
'neutron_subnet_id': 'fake_neutron_subnet_id'}, 'neutron_subnet_id': 'fake_neutron_subnet_id'},
) )
def test_create_delete_share_network(self, net_data): def test_create_delete_share_network(self, net_data):
share_subnet_support = utils.share_network_subnets_are_supported()
share_subnet_fields = (
['neutron_net_id', 'neutron_subnet_id', 'availability_zone']
if share_subnet_support else [])
sn = self.create_share_network(cleanup_in_class=False, **net_data) sn = self.create_share_network(cleanup_in_class=False, **net_data)
default_subnet = (utils.get_default_subnet(self.user_client, sn['id'])
if share_subnet_support
else None)
expected_data = { expected_data = {
'name': 'None', 'name': 'None',
@ -55,9 +64,54 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
'neutron_subnet_id': 'None', 'neutron_subnet_id': 'None',
} }
expected_data.update(net_data) expected_data.update(net_data)
share_network_expected_data = [
(k, v) for k, v in expected_data.items()
if k not in share_subnet_fields]
share_subnet_expected_data = [
(k, v) for k, v in expected_data.items()
if k in share_subnet_fields]
for k, v in expected_data.items(): for k, v in share_network_expected_data:
self.assertEqual(v, sn[k]) self.assertEqual(v, sn[k])
for k, v in share_subnet_expected_data:
self.assertEqual(v, default_subnet[k])
self.admin_client.delete_share_network(sn['id'])
self.admin_client.wait_for_share_network_deletion(sn['id'])
@utils.skip_if_microversion_not_supported('2.51')
def test_create_delete_share_network_with_az(self):
share_subnet_fields = (
['neutron_net_id', 'neutron_subnet_id', 'availability_zone'])
az = self.user_client.list_availability_zones()[0]
net_data = {
'neutron_net_id': 'fake_neutron_net_id',
'neutron_subnet_id': 'fake_neutron_subnet_id',
'availability_zone': az['Name']
}
sn = self.create_share_network(cleanup_in_class=False, **net_data)
default_subnet = utils.get_subnet_by_availability_zone_name(
self.user_client, sn['id'], az['Name'])
expected_data = {
'name': 'None',
'description': 'None',
'neutron_net_id': 'None',
'neutron_subnet_id': 'None',
'availability_zone': 'None',
}
expected_data.update(net_data)
share_network_expected_data = [
(k, v) for k, v in expected_data.items()
if k not in share_subnet_fields]
share_subnet_expected_data = [
(k, v) for k, v in expected_data.items()
if k in share_subnet_fields]
for k, v in share_network_expected_data:
self.assertEqual(v, sn[k])
for k, v in share_subnet_expected_data:
self.assertEqual(v, default_subnet[k])
self.admin_client.delete_share_network(sn['id']) self.admin_client.delete_share_network(sn['id'])
self.admin_client.wait_for_share_network_deletion(sn['id']) self.admin_client.wait_for_share_network_deletion(sn['id'])
@ -67,6 +121,7 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
self.assertEqual(self.name, get['name']) self.assertEqual(self.name, get['name'])
self.assertEqual(self.description, get['description']) self.assertEqual(self.description, get['description'])
if not utils.share_network_subnets_are_supported():
self.assertEqual(self.neutron_net_id, get['neutron_net_id']) self.assertEqual(self.neutron_net_id, get['neutron_net_id'])
self.assertEqual(self.neutron_subnet_id, get['neutron_subnet_id']) self.assertEqual(self.neutron_subnet_id, get['neutron_subnet_id'])
@ -91,11 +146,21 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
'neutron_net_id': 'None', 'neutron_net_id': 'None',
'neutron_subnet_id': 'None', 'neutron_subnet_id': 'None',
} }
subnet_keys = []
if utils.share_network_subnets_are_supported():
subnet_keys = ['neutron_net_id', 'neutron_subnet_id']
subnet = ast.literal_eval(update['share_network_subnets'])
expected_data['neutron_net_id'] = None
expected_data['neutron_subnet_id'] = None
update_values = dict([(k, v) for k, v in net_data.items() update_values = dict([(k, v) for k, v in net_data.items()
if v != '""']) if v != '""'])
expected_data.update(update_values) expected_data.update(update_values)
for k, v in expected_data.items(): for k, v in expected_data.items():
if k in subnet_keys:
self.assertEqual(v, subnet[0][k])
else:
self.assertEqual(v, update[k]) self.assertEqual(v, update[k])
self.admin_client.delete_share_network(sn['id']) self.admin_client.delete_share_network(sn['id'])
@ -119,6 +184,14 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
self.assertTrue(all('name' not in s for s in share_networks)) self.assertTrue(all('name' not in s for s in share_networks))
def _list_share_networks_with_filters(self, filters): def _list_share_networks_with_filters(self, filters):
assert_subnet_fields = utils.share_network_subnets_are_supported()
share_subnet_fields = (['neutron_subnet_id', 'neutron_net_id']
if assert_subnet_fields
else [])
share_network_filters = [(k, v) for k, v in filters.items()
if k not in share_subnet_fields]
share_network_subnet_filters = [(k, v) for k, v in filters.items()
if k in share_subnet_fields]
share_networks = self.admin_client.list_share_networks(filters=filters) share_networks = self.admin_client.list_share_networks(filters=filters)
self.assertGreater(len(share_networks), 0) self.assertGreater(len(share_networks), 0)
@ -126,14 +199,21 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
any(self.sn['id'] == sn['id'] for sn in share_networks)) any(self.sn['id'] == sn['id'] for sn in share_networks))
for sn in share_networks: for sn in share_networks:
try: try:
get = self.admin_client.get_share_network(sn['id']) share_network = self.admin_client.get_share_network(sn['id'])
default_subnet = (
utils.get_default_subnet(self.user_client, sn['id'])
if assert_subnet_fields
else None)
except tempest_lib_exc.NotFound: except tempest_lib_exc.NotFound:
# NOTE(vponomaryov): Case when some share network was deleted # NOTE(vponomaryov): Case when some share network was deleted
# between our 'list' and 'get' requests. Skip such case. # between our 'list' and 'get' requests. Skip such case.
continue continue
for k, v in filters.items(): for k, v in share_network_filters:
self.assertIn(k, get) self.assertIn(k, share_network)
self.assertEqual(v, get[k]) self.assertEqual(v, share_network[k])
for k, v in share_network_subnet_filters:
self.assertIn(k, default_subnet)
self.assertEqual(v, default_subnet[k])
def test_list_share_networks_filter_by_project_id(self): def test_list_share_networks_filter_by_project_id(self):
project_id = self.admin_client.get_project_id( project_id = self.admin_client.get_project_id(

@ -89,13 +89,18 @@ class ShareServersReadWriteBase(base.BaseTestCase):
common_share_network = self.client.get_share_network( common_share_network = self.client.get_share_network(
self.client.share_network) self.client.share_network)
share_net_info = (
utils.get_default_subnet(self.user_client,
common_share_network['id'])
if utils.share_network_subnets_are_supported()
else common_share_network)
neutron_net_id = ( neutron_net_id = (
common_share_network['neutron_net_id'] share_net_info['neutron_net_id']
if 'none' not in common_share_network['neutron_net_id'].lower() if 'none' not in share_net_info['neutron_net_id'].lower()
else None) else None)
neutron_subnet_id = ( neutron_subnet_id = (
common_share_network['neutron_subnet_id'] share_net_info['neutron_subnet_id']
if 'none' not in common_share_network['neutron_subnet_id'].lower() if 'none' not in share_net_info['neutron_subnet_id'].lower()
else None) else None)
share_network = self.client.create_share_network( share_network = self.client.create_share_network(
neutron_net_id=neutron_net_id, neutron_net_id=neutron_net_id,
@ -142,6 +147,7 @@ class ShareServersReadWriteBase(base.BaseTestCase):
self.assertIn(key, server) self.assertIn(key, server)
self._delete_share_and_share_server(self.share['id'], share_server_id) self._delete_share_and_share_server(self.share['id'], share_server_id)
self.client.delete_share_network(share_network['id'])
@testtools.skipUnless( @testtools.skipUnless(
CONF.run_manage_tests, 'Share Manage/Unmanage tests are disabled.') CONF.run_manage_tests, 'Share Manage/Unmanage tests are disabled.')
@ -189,6 +195,7 @@ class ShareServersReadWriteBase(base.BaseTestCase):
self._delete_share_and_share_server(managed_share_id, self._delete_share_and_share_server(managed_share_id,
managed_share_server_id) managed_share_server_id)
self.client.delete_share_network(share_network['id'])
class ShareServersReadWriteNFSTest(ShareServersReadWriteBase): class ShareServersReadWriteNFSTest(ShareServersReadWriteBase):

@ -113,9 +113,14 @@ class SharesTestMigration(base.BaseTestCase):
cls.old_share_net = cls.get_user_client().get_share_network( cls.old_share_net = cls.get_user_client().get_share_network(
cls.get_user_client().share_network) cls.get_user_client().share_network)
share_net_info = (
utils.get_default_subnet(cls.get_user_client(),
cls.old_share_net['id'])
if utils.share_network_subnets_are_supported()
else cls.old_share_net)
cls.new_share_net = cls.create_share_network( cls.new_share_net = cls.create_share_network(
neutron_net_id=cls.old_share_net['neutron_net_id'], neutron_net_id=share_net_info['neutron_net_id'],
neutron_subnet_id=cls.old_share_net['neutron_subnet_id']) neutron_subnet_id=share_net_info['neutron_subnet_id'])
@utils.skip_if_microversion_not_supported('2.22') @utils.skip_if_microversion_not_supported('2.22')
@ddt.data('migration_error', 'migration_success', 'None') @ddt.data('migration_error', 'migration_success', 'None')

@ -138,3 +138,18 @@ def choose_matching_backend(share, pools, share_type):
None) None)
return selected_pool['Name'] return selected_pool['Name']
def share_network_subnets_are_supported():
return is_microversion_supported('2.51')
def get_subnet_by_availability_zone_name(client, share_network_id, az_name):
subnets = client.get_share_network_subnets(share_network_id)
return next((subnet for subnet in subnets
if subnet['availability_zone'] == az_name), None)
def get_default_subnet(client, share_network_id):
return get_subnet_by_availability_zone_name(client, share_network_id,
'None')

@ -579,6 +579,9 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
def post_share_networks(self, **kwargs): def post_share_networks(self, **kwargs):
return (202, {}, {'share_network': {}}) return (202, {}, {'share_network': {}})
def post_share_networks_1234_subnets(self, **kwargs):
return (202, {}, {'share_network_subnet': {}})
def post_shares(self, **kwargs): def post_shares(self, **kwargs):
return (202, {}, {'share': {}}) return (202, {}, {'share': {}})
@ -609,6 +612,12 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
def delete_share_networks_fake_share_network2(self, **kwargs): def delete_share_networks_fake_share_network2(self, **kwargs):
return (202, {}, None) return (202, {}, None)
def delete_share_networks_1234_subnets_fake_subnet1(self, **kwargs):
return (202, {}, None)
def delete_share_networks_1234_subnets_fake_subnet2(self, **kwargs):
return (202, {}, None)
def delete_snapshots_fake_snapshot1(self, **kwargs): def delete_snapshots_fake_snapshot1(self, **kwargs):
return (202, {}, None) return (202, {}, None)
@ -663,6 +672,14 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
} }
return (200, {}, share_nw) return (200, {}, share_nw)
def get_share_networks_1234_subnets_fake_subnet_id(self, **kw):
subnet = {
'share_network_subnet': {
'id': 'fake_subnet_id',
},
}
return (200, {}, subnet)
def get_security_services(self, **kw): def get_security_services(self, **kw):
security_services = { security_services = {
'security_services': [ 'security_services': [

@ -0,0 +1,82 @@
# Copyright 2019 NetApp
# All Rights Reserved.
#
# 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 ddt
import mock
from manilaclient.tests.unit import utils
from manilaclient.tests.unit.v2 import fakes
from manilaclient.v2 import share_network_subnets
@ddt.ddt
class ShareNetworkSubnetTest(utils.TestCase):
def setUp(self):
super(ShareNetworkSubnetTest, self).setUp()
self.manager = share_network_subnets.ShareNetworkSubnetManager(
fakes.FakeClient())
def test_create(self):
share_network_id = 'fake_share_net_id'
expected_url = share_network_subnets.RESOURCES_PATH % {
'share_network_id': share_network_id
}
expected_values = {
'neutron_net_id': 'fake_net_id',
'neutron_subnet_id': 'fake_subnet_id',
'availability_zone': 'fake_availability_zone',
}
expected_body = {'share-network-subnet': expected_values}
payload = expected_values.copy()
payload.update({'share_network_id': share_network_id})
with mock.patch.object(self.manager, '_create', fakes.fake_create):
result = self.manager.create(**payload)
self.assertEqual(expected_url, result['url'])
self.assertEqual(
share_network_subnets.RESOURCE_NAME,
result['resp_key'])
self.assertEqual(
expected_body,
result['body'])
def test_get(self):
share_network = 'fake_share_network'
share_subnet = 'fake_share_subnet'
with mock.patch.object(self.manager, '_get', mock.Mock()):
self.manager.get(share_network, share_subnet)
self.manager._get.assert_called_once_with(
share_network_subnets.RESOURCE_PATH % {
'share_network_id': share_network,
'share_network_subnet_id': share_subnet
},
share_network_subnets.RESOURCE_NAME)
def test_delete(self):
share_network = 'fake_share_network'
share_subnet = 'fake_share_subnet'
with mock.patch.object(self.manager, '_delete', mock.Mock()):
self.manager.delete(share_network, share_subnet)
self.manager._delete.assert_called_once_with(
share_network_subnets.RESOURCE_PATH % {
'share_network_id': share_network,
'share_network_subnet_id': share_subnet
})

@ -42,14 +42,6 @@ class ShareReplicasTest(utils.TestCase):
} }
self._create_common(values) self._create_common(values)
def test_create_with_share_network(self):
values = {
'availability_zone': 'az1',
'share': 's1',
'share_network': 'sn1',
}
self._create_common(values)
def _create_common(self, values): def _create_common(self, values):
with mock.patch.object(self.manager, '_create', fakes.fake_create): with mock.patch.object(self.manager, '_create', fakes.fake_create):

@ -87,6 +87,7 @@ class ShareServerManagerTest(utils.TestCase):
def test_manage(self, driver_options): def test_manage(self, driver_options):
host = 'fake_host' host = 'fake_host'
share_network_id = 'fake_share_net_id' share_network_id = 'fake_share_net_id'
share_network_subnet_id = 'fake_share_network_subnet_id'
identifier = 'ff-aa-kk-ee-00' identifier = 'ff-aa-kk-ee-00'
if driver_options is None: if driver_options is None:
driver_options = {} driver_options = {}
@ -94,12 +95,15 @@ class ShareServerManagerTest(utils.TestCase):
'host': host, 'host': host,
'share_network_id': share_network_id, 'share_network_id': share_network_id,
'identifier': identifier, 'identifier': identifier,
'driver_options': driver_options 'driver_options': driver_options,
'share_network_subnet_id': share_network_subnet_id,
} }
with mock.patch.object(self.manager, '_create', with mock.patch.object(self.manager, '_create',
mock.Mock(return_value='fake')): mock.Mock(return_value='fake')):
result = self.manager.manage(host, share_network_id, identifier, result = self.manager.manage(
driver_options) host, share_network_id, identifier,
driver_options=driver_options,
share_network_subnet_id=share_network_subnet_id)
self.manager._create.assert_called_once_with( self.manager._create.assert_called_once_with(
share_servers.RESOURCES_PATH + '/manage', share_servers.RESOURCES_PATH + '/manage',
{'share_server': expected_body}, 'share_server' {'share_server': expected_body}, 'share_server'

@ -35,6 +35,7 @@ from manilaclient import utils
from manilaclient.v2 import messages from manilaclient.v2 import messages
from manilaclient.v2 import security_services from manilaclient.v2 import security_services
from manilaclient.v2 import share_instances from manilaclient.v2 import share_instances
from manilaclient.v2 import share_network_subnets
from manilaclient.v2 import share_networks from manilaclient.v2 import share_networks
from manilaclient.v2 import share_servers from manilaclient.v2 import share_servers
from manilaclient.v2 import share_snapshots from manilaclient.v2 import share_snapshots
@ -734,36 +735,96 @@ class ShellTest(test_utils.TestCase):
+ ' --share_type fake_share_type' + ' --share_type fake_share_type'
+ ' --share_server_id fake_server') + ' --share_server_id fake_server')
def test_share_server_manage_unsupported_version(self):
self.assertRaises(
exceptions.UnsupportedVersion,
self.run_command,
'--os-share-api-version 2.48 ' +
'share-server-manage fake_host fake_share_net_id fake_id')
def test_share_server_manage_invalid_param_subnet_id(self):
self.assertRaises(
exceptions.CommandError,
self.run_command,
'--os-share-api-version 2.49 ' +
'share-server-manage fake_host fake_share_net_id fake_id ' +
'--share-network-subnet fake_subnet_id')
@ddt.data({'driver_args': '--driver_options opt1=opt1 opt2=opt2', @ddt.data({'driver_args': '--driver_options opt1=opt1 opt2=opt2',
'valid_params': { 'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'}, 'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
}, }},
'version': '--os-share-api-version 2.49', {'driver_args': '--driver_options opt1=opt1 opt2=opt2',
}, 'subnet_id': 'fake_subnet_1',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
}},
{'driver_args': '--driver_options opt1=opt1 opt2=opt2', {'driver_args': '--driver_options opt1=opt1 opt2=opt2',
'valid_params': { 'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'}, 'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
}, },
'version': '2.51',
},
{'driver_args': '--driver_options opt1=opt1 opt2=opt2',
'subnet_id': 'fake_subnet_1',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
},
'version': '2.51',
}, },
{'driver_args': "", {'driver_args': "",
'valid_params': { 'valid_params': {
'driver_options': {} 'driver_options': {}
}, },
'version': '--os-share-api-version 2.49', 'version': '2.51',
}) },
{'driver_args': '--driver_options opt1=opt1 opt2=opt2',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
},
'version': '2.49',
},
{'driver_args': '',
'valid_params': {
'driver_options': {},
},
'network_id': 'fake_network_id',
'version': '2.49',
},
{'driver_args': "",
'valid_params': {
'driver_options': {}
},
'version': '2.49',
},
)
@ddt.unpack @ddt.unpack
def test_share_server_manage(self, driver_args, valid_params, def test_share_server_manage(self, driver_args, valid_params,
version=None): version=None, network_id=None,
subnet_id=None):
subnet_support = (version is None or
api_versions.APIVersion(version) >=
api_versions.APIVersion('2.51'))
network_id = '3456' if network_id is None else network_id
fake_share_network = type( fake_share_network = type(
'FakeShareNetwork', (object,), {'id': '3456'}) 'FakeShareNetwork', (object,), {'id': network_id})
self.mock_object( self.mock_object(
shell_v2, '_find_share_network', shell_v2, '_find_share_network',
mock.Mock(return_value=fake_share_network)) mock.Mock(return_value=fake_share_network))
command = "" if version is None else version command = ('share-server-manage '
command += (' share-server-manage fake_host fake_share_net_id ' '%(host)s '
+ ' 88-as-23-f3-45 ' + driver_args) '%(share_network_id)s '
'%(identifier)s '
'%(driver_args)s ' % {
'host': 'fake_host',
'share_network_id': fake_share_network.id,
'identifier': '88-as-23-f3-45',
'driver_args': driver_args,
})
command += '--share-network-subnet %s' % subnet_id if subnet_id else ''
self.run_command(command) self.run_command(command, version=version)
expected = { expected = {
'share_server': { 'share_server': {
@ -773,6 +834,8 @@ class ShellTest(test_utils.TestCase):
'driver_options': driver_args 'driver_options': driver_args
} }
} }
if subnet_support:
expected['share_server']['share_network_subnet_id'] = subnet_id
expected['share_server'].update(valid_params) expected['share_server'].update(valid_params)
self.assert_called('POST', '/share-servers/manage', body=expected) self.assert_called('POST', '/share-servers/manage', body=expected)
@ -1425,25 +1488,39 @@ class ShellTest(test_utils.TestCase):
self.assert_called('POST', '/share-networks') self.assert_called('POST', '/share-networks')
@ddt.unpack
@ddt.data( @ddt.data(
{'--name': 'fake_name'}, {'data': {'--name': 'fake_name'}},
{'--description': 'fake_description'}, {'data': {'--description': 'fake_description'}},
{'--neutron_net_id': 'fake_neutron_net_id'}, {'data': {'--neutron_net_id': 'fake_neutron_net_id'},
{'--neutron_subnet_id': 'fake_neutron_subnet_id'}, 'version': '2.49',
{'--description': 'fake_description', },
{'data': {'--neutron_subnet_id': 'fake_neutron_subnet_id'},
'version': '2.49',
},
{'data': {
'--description': 'fake_description',
'--name': 'fake_name', '--name': 'fake_name',
'--neutron_net_id': 'fake_neutron_net_id', '--neutron_net_id': 'fake_neutron_net_id',
'--neutron_subnet_id': 'fake_neutron_subnet_id'}, '--neutron_subnet_id': 'fake_neutron_subnet_id'},
{'--name': '""'}, 'version': '2.49',
{'--description': '""'}, },
{'--neutron_net_id': '""'}, {'data': {'--name': '""'}},
{'--neutron_subnet_id': '""'}, {'data': {'--description': '""'}},
{'--description': '""', {'data': {'--neutron_net_id': '""'},
'version': '2.49',
},
{'data': {'--neutron_subnet_id': '""'},
'version': '2.49',
},
{'data': {
'--description': '""',
'--name': '""', '--name': '""',
'--neutron_net_id': '""', '--neutron_net_id': '""',
'--neutron_subnet_id': '""', '--neutron_subnet_id': '""'},
},) 'version': '2.49',
def test_share_network_update(self, data): })
def test_share_network_update(self, data, version=None):
cmd = 'share-network-update 1111' cmd = 'share-network-update 1111'
expected = dict() expected = dict()
for k, v in data.items(): for k, v in data.items():
@ -1451,7 +1528,7 @@ class ShellTest(test_utils.TestCase):
expected[k[2:]] = v expected[k[2:]] = v
expected = dict(share_network=expected) expected = dict(share_network=expected)
self.run_command(cmd) self.run_command(cmd, version=version)
self.assert_called('PUT', '/share-networks/1111', body=expected) self.assert_called('PUT', '/share-networks/1111', body=expected)
@ -1723,6 +1800,143 @@ class ShellTest(test_utils.TestCase):
'/security-services/detail?share_network_id=1111', '/security-services/detail?share_network_id=1111',
) )
@ddt.data(
{},
{'--neutron_net_id': 'fake_neutron_net_id',
'--neutron_subnet_id': 'fake_neutron_subnet_id'},
{'--availability-zone': 'fake_availability_zone_id'},
{'--neutron_net_id': 'fake_neutron_net_id',
'--neutron_subnet_id': 'fake_neutron_subnet_id',
'--availability-zone': 'fake_availability_zone_id'})
def test_share_network_subnet_add(self, data):
fake_share_network = type(
'FakeShareNetwork', (object,), {'id': '1234'})
self.mock_object(
shell_v2, '_find_share_network',
mock.Mock(return_value=fake_share_network))
cmd = 'share-network-subnet-create'
for k, v in data.items():
cmd += ' ' + k + ' ' + v
cmd += ' ' + fake_share_network.id
self.run_command(cmd)
shell_v2._find_share_network.assert_called_once_with(
mock.ANY, fake_share_network.id)
self.assert_called('POST', '/share-networks/1234/subnets')
@ddt.data(
{'--neutron_net_id': 'fake_neutron_net_id'},
{'--neutron_subnet_id': 'fake_neutron_subnet_id'},
{'--neutron_net_id': 'fake_neutron_net_id',
'--availability-zone': 'fake_availability_zone_id'},
{'--neutron_subnet_id': 'fake_neutron_subnet_id',
'--availability-zone': 'fake_availability_zone_id'})
def test_share_network_subnet_add_invalid_param(self, data):
cmd = 'share-network-subnet-create'
for k, v in data.items():
cmd += ' ' + k + ' ' + v
cmd += ' fake_network_id'
self.assertRaises(
exceptions.CommandError,
self.run_command,
cmd)
def test_share_network_subnet_add_invalid_share_network(self):
cmd = 'share-network-subnet-create not-found-id'
self.assertRaises(
exceptions.CommandError,
self.run_command,
cmd)
@ddt.data(('fake_subnet1', ),
('fake_subnet1', 'fake_subnet2'))
def test_share_network_subnet_delete(self, subnet_ids):
fake_share_network = type(
'FakeShareNetwork', (object,), {'id': '1234'})
self.mock_object(
shell_v2, '_find_share_network',
mock.Mock(return_value=fake_share_network))
fake_share_network_subnets = [
share_network_subnets.ShareNetworkSubnet(
'fake', {'id': subnet_id}, True)
for subnet_id in subnet_ids
]
self.run_command(
'share-network-subnet-delete %(network_id)s %(subnet_ids)s' % {
'network_id': fake_share_network.id,
'subnet_ids': ' '.join(subnet_ids)
})
shell_v2._find_share_network.assert_called_once_with(
mock.ANY, fake_share_network.id)
for subnet in fake_share_network_subnets:
self.assert_called_anytime(
'DELETE', '/share-networks/1234/subnets/%s' % subnet.id,
clear_callstack=False)
def test_share_network_subnet_delete_invalid_share_network(self):
command = 'share-network-subnet-delete %(net_id)s %(subnet_id)s' % {
'net_id': 'not-found-id',
'subnet_id': '1234',
}
self.assertRaises(
exceptions.CommandError,
self.run_command,
command)
def test_share_network_subnet_delete_invalid_share_network_subnet(self):
fake_share_network = type(
'FakeShareNetwork', (object,), {'id': '1234'})
self.mock_object(
shell_v2, '_find_share_network',
mock.Mock(return_value=fake_share_network))
command = 'share-network-subnet-delete %(net_id)s %(subnet_id)s' % {
'net_id': fake_share_network.id,
'subnet_id': 'not-found-id',
}
self.assertRaises(
exceptions.CommandError,
self.run_command,
command)
@mock.patch.object(cliutils, 'print_dict', mock.Mock())
def test_share_network_subnet_show(self):
fake_share_network = type(
'FakeShareNetwork', (object,), {'id': '1234'})
self.mock_object(
shell_v2, '_find_share_network',
mock.Mock(return_value=fake_share_network))
args = {
'share_net_id': fake_share_network.id,
'subnet_id': 'fake_subnet_id',
}
self.run_command(
'share-network-subnet-show %(share_net_id)s %(subnet_id)s' % args)
self.assert_called(
'GET',
'/share-networks/%(share_net_id)s/subnets/%(subnet_id)s' % args,
)
cliutils.print_dict.assert_called_once_with(mock.ANY)
def test_share_network_subnet_show_invalid_share_network(self):
command = 'share-network-subnet-show %(net_id)s %(subnet_id)s' % {
'net_id': 'not-found-id',
'subnet_id': 1234,
}
self.assertRaises(
exceptions.CommandError,
self.run_command,
command)
@mock.patch.object(cliutils, 'print_list', mock.Mock()) @mock.patch.object(cliutils, 'print_list', mock.Mock())
def test_share_server_list_select_column(self): def test_share_server_list_select_column(self):
self.run_command('share-server-list --columns id,host,status') self.run_command('share-server-list --columns id,host,status')
@ -2848,19 +3062,14 @@ class ShellTest(test_utils.TestCase):
@ddt.data( @ddt.data(
'fake-share-id --az fake-az', 'fake-share-id --az fake-az',
'fake-share-id --availability-zone fake-az --share-network ' 'fake-share-id --availability-zone fake-az',
'fake-network',
) )
@mock.patch.object(shell_v2, '_find_share_network', mock.Mock())
@mock.patch.object(shell_v2, '_find_share', mock.Mock()) @mock.patch.object(shell_v2, '_find_share', mock.Mock())
def test_share_replica_create(self, data): def test_share_replica_create(self, data):
fshare = type('FakeShare', (object,), {'id': 'fake-share-id'}) fshare = type('FakeShare', (object,), {'id': 'fake-share-id'})
shell_v2._find_share.return_value = fshare shell_v2._find_share.return_value = fshare
fnetwork = type('FakeShareNetwork', (object,), {'id': 'fake-network'})
shell_v2._find_share_network.return_value = fnetwork
cmd = 'share-replica-create' + ' ' + data cmd = 'share-replica-create' + ' ' + data
self.run_command(cmd) self.run_command(cmd)

@ -36,6 +36,7 @@ from manilaclient.v2 import share_group_types
from manilaclient.v2 import share_groups from manilaclient.v2 import share_groups
from manilaclient.v2 import share_instance_export_locations from manilaclient.v2 import share_instance_export_locations
from manilaclient.v2 import share_instances from manilaclient.v2 import share_instances
from manilaclient.v2 import share_network_subnets
from manilaclient.v2 import share_networks from manilaclient.v2 import share_networks
from manilaclient.v2 import share_replica_export_locations from manilaclient.v2 import share_replica_export_locations
from manilaclient.v2 import share_replicas from manilaclient.v2 import share_replicas
@ -207,6 +208,8 @@ class Client(object):
self.services = services.ServiceManager(self) self.services = services.ServiceManager(self)
self.security_services = security_services.SecurityServiceManager(self) self.security_services = security_services.SecurityServiceManager(self)
self.share_networks = share_networks.ShareNetworkManager(self) self.share_networks = share_networks.ShareNetworkManager(self)
self.share_network_subnets = (
share_network_subnets.ShareNetworkSubnetManager(self))
self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self)
self.quotas = quotas.QuotaSetManager(self) self.quotas = quotas.QuotaSetManager(self)

@ -0,0 +1,92 @@
# Copyright 2019 NetApp
# All Rights Reserved.
#
# 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.
from manilaclient import base
from manilaclient.common.apiclient import base as common_base
RESOURCES_PATH = '/share-networks/%(share_network_id)s/subnets'
RESOURCE_PATH = RESOURCES_PATH + '/%(share_network_subnet_id)s'
RESOURCE_NAME = 'share_network_subnet'
class ShareNetworkSubnet(common_base.Resource):
"""Network subnet info for Manila share networks."""
def __repr__(self):
return "<ShareNetworkSubnet: %s>" % self.id
def __getitem__(self, key):
return self._info[key]
def delete(self):
"""Delete this share network subnet."""
self.manager.delete(self)
class ShareNetworkSubnetManager(base.ManagerWithFind):
"""Manage :class:`ShareNetworkSubnet` resources."""
resource_class = ShareNetworkSubnet
def create(self, neutron_net_id=None, neutron_subnet_id=None,
availability_zone=None, share_network_id=None):
"""Create share network subnet.
:param neutron_net_id: ID of Neutron network
:param neutron_subnet_id: ID of Neutron subnet
:param availability_zone: Name of the target availability zone
:rtype: :class:`ShareNetworkSubnet`
"""
values = {}
if neutron_net_id:
values['neutron_net_id'] = neutron_net_id
if neutron_subnet_id:
values['neutron_subnet_id'] = neutron_subnet_id
if availability_zone:
values['availability_zone'] = availability_zone
body = {'share-network-subnet': values}
url = '/share-networks/%(share_network_id)s/subnets' % {
'share_network_id': share_network_id
}
return self._create(url, body, RESOURCE_NAME)
def get(self, share_network, share_network_subnet):
"""Get a share network subnet.
:param policy: share network subnet to get.
:rtype: :class:`NetworkSubnetInfo`
"""
share_network_id = common_base.getid(share_network)
share_network_subnet_id = common_base.getid(share_network_subnet)
url = ('/share-networks/%(share_network_id)s/subnets'
'/%(share_network_subnet)s') % {
'share_network_id': share_network_id,
'share_network_subnet': share_network_subnet_id
}
return self._get(url, "share_network_subnet")
def delete(self, share_network, share_network_subnet):
"""Delete a share network subnet.
:param share_network: share network that owns the subnet.
:param share_network_subnet: share network subnet to be deleted.
"""
url = ('/share-networks/%(share_network_id)s/subnets'
'/%(share_network_subnet)s') % {
'share_network_id': common_base.getid(share_network),
'share_network_subnet': share_network_subnet
}
self._delete(url)

@ -70,7 +70,7 @@ class ShareNetworkManager(base.ManagerWithFind):
return self._create(RESOURCES_PATH, body, RESOURCE_NAME) return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
@api_versions.wraps("2.26") # noqa @api_versions.wraps("2.26", "2.50") # noqa
def create(self, neutron_net_id=None, neutron_subnet_id=None, def create(self, neutron_net_id=None, neutron_subnet_id=None,
name=None, description=None): name=None, description=None):
"""Create share network. """Create share network.
@ -95,6 +95,26 @@ class ShareNetworkManager(base.ManagerWithFind):
return self._create(RESOURCES_PATH, body, RESOURCE_NAME) return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
@api_versions.wraps("2.51") # noqa
def create(self, neutron_net_id=None, neutron_subnet_id=None,
name=None, description=None, availability_zone=None):
values = {}
if neutron_net_id:
values['neutron_net_id'] = neutron_net_id
if neutron_subnet_id:
values['neutron_subnet_id'] = neutron_subnet_id
if name:
values['name'] = name
if description:
values['description'] = description
if availability_zone:
values['availability_zone'] = availability_zone
body = {RESOURCE_NAME: values}
return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
def add_security_service(self, share_network, security_service): def add_security_service(self, share_network, security_service):
"""Associate given security service with a share network. """Associate given security service with a share network.

@ -89,13 +89,12 @@ class ShareReplicaManager(base.ManagerWithFind):
@api_versions.wraps("2.11") @api_versions.wraps("2.11")
@api_versions.experimental_api @api_versions.experimental_api
def create(self, share, availability_zone=None, share_network=None): def create(self, share, availability_zone=None):
"""Create a replica for a share. """Create a replica for a share.
:param share: The share to create the replica of. Can be the share :param share: The share to create the replica of. Can be the share
object or its UUID. object or its UUID.
:param availability_zone: The 'availability_zone' object or its UUID. :param availability_zone: The 'availability_zone' object or its UUID.
:param share_network: either share network object or its UUID.
""" """
share_id = common_base.getid(share) share_id = common_base.getid(share)
@ -104,9 +103,6 @@ class ShareReplicaManager(base.ManagerWithFind):
if availability_zone: if availability_zone:
body['availability_zone'] = common_base.getid(availability_zone) body['availability_zone'] = common_base.getid(availability_zone)
if share_network:
body['share_network'] = common_base.getid(share_network)
return self._create(RESOURCES_PATH, return self._create(RESOURCES_PATH,
{RESOURCE_NAME: body}, {RESOURCE_NAME: body},
RESOURCE_NAME) RESOURCE_NAME)

@ -97,7 +97,7 @@ class ShareServerManager(base.ManagerWithFind):
query_string = self._build_query_string(search_opts) query_string = self._build_query_string(search_opts)
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME) return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
@api_versions.wraps("2.49") @api_versions.wraps("2.49", "2.50")
def manage(self, host, share_network_id, identifier, driver_options=None): def manage(self, host, share_network_id, identifier, driver_options=None):
driver_options = driver_options or {} driver_options = driver_options or {}
@ -112,6 +112,23 @@ class ShareServerManager(base.ManagerWithFind):
return self._create(resource_path, {'share_server': body}, return self._create(resource_path, {'share_server': body},
'share_server') 'share_server')
@api_versions.wraps("2.51") # noqa
def manage(self, host, share_network_id, identifier,
share_network_subnet_id=None, driver_options=None):
driver_options = driver_options or {}
body = {
'host': host,
'share_network_id': share_network_id,
'identifier': identifier,
'share_network_subnet_id': share_network_subnet_id,
'driver_options': driver_options,
}
resource_path = RESOURCE_PATH % 'manage'
return self._create(resource_path, {'share_server': body},
'share_server')
@api_versions.wraps("2.49") @api_versions.wraps("2.49")
def unmanage(self, share_server, force=False): def unmanage(self, share_server, force=False):
return self._action("unmanage", share_server, {'force': force}) return self._action("unmanage", share_server, {'force': force})

@ -16,6 +16,7 @@
from __future__ import print_function from __future__ import print_function
from operator import xor
import os import os
import sys import sys
import time import time
@ -1178,15 +1179,32 @@ def do_manage(cs, args):
help='One or more driver-specific key=value pairs that may be necessary to' help='One or more driver-specific key=value pairs that may be necessary to'
' manage the share server (Optional, Default=None).', ' manage the share server (Optional, Default=None).',
default=None) default=None)
@cliutils.arg(
'--share-network-subnet', '--share_network_subnet',
type=str,
metavar='<share_network_subnet>',
help="Share network subnet where share server has network allocations in. "
"The default subnet will be used if it's not specified. Available "
"for microversion >= 2.51 (Optional, Default=None).",
default=None)
def do_share_server_manage(cs, args): def do_share_server_manage(cs, args):
"""Manage share server not handled by Manila (Admin only).""" """Manage share server not handled by Manila (Admin only)."""
driver_options = _extract_key_value_options(args, 'driver_options') driver_options = _extract_key_value_options(args, 'driver_options')
share_network = _find_share_network(cs, args.share_network) manage_kwargs = {
'driver_options': driver_options,
}
if cs.api_version < api_versions.APIVersion("2.51"):
if getattr(args, 'share_network_subnet'):
raise exceptions.CommandError(
"Share network subnet option is only available with manila "
"API version >= 2.51")
else:
manage_kwargs['share_network_subnet_id'] = args.share_network_subnet
share_server = cs.share_servers.manage( share_server = cs.share_servers.manage(
args.host, share_network.id, args.identifier, args.host, args.share_network, args.identifier,
driver_options=driver_options) **manage_kwargs)
cliutils.print_dict(share_server._info) cliutils.print_dict(share_server._info)
@ -2693,6 +2711,16 @@ def do_share_network_create(cs, args):
metavar='<description>', metavar='<description>',
default=None, default=None,
help="Share network description.") help="Share network description.")
@cliutils.arg(
'--availability-zone', '--availability_zone', '--az',
metavar='<availability_zone>',
default=None,
action='single_alias',
help="Availability zone in which the subnet should be created. Share "
"networks can have one or more subnets in different availability "
"zones when the driver is operating with "
"'driver_handles_share_servers' extra_spec set to True. Available "
"only for microversion >= 2.51. (Default=None)")
def do_share_network_create(cs, args): def do_share_network_create(cs, args):
"""Create description for network used by the tenant.""" """Create description for network used by the tenant."""
values = { values = {
@ -2701,6 +2729,12 @@ def do_share_network_create(cs, args):
'name': args.name, 'name': args.name,
'description': args.description, 'description': args.description,
} }
if cs.api_version >= api_versions.APIVersion("2.51"):
values['availability_zone'] = args.availability_zone
elif args.availability_zone:
raise exceptions.CommandError(
"Creating share networks with a given az is only "
"available with manila API version >= 2.51")
share_network = cs.share_networks.create(**values) share_network = cs.share_networks.create(**values)
info = share_network._info.copy() info = share_network._info.copy()
cliutils.print_dict(info) cliutils.print_dict(info)
@ -2771,9 +2805,8 @@ def do_share_network_update(cs, args):
metavar='<neutron-net-id>', metavar='<neutron-net-id>',
default=None, default=None,
action='single_alias', action='single_alias',
help="Neutron network ID. Used to set up network for share servers. This " help="Neutron network ID. Used to set up network for share servers. "
"option is deprecated and will be rejected in newer releases of " "This option is deprecated for microversion >= 2.51.")
"OpenStack Manila.")
@cliutils.arg( @cliutils.arg(
'--neutron-subnet-id', '--neutron-subnet-id',
'--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id', '--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
@ -2781,7 +2814,8 @@ def do_share_network_update(cs, args):
default=None, default=None,
action='single_alias', action='single_alias',
help="Neutron subnet ID. Used to set up network for share servers. " help="Neutron subnet ID. Used to set up network for share servers. "
"This subnet should belong to specified neutron network.") "This subnet should belong to specified neutron network. "
"This option is deprecated for microversion >= 2.51.")
@cliutils.arg( @cliutils.arg(
'--name', '--name',
metavar='<name>', metavar='<name>',
@ -2794,6 +2828,7 @@ def do_share_network_update(cs, args):
help="Share network description.") help="Share network description.")
def do_share_network_update(cs, args): def do_share_network_update(cs, args):
"""Update share network data.""" """Update share network data."""
values = { values = {
'neutron_net_id': args.neutron_net_id, 'neutron_net_id': args.neutron_net_id,
'neutron_subnet_id': args.neutron_subnet_id, 'neutron_subnet_id': args.neutron_subnet_id,
@ -3189,6 +3224,100 @@ def do_share_network_security_service_list(cs, args):
cliutils.print_list(security_services, fields=fields) cliutils.print_list(security_services, fields=fields)
@cliutils.arg(
'share_network',
metavar='<share-network>',
help='Share network name or ID.')
@cliutils.arg(
'--neutron-net-id',
'--neutron-net_id', '--neutron_net_id', '--neutron_net-id',
metavar='<neutron-net-id>',
default=None,
action='single_alias',
help="Neutron network ID. Used to set up network for share servers. "
"Optional, Default = None.")
@cliutils.arg(
'--neutron-subnet-id',
'--neutron-subnet_id', '--neutron_subnet_id', '--neutron_subnet-id',
metavar='<neutron-subnet-id>',
default=None,
action='single_alias',
help="Neutron subnet ID. Used to set up network for share servers. "
"This subnet should belong to specified neutron network. "
"Optional, Default = None.")
@cliutils.arg(
'--availability-zone',
'--availability_zone',
'--az',
default=None,
action='single_alias',
metavar='<availability-zone>',
help='Optional availability zone that the subnet is available within '
'(Default=None). If None, the subnet will be considered as being '
'available across all availability zones.')
def do_share_network_subnet_create(cs, args):
"""Add a new subnet into a share network."""
if xor(bool(args.neutron_net_id), bool(args.neutron_subnet_id)):
raise exceptions.CommandError(
"Both neutron_net_id and neutron_subnet_id should be specified. "
"Alternatively, neither of them should be specified.")
share_network = _find_share_network(cs, args.share_network)
values = {
'share_network_id': share_network.id,
'neutron_net_id': args.neutron_net_id,
'neutron_subnet_id': args.neutron_subnet_id,
'availability_zone': args.availability_zone,
}
share_network_subnet = cs.share_network_subnets.create(**values)
info = share_network_subnet._info.copy()
cliutils.print_dict(info)
@cliutils.arg(
'share_network',
metavar='<share-network>',
help='Share network name or ID.')
@cliutils.arg(
'share_network_subnet',
metavar='<share-network-subnet>',
nargs='+',
help='Name or ID of share network subnet(s) to be deleted.')
def do_share_network_subnet_delete(cs, args):
"""Delete one or more share network subnets."""
failure_count = 0
share_network_ref = _find_share_network(cs, args.share_network)
for subnet in args.share_network_subnet:
try:
cs.share_network_subnets.delete(share_network_ref, subnet)
except Exception as e:
failure_count += 1
print("Deletion of share network subnet %s failed: %s" % (
subnet, e), file=sys.stderr)
if failure_count == len(args.share_network_subnet):
raise exceptions.CommandError("Unable to delete any of the specified "
"share network subnets.")
@cliutils.arg(
'share_network',
metavar='<share-network>',
help='Name or ID of share network(s) to which the subnet belongs.')
@cliutils.arg(
'share_network_subnet',
metavar='<share-network-subnet>',
help='Share network subnet ID to show.')
def do_share_network_subnet_show(cs, args):
"""Show share network subnet."""
share_network = _find_share_network(cs, args.share_network)
share_network_subnet = cs.share_network_subnets.get(
share_network.id, args.share_network_subnet)
view_data = share_network_subnet._info.copy()
cliutils.print_dict(view_data)
@cliutils.arg( @cliutils.arg(
'share_network', 'share_network',
metavar='<share-network>', metavar='<share-network>',
@ -3538,6 +3667,14 @@ def do_security_service_delete(cs, args):
default=None, default=None,
help='Comma separated list of columns to be displayed ' help='Comma separated list of columns to be displayed '
'example --columns "id,host,status".') 'example --columns "id,host,status".')
@cliutils.arg(
'--share-network-subnet', '--share_network_subnet',
type=str,
metavar='<share_network_subnet>',
help="Filter results by share network subnet that the share server's "
"network allocation exists whithin. Available for micro version "
">= 2.51 (Optional, Default=None).",
default=None)
def do_share_server_list(cs, args): def do_share_server_list(cs, args):
"""List all share servers (Admin only).""" """List all share servers (Admin only)."""
search_opts = { search_opts = {
@ -3555,6 +3692,15 @@ def do_share_server_list(cs, args):
"Updated_at", "Updated_at",
] ]
if cs.api_version < api_versions.APIVersion("2.51"):
if getattr(args, 'share_network_subnet'):
raise exceptions.CommandError(
"Share network subnet option is only available with manila "
"API version >= 2.51")
else:
search_opts.update({'share_network_subnet': args.share_network_subnet})
fields.append("Share Network Subnet Id")
if args.columns is not None: if args.columns is not None:
fields = _split_columns(columns=args.columns) fields = _split_columns(columns=args.columns)
@ -5197,25 +5343,12 @@ def do_share_replica_list(cs, args):
action='single_alias', action='single_alias',
metavar='<availability-zone>', metavar='<availability-zone>',
help='Optional Availability zone in which replica should be created.') help='Optional Availability zone in which replica should be created.')
@cliutils.arg(
'--share-network',
'--share_network',
metavar='<network-info>',
default=None,
action='single_alias',
help='Optional network info ID or name.')
@api_versions.wraps("2.11") @api_versions.wraps("2.11")
def do_share_replica_create(cs, args): def do_share_replica_create(cs, args):
"""Create a share replica (Experimental).""" """Create a share replica (Experimental)."""
share = _find_share(cs, args.share) share = _find_share(cs, args.share)
share_network = None replica = cs.share_replicas.create(share, args.availability_zone)
if args.share_network:
share_network = _find_share_network(cs, args.share_network)
replica = cs.share_replicas.create(share,
args.availability_zone,
share_network)
_print_share_replica(cs, replica) _print_share_replica(cs, replica)

@ -0,0 +1,13 @@
---
features:
- Added CLI commands to get, add and delete share network subnets.
- Updated CLI command for managing share servers to accept
``share_network_subnet`` parameter.
- Deprecated ``neutron_subnet_id`` parameter from CLI command to update a
share network.
- Updated CLI command for listing share servers to show a new column
``Share Network Subnet Id``, and to accept a filter parameter
``share_network_subnet``.
fixes:
- Fixed share replica create API to make the replica inherit parent share's
share network.