RBAC Support for OSMCLIENT 89/5489/2
authorAnurag Dwivedi <anurag.dwivedi@riftio.com>
Mon, 2 Oct 2017 14:15:31 +0000 (10:15 -0400)
committerMike Marchetti <mmarchetti@sandvine.com>
Mon, 2 Oct 2017 15:01:27 +0000 (11:01 -0400)
Signed-off-by: Anurag Dwivedi <anurag.dwivedi@riftio.com>
Change-Id: Iaefb4aa98d66b15e69f7fba399bf0cded82e6610

12 files changed:
osmclient/scripts/osm.py
osmclient/v1/client.py
osmclient/v1/ns.py
osmclient/v1/nsd.py
osmclient/v1/tests/test_ns.py
osmclient/v1/tests/test_nsd.py
osmclient/v1/tests/test_vnf.py
osmclient/v1/tests/test_vnfd.py
osmclient/v1/vca.py
osmclient/v1/vim.py
osmclient/v1/vnf.py
osmclient/v1/vnfd.py

index dee931f..5d8b2b3 100755 (executable)
@@ -36,6 +36,11 @@ import time
               envvar='OSM_SO_PORT',
               help='hostname of server.  ' +
                    'Also can set OSM_SO_PORT in environment')
+@click.option('--so-project',
+              default='default',
+              envvar='OSM_SO_PROJECT',
+              help='Project Name in SO.  ' +
+                   'Also can set OSM_SO_PROJECT in environment')
 @click.option('--ro-hostname',
               default=None,
               envvar='OSM_RO_HOSTNAME',
@@ -47,7 +52,7 @@ import time
               help='hostname of RO server.  ' +
                    'Also can set OSM_RO_PORT in environment')
 @click.pass_context
