Support of user and project mgmt in sol005 client 35/6235/21
authorgarciadeblas <gerardo.garciadeblas@telefonica.com>
Tue, 29 May 2018 12:15:43 +0000 (14:15 +0200)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Thu, 29 Nov 2018 13:48:27 +0000 (14:48 +0100)
Change-Id: I5880e0cb1efecdbed8c5c3ab800e5e2ed6cda517
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
osmclient/scripts/osm.py
osmclient/sol005/client.py
osmclient/sol005/project.py [new file with mode: 0644]
osmclient/sol005/sdncontroller.py
osmclient/sol005/user.py [new file with mode: 0644]

index c80c3eb..c07b62d 100755 (executable)
@@ -50,6 +50,26 @@ def check_client_version(obj, what, version='sol005'):
               envvar='OSM_HOSTNAME',
               help='hostname of server.  ' +
                    'Also can set OSM_HOSTNAME in environment')
+@click.option('--sol005/--no-sol005',
+              default=True,
+              envvar='OSM_SOL005',
+              help='Use ETSI NFV SOL005 API (default) or the previous SO API. ' +
+                   'Also can set OSM_SOL005 in environment')
+@click.option('--user',
+              default=None,
+              envvar='OSM_USER',
+              help='user (only from Release FOUR, defaults to admin). ' +
+                   'Also can set OSM_USER in environment')
+@click.option('--password',
+              default=None,
+              envvar='OSM_PASSWORD',
+              help='password (only from Release FOUR, defaults to admin). ' +
+                   'Also can set OSM_PASSWORD in environment')
+@click.option('--project',
+              default=None,
+              envvar='OSM_PROJECT',
+              help='project (only from Release FOUR, defaults to admin). ' +
+                   'Also can set OSM_PROJECT in environment')
 @click.option('--so-port',
               default=None,
               envvar='OSM_SO_PORT',
@@ -66,16 +86,12 @@ def check_client_version(obj, what, version='sol005'):
               help='hostname of RO server.  ' +
               'Also can set OSM_RO_HOSTNAME in environment')
 @click.option('--ro-port',
-              default=9090,
+              default=None,
               envvar='OSM_RO_PORT',
               help='hostname of RO server.  ' +
                    'Also can set OSM_RO_PORT in environment')
-@click.option('--sol005/--no-sol005',
-              default=True,
-              envvar='OSM_SOL005',
-              help='Use ETSI NFV SOL005 API (default) or the previous SO API')
 @click.pass_context
