From: Anurag Dwivedi Date: Mon, 2 Oct 2017 14:15:31 +0000 (-0400) Subject: RBAC Support for OSMCLIENT X-Git-Tag: v3.0.0rc2 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=8ba7b704251392e23eb01bdee505c058c62f2887;p=osm%2Fosmclient.git RBAC Support for OSMCLIENT Signed-off-by: Anurag Dwivedi Change-Id: Iaefb4aa98d66b15e69f7fba399bf0cded82e6610 --- diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index dee931f..5d8b2b3 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -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) diff --git a/osmclient/v1/client.py b/osmclient/v1/client.py index cfaf15a..7a332eb 100644 --- a/osmclient/v1/client.py +++ b/osmclient/v1/client.py @@ -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' + + diff --git a/osmclient/v1/ns.py b/osmclient/v1/ns.py index fc3fe06..fe62e3d 100644 --- a/osmclient/v1/ns.py +++ b/osmclient/v1/ns.py @@ -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') diff --git a/osmclient/v1/nsd.py b/osmclient/v1/nsd.py index f2b74f1..52c5772 100644 --- a/osmclient/v1/nsd.py +++ b/osmclient/v1/nsd.py @@ -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)) diff --git a/osmclient/v1/tests/test_ns.py b/osmclient/v1/tests/test_ns.py index 66361e8..19fc58f 100644 --- a/osmclient/v1/tests/test_ns.py +++ b/osmclient/v1/tests/test_ns.py @@ -17,28 +17,28 @@ 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') diff --git a/osmclient/v1/tests/test_nsd.py b/osmclient/v1/tests/test_nsd.py index 175ebf0..b0b5492 100644 --- a/osmclient/v1/tests/test_nsd.py +++ b/osmclient/v1/tests/test_nsd.py @@ -17,22 +17,26 @@ 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') diff --git a/osmclient/v1/tests/test_vnf.py b/osmclient/v1/tests/test_vnf.py index da54b0f..69aca68 100644 --- a/osmclient/v1/tests/test_vnf.py +++ b/osmclient/v1/tests/test_vnf.py @@ -17,33 +17,33 @@ 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') diff --git a/osmclient/v1/tests/test_vnfd.py b/osmclient/v1/tests/test_vnfd.py index 6ec1031..cd98888 100644 --- a/osmclient/v1/tests/test_vnfd.py +++ b/osmclient/v1/tests/test_vnfd.py @@ -17,22 +17,27 @@ 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') diff --git a/osmclient/v1/vca.py b/osmclient/v1/vca.py index b6b4771..adac986 100644 --- a/osmclient/v1/vca.py +++ b/osmclient/v1/vca.py @@ -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)) diff --git a/osmclient/v1/vim.py b/osmclient/v1/vim.py index f51f192..334e89e 100644 --- a/osmclient/v1/vim.py +++ b/osmclient/v1/vim.py @@ -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'] diff --git a/osmclient/v1/vnf.py b/osmclient/v1/vnf.py index 856d58b..46ff3af 100644 --- a/osmclient/v1/vnf.py +++ b/osmclient/v1/vnf.py @@ -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() diff --git a/osmclient/v1/vnfd.py b/osmclient/v1/vnfd.py index 28a8f92..dd3b117 100644 --- a/osmclient/v1/vnfd.py +++ b/osmclient/v1/vnfd.py @@ -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))