-def cli(ctx, hostname, so_port, ro_hostname, ro_port):
+def cli(ctx, hostname, so_port, so_project, ro_hostname, ro_port):
     if hostname is None:
         print(
             "either hostname option or OSM_HOSTNAME " +
@@ -56,6 +61,7 @@ def cli(ctx, hostname, so_port, ro_hostname, ro_port):
     ctx.obj = client.Client(
         host=hostname,
         so_port=so_port,
+        so_project=so_project,
         ro_host=ro_hostname,
         ro_port=ro_port)
 
index cfaf15a..7a332eb 100644 (file)
@@ -35,6 +35,7 @@ class Client(object):
         self,
         host=None,
         so_port=8008,
+        so_project='default',
         ro_host=None,
         ro_port=9090,
         upload_port=8443,
@@ -50,6 +51,8 @@ class Client(object):
             self._host = host
             self._so_port = so_port
 
+        self._so_project = so_project
+
         http_client = http.Http(
             'https://{}:{}/'.format(
                 self._host,
@@ -58,6 +61,8 @@ class Client(object):
             ['Accept: application/vnd.yand.data+json',
              'Content-Type: application/json'])
 
+        self._so_version = self.get_so_version(http_client)
+
         if ro_host is None:
             ro_host = host
         ro_http_client = http.Http('http://{}:{}/'.format(ro_host, ro_port))
@@ -65,17 +70,26 @@ class Client(object):
             ['Accept: application/vnd.yand.data+json',
              'Content-Type: application/json'])
 
-        upload_client = http.Http(
-            'https://{}:{}/composer/upload?api_server={}{}'.format(
+        upload_client_url = 'https://{}:{}/composer/upload?api_server={}{}'.format(
                 self._host,
                 upload_port,
                 'https://localhost&upload_server=https://',
-                self._host))
+                self._host)
+
+        if self._so_version == 'v3':
+            upload_client_url = 'https://{}:{}/composer/upload?api_server={}{}&project_name={}'.format(
+                self._host,
+                upload_port,
+                'https://localhost&upload_server=https://',
+                self._host,
+                self._so_project)
 
-        self.vnf = vnf.Vnf(http_client, **kwargs)
-        self.vnfd = vnfd.Vnfd(http_client, **kwargs)
+        upload_client = http.Http(upload_client_url)
+
+        self.vnf = vnf.Vnf(http_client, client=self, **kwargs)
+        self.vnfd = vnfd.Vnfd(http_client, client=self, **kwargs)
         self.ns = ns.Ns(http=http_client, client=self, **kwargs)
-        self.nsd = nsd.Nsd(http_client, **kwargs)
+        self.nsd = nsd.Nsd(http_client, client=self, **kwargs)
         self.vim = vim.Vim(
             http=http_client,
             ro_http=ro_http_client,
@@ -86,5 +100,27 @@ class Client(object):
             upload_http=upload_client,
             client=self,
             **kwargs)
-        self.vca = vca.Vca(http_client, **kwargs)
+        self.vca = vca.Vca(http_client, client=self, **kwargs)
         self.utils = utils.Utils(http_client, **kwargs)
+
+    @property
+    def so_rbac_project_path(self):
+        if self._so_version == 'v3':
+            return 'project/{}/'.format(self._so_project)
+        else:
+            return ''
+
+    def get_so_version(self, http_client):
+        try:
+            resp = http_client.get_cmd('api/operational/version')
+            if not resp or 'rw-base:version' not in resp:
+                return 'v2'
+
+            if resp['rw-base:version']['version'].split('.')[0] == '5':
+                # SO Version 5.x.x.x.x translates to OSM V3
+                return 'v3'
+            return 'v2'
+        except Exception as e:
+            return 'v2'
+
+
index fc3fe06..fe62e3d 100644 (file)
@@ -33,8 +33,8 @@ class Ns(object):
     def list(self):
         """Returns a list of ns's
         """
-        resp = self._http.get_cmd('api/running/ns-instance-config')
-
+        resp = self._http.get_cmd('api/running/{}ns-instance-config'
+                .format(self._client.so_rbac_project_path))
         if not resp or 'nsr:ns-instance-config' not in resp:
             return list()
 
@@ -60,8 +60,8 @@ class Ns(object):
 
         ns = self.get(ns_name)
         resp = self._http.post_cmd(
-            'v1/api/config/ns-instance-config/nsr/{}/scaling-group/{}/instance'
-            .format(ns['id'], ns_scale_group), postdata)
+            'v1/api/config/{}ns-instance-config/nsr/{}/scaling-group/{}/instance'
+            .format(self._client.so_rbac_project_path, ns['id'], ns_scale_group), postdata)
         if 'success' not in resp:
             raise ClientException(
                 "failed to scale ns: {} result: {}".format(
@@ -77,17 +77,32 @@ class Ns(object):
         nsr['id'] = str(uuid.uuid1())
 
         nsd = self._client.nsd.get(nsd_name)
-
-        datacenter = self._client.vim.get_datacenter(account)
-        if datacenter is None:
-            raise NotFound("cannot find datacenter account {}".format(account))
+        
+        if self._client._so_version == 'v3':
+            datacenter, resource_orchestrator = self._client.vim.get_datacenter(account)
+            if datacenter is None or resource_orchestrator is None:
+                raise NotFound("cannot find datacenter account {}".format(account))
+            if 'uuid' not in datacenter:
+                raise NotFound("The RO Datacenter - {} is invalid. Please select another".format(account))
+        else:
+            # Backwards Compatiility
+            datacenter = self._client.vim.get_datacenter(account)
+            if datacenter is None:
+                raise NotFound("cannot find datacenter account {}".format(account))
 
         nsr['nsd'] = nsd
         nsr['name'] = nsr_name
         nsr['short-name'] = nsr_name
         nsr['description'] = description
         nsr['admin-status'] = admin_status
-        nsr['om-datacenter'] = datacenter['uuid']
+        
+        if self._client._so_version == 'v3':
+            # New format for V3
+            nsr['resource-orchestrator'] = resource_orchestrator
+            nsr['datacenter'] = datacenter['name']
+        else:
+            # Backwards Compatiility
+            nsr['om-datacenter'] = datacenter['uuid']
 
         if ssh_keys is not None:
             # ssh_keys is comma separate list
@@ -106,7 +121,8 @@ class Ns(object):
         postdata['nsr'].append(nsr)
 
         resp = self._http.post_cmd(
-            'api/config/ns-instance-config/nsr',
+            'api/config/{}ns-instance-config/nsr'
+            .format(self._client.so_rbac_project_path),
             postdata)
 
         if 'success' not in resp:
@@ -118,7 +134,8 @@ class Ns(object):
 
     def get_opdata(self, id):
         return self._http.get_cmd(
-              'api/operational/ns-instance-opdata/nsr/{}?deep'.format(id))
+              'api/operational/{}ns-instance-opdata/nsr/{}?deep'
+              .format(self._client.so_rbac_project_path, id))
 
     def get_field(self, ns_name, field):
         nsr = self.get(ns_name)
@@ -140,8 +157,8 @@ class Ns(object):
         if ns is None:
             raise NotFound("cannot find ns {}".format(ns_name))
 
-        return self._http.delete_cmd('api/config/ns-instance-config/nsr/' +
-                                     ns['id'])
+        return self._http.delete_cmd('api/config/{}ns-instance-config/nsr/{}'
+                    .format(self._client.so_rbac_project_path, ns['id']))
 
     def delete(self, ns_name, wait=True):
         vnfs = self.get_field(ns_name, 'constituent-vnfr-ref')
index f2b74f1..52c5772 100644 (file)
@@ -24,13 +24,22 @@ from osmclient.common.exceptions import ClientException
 
 class Nsd(object):
 
-    def __init__(self, http=None):
+    def __init__(self, http=None, client=None):
         self._http = http
+        self._client = client
 
     def list(self):
-        resp = self._http.get_cmd('api/running/nsd-catalog/nsd')
-        if resp and 'nsd:nsd' in resp:
-            return resp['nsd:nsd']
+        resp = self._http.get_cmd('api/running/{}nsd-catalog/nsd'
+                .format(self._client.so_rbac_project_path))
+        
+        if self._client._so_version == 'v3':
+            if resp and 'project-nsd:nsd' in resp:
+                return resp['project-nsd:nsd']
+        else:
+            # Backwards Compatibility
+            if resp and 'nsd:nsd' in resp:
+                return resp['nsd:nsd']
+
         return list()
 
     def get(self, name):
@@ -45,6 +54,7 @@ class Nsd(object):
             raise NotFound("cannot find nsd {}".format(nsd_name))
 
         resp = self._http.delete_cmd(
-            'api/running/nsd-catalog/nsd/{}'.format(nsd['id']))
+            'api/running/{}nsd-catalog/nsd/{}'
+            .format(self._client.so_rbac_project_path, nsd['id']))
         if 'success' not in resp:
             raise ClientException("failed to delete nsd {}".format(nsd_name))