-def cli(ctx, hostname, so_port, so_project, ro_hostname, ro_port, sol005):
+def cli(ctx, hostname, sol005, user, password, project, so_port, so_project, ro_hostname, ro_port):
     if hostname is None:
         print((
             "either hostname option or OSM_HOSTNAME " +
@@ -90,6 +106,12 @@ def cli(ctx, hostname, so_port, so_project, ro_hostname, ro_port, sol005):
         kwargs['ro_host']=ro_hostname
     if ro_port is not None:
         kwargs['ro_port']=ro_port
+    if user is not None:
+        kwargs['user']=user
+    if password is not None:
+        kwargs['password']=password
+    if project is not None:
+        kwargs['project']=project
 
     ctx.obj = client.Client(host=hostname, sol005=sol005, **kwargs)
 
@@ -1269,6 +1291,182 @@ def sdnc_show(ctx, name):
     print(table)
 
 
+####################
+# Project mgmt operations
+####################
+
+@cli.command(name='project-create')
+@click.argument('name')
+#@click.option('--description',
+#              default='no description',
+#              help='human readable description')
+@click.pass_context
+def project_create(ctx, name):
+    '''Creates a new project
+
+    NAME: name of the project
+    '''
+    project = {}
+    project['name'] = name
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        ctx.obj.project.create(name, project)
+    except ClientException as inst:
+        print(inst.message)
+
+
+@cli.command(name='project-delete')
+@click.argument('name')
+#@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.pass_context
+def project_delete(ctx, name):
+    '''deletes a project
+
+    NAME: name or ID of the project to be deleted
+    '''
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        ctx.obj.project.delete(name)
+    except ClientException as inst:
+        print(inst.message)
+        exit(1)
+
+
+@cli.command(name='project-list')
+@click.option('--filter', default=None,
+              help='restricts the list to the projects matching the filter')
+@click.pass_context
+def project_list(ctx, filter):
+    '''list all projects'''
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        resp = ctx.obj.project.list(filter)
+    except ClientException as inst:
+        print(inst.message)
+        exit(1)
+    table = PrettyTable(['name', 'id'])
+    for proj in resp:
+        table.add_row([proj['name'], proj['_id']])
+    table.align = 'l'
+    print(table)
+
+
+@cli.command(name='project-show')
+@click.argument('name')
+@click.pass_context
+def project_show(ctx, name):
+    '''shows the details of a project
+
+    NAME: name or ID of the project
+    '''
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        resp = ctx.obj.project.get(name)
+    except ClientException as inst:
+        print(inst.message)
+        exit(1)
+
+    table = PrettyTable(['key', 'attribute'])
+    for k, v in resp.items():
+        table.add_row([k, json.dumps(v, indent=2)])
+    table.align = 'l'
+    print(table)
+
+
+####################
+# User mgmt operations
+####################
+
+@cli.command(name='user-create')
+@click.argument('username')
+@click.option('--password',
+              prompt=True,
+              hide_input=True,
+              confirmation_prompt=True,
+              help='user password')
+@click.option('--projects',
+              default=None,
+              help='list of project ids that the user belongs to')
+#@click.option('--description',
+#              default='no description',
+#              help='human readable description')
+@click.pass_context
+def user_create(ctx, username, password, projects):
+    '''Creates a new user
+
+    USERNAME: name of the user
+    '''
+    user = {}
+    user['username'] = username
+    user['password'] = password
+    user['projects'] = projects
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        ctx.obj.user.create(username, user)
+    except ClientException as inst:
+        print(inst.message)
+
+
+@cli.command(name='user-delete')
+@click.argument('name')
+#@click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions')
+@click.pass_context
+def user_delete(ctx, name):
+    '''deletes a user
+
+    NAME: name or ID of the user to be deleted
+    '''
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        ctx.obj.user.delete(name)
+    except ClientException as inst:
+        print(inst.message)
+        exit(1)
+
+
+@cli.command(name='user-list')
+@click.option('--filter', default=None,
+              help='restricts the list to the users matching the filter')
+@click.pass_context
+def user_list(ctx, filter):
+    '''list all users'''
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        resp = ctx.obj.user.list(filter)
+    except ClientException as inst:
+        print(inst.message)
+        exit(1)
+    table = PrettyTable(['name', 'id'])
+    for user in resp:
+        table.add_row([user['name'], user['_id']])
+    table.align = 'l'
+    print(table)
+
+
+@cli.command(name='user-show')
+@click.argument('name')
+@click.pass_context
+def user_show(ctx, name):
+    '''shows the details of a user
+
+    NAME: name or ID of the user
+    '''
+    try:
+        check_client_version(ctx.obj, ctx.command.name)
+        resp = ctx.obj.user.get(name)
+        if 'password' in resp:
+            resp['password']='********'
+    except ClientException as inst:
+        print(inst.message)
+        exit(1)
+
+    table = PrettyTable(['key', 'attribute'])
+    for k, v in resp.items():
+        table.add_row([k, json.dumps(v, indent=2)])
+    table.align = 'l'
+    print(table)
+
+
 ####################
 # Fault Management operations
 ####################
index 479fc9a..5de4ff0 100644 (file)
@@ -27,6 +27,8 @@ from osmclient.sol005 import vim
 from osmclient.sol005 import package
 from osmclient.sol005 import http
 from osmclient.sol005 import sdncontroller
+from osmclient.sol005 import project as projectmodule
+from osmclient.sol005 import user as usermodule
 from osmclient.common.exceptions import ClientException
 import json
 
@@ -36,15 +38,14 @@ class Client(object):
         self,
         host=None,
         so_port=9999,
-        so_project='admin',
-        ro_host=None,
-        ro_port=9090,
+        user='admin',
+        password='admin',
+        project='admin',
         **kwargs):
 
-        self._user = 'admin'
-        self._password = 'admin'
-        #self._project = so_project
-        self._project = 'admin'
+        self._user = user
+        self._password = password
+        self._project = project
         self._auth_endpoint = '/admin/v1/tokens'
         self._headers = {}
 
@@ -56,13 +57,6 @@ class Client(object):
             self._host = host
             self._so_port = so_port
 
-        if ro_host is None:
-            ro_host = host
-        ro_http_client = http.Http('http://{}:{}/openmano'.format(ro_host, ro_port))
-        ro_http_client.set_http_header(
-            ['Accept: application/json',
-             'Content-Type: application/json'])
-
         self._http_client = http.Http(
             'https://{}:{}/osm'.format(self._host,self._so_port))
         self._headers['Accept'] = 'application/json'
@@ -86,6 +80,8 @@ class Client(object):
         self.vim = vim.Vim(self._http_client, client=self)
         self.sdnc = sdncontroller.SdnController(self._http_client, client=self)
         self.vnf = vnf.Vnf(self._http_client, client=self)
+        self.project = projectmodule.Project(self._http_client, client=self)
+        self.user = usermodule.User(self._http_client, client=self)
         '''
         self.vca = vca.Vca(http_client, client=self, **kwargs)
         self.utils = utils.Utils(http_client, **kwargs)
@@ -94,7 +90,7 @@ class Client(object):
     def get_token(self):
         postfields_dict = {'username': self._user,
                            'password': self._password,
-                           'project-id': self._project}
+                           'project_id': self._project}
         http_code, resp = self._http_client.post_cmd(endpoint=self._auth_endpoint,
                               postfields_dict=postfields_dict)
         if http_code not in (200, 201, 202, 204):
diff --git a/osmclient/sol005/project.py b/osmclient/sol005/project.py
new file mode 100644 (file)
index 0000000..5179f2e
--- /dev/null
@@ -0,0 +1,136 @@
+#
+# Copyright 2018 Telefonica Investigacion y Desarrollo S.A.U.
+#
+# 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.
+
+"""
+OSM project mgmt API
+"""
+
+from osmclient.common import utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import json
+
+
+class Project(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+        self._apiName = '/admin'
+        self._apiVersion = '/v1'
+        self._apiResource = '/projects'
+        self._apiBase = '{}{}{}'.format(self._apiName,
+                                        self._apiVersion, self._apiResource)
+
+    def create(self, name, project):
+        """Creates a new OSM project
+        """
+        http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
+                                       postfields_dict=project)
+        #print('HTTP CODE: {}'.format(http_code))
+        #print('RESP: {}'.format(resp))
+        if http_code in (200, 201, 202, 204):
+            if resp:
+                resp = json.loads(resp)
+            if not resp or 'id' not in resp:
+                raise ClientException('unexpected response from server - {}'.format(
+                                      resp))
+            print(resp['id'])
+        else:
+            msg = ""
+            if resp:
+                try:
+                    msg = json.loads(resp)
+                except ValueError:
+                    msg = resp
+            raise ClientException("failed to create project {} - {}".format(name, msg))
+
+    def update(self, name, project):
+        """Updates an OSM project identified by name
+        """
+        proj = self.get(name)
+        http_code, resp = self._http.put_cmd(endpoint='{}/{}'.format(self._apiBase,proj['_id']),
+                                       postfields_dict=project)
+        #print('HTTP CODE: {}'.format(http_code))
+        #print('RESP: {}'.format(resp))
+        if http_code in (200, 201, 202, 204):
+            if resp:
+                resp = json.loads(resp)
+            if not resp or 'id' not in resp:
+                raise ClientException('unexpected response from server - {}'.format(
+                                      resp))
+            print(resp['id'])
+        else:
+            msg = ""
+            if resp:
+                try:
+                    msg = json.loads(resp)
+                except ValueError:
+                    msg = resp
+            raise ClientException("failed to update project {} - {}".format(name, msg))
+
+    def delete(self, name, force=False):
+        """Deletes an OSM project identified by name
+        """
+        project = self.get(name)
+        querystring = ''
+        if force:
+            querystring = '?FORCE=True'
+        http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
+                                         project['_id'], querystring))
+        #print('HTTP CODE: {}'.format(http_code))
+        #print('RESP: {}'.format(resp))
+        if http_code == 202:
+            print('Deletion in progress')
+        elif http_code == 204:
+            print('Deleted')
+        elif resp and 'result' in resp:
+            print('Deleted')
+        else:
+            msg = ""
+            if resp:
+                try:
+                    msg = json.loads(resp)
+                except ValueError:
+                    msg = resp
+            raise ClientException("failed to delete project {} - {}".format(name, msg))
+
+    def list(self, filter=None):
+        """Returns the list of OSM projects
+        """
+        filter_string = ''
+        if filter:
+            filter_string = '?{}'.format(filter)
+        resp = self._http.get_cmd('{}{}'.format(self._apiBase,filter_string))
+        #print('RESP: {}'.format(resp))
+        if resp:
+            return resp
+        return list()
+
+    def get(self, name):
+        """Returns a specific OSM project based on name or id
+        """
+        if utils.validate_uuid4(name):
+            for proj in self.list():
+                if name == proj['_id']:
+                    return proj
+        else:
+            for proj in self.list():
+                if name == proj['name']:
+                    return proj
+        raise NotFound("Project {} not found".format(name))
+
+
index 6129961..391089f 100644 (file)
@@ -33,6 +33,7 @@ class SdnController(object):
         self._apiResource = '/sdns'
         self._apiBase = '{}{}{}'.format(self._apiName,
                                         self._apiVersion, self._apiResource)
+
     def create(self, name, sdn_controller):
         http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
                                        postfields_dict=sdn_controller)
diff --git a/osmclient/sol005/user.py b/osmclient/sol005/user.py
new file mode 100644 (file)
index 0000000..ee572f1
--- /dev/null
@@ -0,0 +1,139 @@
+#
+# Copyright 2018 Telefonica Investigacion y Desarrollo S.A.U.
+#
+# 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.
+
+"""
+OSM user mgmt API
+"""
+
+from osmclient.common import utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import json
+import yaml
+
+
+class User(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+        self._apiName = '/admin'
+        self._apiVersion = '/v1'
+        self._apiResource = '/users'
+        self._apiBase = '{}{}{}'.format(self._apiName,
+                                        self._apiVersion, self._apiResource)
+
+    def create(self, name, user):
+        """Creates a new OSM user
+        """
+        if 'projects' in user and user['projects'] is not None:
+            user['projects'] = yaml.safe_load(user['projects'])
+        http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
+                                       postfields_dict=user)
+        #print('HTTP CODE: {}'.format(http_code))
+        #print('RESP: {}'.format(resp))
+        if http_code in (200, 201, 202, 204):
+            if resp:
+                resp = json.loads(resp)
+            if not resp or 'id' not in resp:
+                raise ClientException('unexpected response from server - {}'.format(
+                                      resp))
+            print(resp['id'])
+        else:
+            msg = ""
+            if resp:
+                try:
+                    msg = json.loads(resp)
+                except ValueError:
+                    msg = resp
+            raise ClientException("failed to create user {} - {}".format(name, msg))
+
+    def update(self, name, user):
+        """Updates an existing OSM user identified by name
+        """
+        myuser  = self.get(name)
+        http_code, resp = self._http.put_cmd(endpoint='{}/{}'.format(self._apiBase,myuser['_id']),
+                                       postfields_dict=user)
+        #print('HTTP CODE: {}'.format(http_code))
+        #print('RESP: {}'.format(resp))
+        if http_code in (200, 201, 202, 204):
+            if resp:
+                resp = json.loads(resp)
+            if not resp or 'id' not in resp:
+                raise ClientException('unexpected response from server - {}'.format(
+                                      resp))
+            print(resp['id'])
+        else:
+            msg = ""
+            if resp:
+                try:
+                    msg = json.loads(resp)
+                except ValueError:
+                    msg = resp
+            raise ClientException("failed to update user {} - {}".format(name, msg))
+
+    def delete(self, name, force=False):
+        """Deletes an existing OSM user identified by name
+        """
+        user = self.get(name)
+        querystring = ''
+        if force:
+            querystring = '?FORCE=True'
+        http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
+                                         user['_id'], querystring))
+        #print('HTTP CODE: {}'.format(http_code))
+        #print('RESP: {}'.format(resp))
+        if http_code == 202:
+            print('Deletion in progress')
+        elif http_code == 204:
+            print('Deleted')
+        elif resp and 'result' in resp:
+            print('Deleted')
+        else:
+            msg = ""
+            if resp:
+                try:
+                    msg = json.loads(resp)
+                except ValueError:
+                    msg = resp
+            raise ClientException("failed to delete user {} - {}".format(name, msg))
+
+    def list(self, filter=None):
+        """Returns the list of OSM users
+        """
+        filter_string = ''
+        if filter:
+            filter_string = '?{}'.format(filter)
+        resp = self._http.get_cmd('{}{}'.format(self._apiBase,filter_string))
+        #print('RESP: {}'.format(resp))
+        if resp:
+            return resp
+        return list()
+
+    def get(self, name):
+        """Returns an OSM user based on name or id
+        """
+        if utils.validate_uuid4(name):
+            for user in self.list():
+                if name == user['_id']:
+                    return user
+        else:
+            for user in self.list():
+                if name == user['name']:
+                    return user
+        raise NotFound("User {} not found".format(name))
+
+