index 66361e8..19fc58f 100644 (file)
 import unittest
 from mock import Mock
 from osmclient.v1 import ns
+from osmclient.v1 import client
 from osmclient.common.exceptions import NotFound
 
 
-class TestNs(unittest.TestCase):
-
+class TestNs(unittest.TestCase): 
     def test_list_empty(self):
         mock = Mock()
         mock.get_cmd.return_value = list()
-        assert len(ns.Ns(mock).list()) == 0
+        assert len(ns.Ns(mock, client=client.Client(host='127.0.0.1')).list()) == 0
 
     def test_get_notfound(self):
         mock = Mock()
         mock.get_cmd.return_value = 'foo'
-        self.assertRaises(NotFound, ns.Ns(mock).get, 'bar')
+        self.assertRaises(NotFound, ns.Ns(mock, client=client.Client(host='127.0.0.1')).get, 'bar')
 
     def test_get_found(self):
         mock = Mock()
         mock.get_cmd.return_value = {'nsr:ns-instance-config':
                                      {'nsr': [{'name': 'foo'}]}}
-        assert ns.Ns(mock).get('foo')
+        assert ns.Ns(mock, client=client.Client(host='127.0.0.1')).get('foo')
 
     def test_get_monitoring_notfound(self):
         mock = Mock()
         mock.get_cmd.return_value = 'foo'
-        self.assertRaises(NotFound, ns.Ns(mock).get_monitoring, 'bar')
+        self.assertRaises(NotFound, ns.Ns(mock, client=client.Client(host='127.0.0.1')).get_monitoring, 'bar')
index 175ebf0..b0b5492 100644 (file)
 import unittest
 from mock import Mock
 from osmclient.v1 import nsd
+from osmclient.v1 import client
 from osmclient.common.exceptions import NotFound
 
 
 class TestNsd(unittest.TestCase):
-
     def test_list_empty(self):
         mock = Mock()
         mock.get_cmd.return_value = list()
-        assert len(nsd.Nsd(mock).list()) == 0
+        assert len(nsd.Nsd(mock, client=client.Client(host='127.0.0.1')).list()) == 0
 
     def test_get_notfound(self):
         mock = Mock()
         mock.get_cmd.return_value = 'foo'
-        self.assertRaises(NotFound, nsd.Nsd(mock).get, 'bar')
+        self.assertRaises(NotFound, nsd.Nsd(mock, client=client.Client(host='127.0.0.1')).get, 'bar')
 
     def test_get_found(self):
         mock = Mock()
-        mock.get_cmd.return_value = {'nsd:nsd': [{'name': 'foo'}]}
-        assert nsd.Nsd(mock).get('foo')
+        if client.Client(host='127.0.0.1')._so_version == 'v3':
+            mock.get_cmd.return_value = {'project-nsd:nsd': [{'name': 'foo'}]}
+        else:
+            # Backwards Compatibility
+            mock.get_cmd.return_value = {'nsd:nsd': [{'name': 'foo'}]}
+        assert nsd.Nsd(mock, client=client.Client(host='127.0.0.1')).get('foo')
index da54b0f..69aca68 100644 (file)
 import unittest
 from mock import Mock
 from osmclient.v1 import vnf
+from osmclient.v1 import client
 from osmclient.common.exceptions import NotFound
 
 
 class TestVnf(unittest.TestCase):
-
     def test_list_empty(self):
         mock = Mock()
         mock.get_cmd.return_value = list()
-        assert len(vnf.Vnf(mock).list()) == 0
+        assert len(vnf.Vnf(mock, client=client.Client(host='127.0.0.1')).list()) == 0
 
     def test_get_notfound(self):
         mock = Mock()
         mock.get_cmd.return_value = 'foo'
-        self.assertRaises(NotFound, vnf.Vnf(mock).get, 'bar')
+        self.assertRaises(NotFound, vnf.Vnf(mock, client=client.Client(host='127.0.0.1')).get, 'bar')
 
     def test_get_found(self):
         mock = Mock()
         mock.get_cmd.return_value = {'vnfr:vnfr': [{'name': 'foo'}]}
-        assert vnf.Vnf(mock).get('foo')
+        assert vnf.Vnf(mock, client=client.Client(host='127.0.0.1')).get('foo')
 
     def test_get_monitoring_notfound(self):
         mock = Mock()
         mock.get_cmd.return_value = 'foo'
-        self.assertRaises(NotFound, vnf.Vnf(mock).get_monitoring, 'bar')
+        self.assertRaises(NotFound, vnf.Vnf(mock, client=client.Client(host='127.0.0.1')).get_monitoring, 'bar')
 
     def test_get_monitoring_found(self):
         mock = Mock()
         mock.get_cmd.return_value = {'vnfr:vnfr': [{'name': 'foo',
                                                     'monitoring-param': True}]}
-        assert vnf.Vnf(mock).get_monitoring('foo')
+        assert vnf.Vnf(mock, client=client.Client(host='127.0.0.1')).get_monitoring('foo')
index 6ec1031..cd98888 100644 (file)
 import unittest
 from mock import Mock
 from osmclient.v1 import vnfd
+from osmclient.v1 import client
 from osmclient.common.exceptions import NotFound
 
 
 class TestVnfd(unittest.TestCase):
-
     def test_list_empty(self):
         mock = Mock()
         mock.get_cmd.return_value = list()
-        assert len(vnfd.Vnfd(mock).list()) == 0
+        assert len(vnfd.Vnfd(mock, client=client.Client(host='127.0.0.1')).list()) == 0
 
     def test_get_notfound(self):
         mock = Mock()
         mock.get_cmd.return_value = 'foo'
-        self.assertRaises(NotFound, vnfd.Vnfd(mock).get, 'bar')
+        self.assertRaises(NotFound, vnfd.Vnfd(mock, client=client.Client(host='127.0.0.1')).get, 'bar')
 
     def test_get_found(self):
         mock = Mock()
-        mock.get_cmd.return_value = {'vnfd:vnfd': [{'name': 'foo'}]}
-        assert vnfd.Vnfd(mock).get('foo')
+        if client.Client(host='127.0.0.1')._so_version == 'v3':
+            mock.get_cmd.return_value = {'project-vnfd:vnfd': [{'name': 'foo'}]}
+        else:
+            # Backwards Compatibility
+            mock.get_cmd.return_value = {'vnfd:vnfd': [{'name': 'foo'}]}
+
+        assert vnfd.Vnfd(mock, client=client.Client(host='127.0.0.1')).get('foo')
index b6b4771..adac986 100644 (file)
@@ -23,19 +23,21 @@ from osmclient.common.exceptions import ClientException
 
 class Vca(object):
 
-    def __init__(self, http=None):
+    def __init__(self, http=None, client=None):
         self._http = http
+        self._client = client
 
     def list(self):
-        resp = self._http.get_cmd('api/config/config-agent')
+        resp = self._http.get_cmd('api/config/{}config-agent'
+                .format(self._client.so_rbac_project_path))
         if resp and 'rw-config-agent:config-agent' in resp:
             return resp['rw-config-agent:config-agent']['account']
         return list()
 
     def delete(self, name):
         if ('success' not in
-            self._http.delete_cmd('api/config/config-agent/account/{}'
-                                  .format(name))):
+            self._http.delete_cmd('api/config/{}config-agent/account/{}'
+                                  .format(self._client.so_rbac_project_path, name))):
             raise ClientException("failed to delete config agent {}"
                                   .format(name))
 
@@ -52,7 +54,7 @@ class Vca(object):
         account['juju']['ip-address'] = server
         postdata['account'].append(account)
 
-        if 'success' not in self._http.post_cmd('api/config/config-agent',
-                                                postdata):
+        if 'success' not in self._http.post_cmd('api/config/{}config-agent'
+                                .format(self._client.so_rbac_project_path), postdata):
             raise ClientException("failed to create config agent {}"
                                   .format(name))
index f51f192..334e89e 100644 (file)
@@ -130,28 +130,45 @@ class Vim(object):
             raise ClientException("failed to delete vim {}".format(vim_name))
 
     def list(self):
-        resp = self._http.get_cmd('v1/api/operational/datacenters')
-        if not resp or 'rw-launchpad:datacenters' not in resp:
-            return list()
-
-        datacenters = resp['rw-launchpad:datacenters']
-
-        vim_accounts = list()
-        if 'ro-accounts' not in datacenters:
+        if self._client._so_version == 'v3':
+            resp = self._http.get_cmd('v1/api/operational/{}ro-account-state'
+                    .format(self._client.so_rbac_project_path))
+            datacenters = []
+            if not resp or 'rw-ro-account:ro-account-state' not in resp:
+                return list()
+
+            ro_accounts = resp['rw-ro-account:ro-account-state']
+            for ro_account in ro_accounts['account']:
+                for datacenter in ro_account['datacenters']['datacenters']:
+                    datacenters.append({"name": datacenter['name'], "uuid": datacenter['uuid']
+                        if 'uuid' in datacenter else None}) 
+
+            vim_accounts = datacenters
             return vim_accounts
-
-        tenant = self._get_ro_tenant()
-        if tenant is None:
+        else:
+            # Backwards Compatibility
+            resp = self._http.get_cmd('v1/api/operational/datacenters')
+            if not resp or 'rw-launchpad:datacenters' not in resp:
+                return list()
+            datacenters = resp['rw-launchpad:datacenters']
+            vim_accounts = list()
+            if 'ro-accounts' not in datacenters:
+                return vim_accounts
+            tenant = self._get_ro_tenant()
+            if tenant is None:
+                return vim_accounts
+            for roaccount in datacenters['ro-accounts']:
+                if 'datacenters' not in roaccount:
+                    continue
+                for datacenter in roaccount['datacenters']:
+                    vim_accounts.append(self._get_ro_datacenter(datacenter['name'],
+                                                              tenant['uuid']))
             return vim_accounts
 
-        for roaccount in datacenters['ro-accounts']:
-            if 'datacenters' not in roaccount:
-                continue
-            for datacenter in roaccount['datacenters']:
-                vim_accounts.append(self._get_ro_datacenter(datacenter['name'],
-                                                            tenant['uuid']))
-        return vim_accounts
-
     def _get_ro_tenant(self, name='osm'):
         resp = self._ro_http.get_cmd('openmano/tenants/{}'.format(name))
 
@@ -182,24 +199,43 @@ class Vim(object):
         return self._get_ro_datacenter(name, tenant['uuid'])
 
     def get_datacenter(self, name):
-        resp = self._http.get_cmd('v1/api/operational/datacenters')
-        if not resp:
-            return None
-
-        if not resp or 'rw-launchpad:datacenters' not in resp:
-            return None
-        if 'ro-accounts' not in resp['rw-launchpad:datacenters']:
+        if self._client._so_version == 'v3':
+            resp = self._http.get_cmd('v1/api/operational/{}ro-account-state'
+                    .format(self._client.so_rbac_project_path))
+            if not resp:
+                return None, None
+
+            if not resp or 'rw-ro-account:ro-account-state' not in resp:
+                return None, None
+
+            ro_accounts = resp['rw-ro-account:ro-account-state']
+            for ro_account in ro_accounts['account']:
+                for datacenter in ro_account['datacenters']['datacenters']:
+                    if datacenter['name'] == name:
+                        return datacenter, ro_account['name']        
+            return None, None
+        else:
+            # Backwards Compatibility
+            resp = self._http.get_cmd('v1/api/operational/datacenters')
+            if not resp:
+                return None
+            if not resp or 'rw-launchpad:datacenters' not in resp:
+                return None
+            if 'ro-accounts' not in resp['rw-launchpad:datacenters']:
+                return None
+            for roaccount in resp['rw-launchpad:datacenters']['ro-accounts']:
+                if 'datacenters' not in roaccount:
+                    continue
+                for datacenter in roaccount['datacenters']:
+                    if datacenter['name'] == name:
+                        return datacenter
             return None
-        for roaccount in resp['rw-launchpad:datacenters']['ro-accounts']:
-            if 'datacenters' not in roaccount:
-                continue
-            for datacenter in roaccount['datacenters']:
-                if datacenter['name'] == name:
-                    return datacenter
-        return None
 
     def get_resource_orchestrator(self):
-        resp = self._http.get_cmd('v1/api/operational/resource-orchestrator')
+        resp = self._http.get_cmd('v1/api/operational/{}resource-orchestrator'
+                .format(self._client.so_rbac_project_path))
+
         if not resp or 'rw-launchpad:resource-orchestrator' not in resp:
             return None
         return resp['rw-launchpad:resource-orchestrator']
index 856d58b..46ff3af 100644 (file)
@@ -22,11 +22,13 @@ from osmclient.common.exceptions import NotFound
 
 
 class Vnf(object):
-    def __init__(self, http=None):
+    def __init__(self, http=None, client=None):
         self._http = http
+        self._client = client
 
     def list(self):
-        resp = self._http.get_cmd('v1/api/operational/vnfr-catalog/vnfr')
+        resp = self._http.get_cmd('v1/api/operational/{}vnfr-catalog/vnfr'
+                .format(self._client.so_rbac_project_path))
         if resp and 'vnfr:vnfr' in resp:
             return resp['vnfr:vnfr']
         return list()
index 28a8f92..dd3b117 100644 (file)
@@ -24,13 +24,22 @@ from osmclient.common.exceptions import ClientException
 
 class Vnfd(object):
 
-    def __init__(self, http=None):
+    def __init__(self, http=None, client=None):
         self._http = http
+        self._client = client
 
     def list(self):
-        resp = self._http.get_cmd('api/running/vnfd-catalog/vnfd')
-        if resp and 'vnfd:vnfd' in resp:
-            return resp['vnfd:vnfd']
+        resp = self._http.get_cmd('api/running/{}vnfd-catalog/vnfd'
+                .format(self._client.so_rbac_project_path))
+
+        if self._client._so_version == 'v3':
+            if resp and 'project-vnfd:vnfd' in resp:
+                return resp['project-vnfd:vnfd']
+        else:
+            # Backwards Compatibility
+            if resp and 'vnfd:vnfd' in resp:
+                return resp['vnfd:vnfd']
+                
         return list()
 
     def get(self, name):
@@ -41,7 +50,7 @@ class Vnfd(object):
 
     def delete(self, vnfd_name):
         vnfd = self.get(vnfd_name)
-        resp = self._http.delete_cmd('api/running/vnfd-catalog/vnfd/{}'
-                                     .format(vnfd['id']))
+        resp = self._http.delete_cmd('api/running/{}vnfd-catalog/vnfd/{}'
+                                     .format(self._client.so_rbac_project_path, vnfd['id']))
         if 'success' not in resp:
             raise ClientException("failed to delete vnfd {}".format(vnfd_name))