update versions under scenario 49/39949/7
authorSerenaFeng <feng.xiaowei@zte.com.cn>
Wed, 23 Aug 2017 06:04:59 +0000 (14:04 +0800)
committerSerenaFeng <feng.xiaowei@zte.com.cn>
Fri, 25 Aug 2017 05:54:16 +0000 (13:54 +0800)
1. post, add one or more new versions
2. update, replace existed version as a totality
3. delete, delete one or more versions by name
4. in post&update, if schema is not consistent with ScenarioVersion model,
BadRequest will be raised(only extra keys will be detected currently)
5. in post, if project already exist, return Conflict with already exist
message
6. in update, if a version appears more than once, also return Conflict
with already exist message
7. add update with conflict unittest for projects

Change-Id: I0d2c8a5567f3abce7b0313a64e18303af02cc002
Signed-off-by: SerenaFeng <feng.xiaowei@zte.com.cn>
utils/test/testapi/opnfv_testapi/resources/models.py
utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py
utils/test/testapi/opnfv_testapi/router/url_mappings.py
utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py

index 6f04cc2..e70a6ed 100644 (file)
@@ -61,11 +61,11 @@ class ModelBase(object):
                     '{} has no attribute {}'.format(cls.__name__, k))
             value = v
             if isinstance(v, dict) and k in attr_parser:
-                value = attr_parser[k].from_dict(v)
+                value = attr_parser[k].from_dict_with_raise(v)
             elif isinstance(v, list) and k in attr_parser:
                 value = []
                 for item in v:
-                    value.append(attr_parser[k].from_dict(item))
+                    value.append(attr_parser[k].from_dict_with_raise(item))
 
             t.__setattr__(k, value)
 
index d215d18..d6918a6 100644 (file)
@@ -147,6 +147,9 @@ class ScenarioUpdater(object):
             ('projects', 'put'): self._update_requests_update_projects,
             ('projects', 'delete'): self._update_requests_delete_projects,
             ('owner', 'put'): self._update_requests_change_owner,
+            ('versions', 'post'): self._update_requests_add_versions,
+            ('versions', 'put'): self._update_requests_update_versions,
+            ('versions', 'delete'): self._update_requests_delete_versions,
         }
         updates[(item, action)](self.data)
 
@@ -210,42 +213,16 @@ class ScenarioUpdater(object):
     @iter_installers
     @iter_versions
     def _update_requests_add_projects(self, version):
-        exists = list()
-        malformat = list()
-        for n in self.body:
-            try:
-                f_n = models.ScenarioProject.from_dict_with_raise(n)
-                if not any(o.project == f_n.project for o in version.projects):
-                    version.projects.append(f_n)
-                else:
-                    exists.append(n['project'])
-            except Exception as e:
-                malformat.append(e.message)
-        if malformat:
-            raises.BadRequest(message.bad_format(malformat))
-        elif exists:
-            raises.Conflict(message.exist('projects', exists))
+        version.projects = self._update_with_body(models.ScenarioProject,
+                                                  'project',
+                                                  version.projects)
 
     @iter_installers
     @iter_versions
     def _update_requests_update_projects(self, version):
-        exists = list()
-        malformat = list()
-        projects = list()
-        for n in self.body:
-            try:
-                f_n = models.ScenarioProject.from_dict_with_raise(n)
-                if not any(o.project == f_n.project for o in projects):
-                    projects.append(models.ScenarioProject.from_dict(n))
-                else:
-                    exists.append(n['project'])
-            except:
-                malformat.append(n)
-        if malformat:
-            raises.BadRequest(message.bad_format(malformat))
-        elif exists:
-            raises.Forbidden(message.exist('projects', exists))
-        version.projects = projects
+        version.projects = self._update_with_body(models.ScenarioProject,
+                                                  'project',
+                                                  list())
 
     @iter_installers
     @iter_versions
@@ -257,12 +234,50 @@ class ScenarioUpdater(object):
     def _update_requests_change_owner(self, version):
         version.owner = self.body.get('owner')
 
+    @iter_installers
+    def _update_requests_add_versions(self, installer):
+        installer.versions = self._update_with_body(models.ScenarioVersion,
+                                                    'version',
+                                                    installer.versions)
+
+    @iter_installers
+    def _update_requests_update_versions(self, installer):
+        installer.versions = self._update_with_body(models.ScenarioVersion,
+                                                    'version',
+                                                    list())
+
+    @iter_installers
+    def _update_requests_delete_versions(self, installer):
+        installer.versions = self._remove_versions(installer.versions)
+
+    def _update_with_body(self, clazz, field, withs):
+        exists = list()
+        malformat = list()
+        for new in self.body:
+            try:
+                format_new = clazz.from_dict_with_raise(new)
+                new_name = getattr(format_new, field)
+                if not any(getattr(o, field) == new_name for o in withs):
+                    withs.append(format_new)
+                else:
+                    exists.append(new_name)
+            except Exception as error:
+                malformat.append(error.message)
+        if malformat:
+            raises.BadRequest(message.bad_format(malformat))
+        elif exists:
+            raises.Conflict(message.exist('{}s'.format(field), exists))
+        return withs
+
     def _filter_installers(self, installers):
         return self._filter('installer', installers)
 
     def _filter_versions(self, versions):
         return self._filter('version', versions)
 
+    def _remove_versions(self, versions):
+        return self._remove('version', versions)
+
     def _filter_projects(self, projects):
         return self._filter('project', projects)
 
@@ -602,3 +617,74 @@ class ScenarioOwnerHandler(GenericScenarioUpdateHandler):
                        locators={'scenario': scenario,
                                  'installer': None,
                                  'version': None})
+
+
+class ScenarioVersionsHandler(GenericScenarioUpdateHandler):
+    @swagger.operation(nickname="addVersionsUnderScenario")
+    def post(self, scenario):
+        """
+        @description: add versions to scenario
+        @notes: add one or multiple versions
+            POST /api/v1/scenarios/<scenario_name>/versions? \
+                installer=<installer_name>
+        @param body: versions to be added
+        @type body: C{list} of L{ScenarioVersion}
+        @in body: body
+        @param installer: installer type
+        @type installer: L{string}
+        @in installer: query
+        @required installer: True
+        @return 200: versions are added.
+        @raise 400: bad schema
+        @raise 409: conflict, version already exists
+        @raise 404:  scenario/installer not exist
+        """
+        self.do_update('versions',
+                       'post',
+                       locators={'scenario': scenario,
+                                 'installer': None})
+
+    @swagger.operation(nickname="updateVersionsUnderScenario")
+    def put(self, scenario):
+        """
+        @description: replace all versions
+        @notes: substitute all versions as a totality
+            PUT /api/v1/scenarios/<scenario_name>/versions? \
+                installer=<installer_name>
+        @param body: new versions
+        @type body: C{list} of L{ScenarioVersion}
+        @in body: body
+        @param installer: installer type
+        @type installer: L{string}
+        @in installer: query
+        @required installer: True
+        @return 200: replace versions success.
+        @raise 400: bad schema
+        @raise 404:  scenario/installer not exist
+        """
+        self.do_update('versions',
+                       'put',
+                       locators={'scenario': scenario,
+                                 'installer': None})
+
+    @swagger.operation(nickname="deleteVersionsUnderScenario")
+    def delete(self, scenario):
+        """
+        @description: delete one or multiple versions
+        @notes: delete one or multiple versions
+            DELETE /api/v1/scenarios/<scenario_name>/versions? \
+                installer=<installer_name>
+        @param body: versions(names) to be deleted
+        @type body: C{list} of L{string}
+        @in body: body
+        @param installer: installer type
+        @type installer: L{string}
+        @in installer: query
+        @required installer: True
+        @return 200: delete versions success.
+        @raise 404:  scenario/installer not exist
+        """
+        self.do_update('versions',
+                       'delete',
+                       locators={'scenario': scenario,
+                                 'installer': None})
index 9c9556c..bdfc701 100644 (file)
@@ -64,6 +64,8 @@ mappings = [
      scenario_handlers.ScenarioProjectsHandler),
     (r"/api/v1/scenarios/([^/]+)/owner",
      scenario_handlers.ScenarioOwnerHandler),
+    (r"/api/v1/scenarios/([^/]+)/versions",
+     scenario_handlers.ScenarioVersionsHandler),
 
     # static path
     (r'/(.*\.(css|png|gif|js|html|json|map|woff2|woff|ttf))',
index 50a8c8d..360b4fa 100644 (file)
@@ -175,6 +175,10 @@ class TestScenarioUpdate(TestScenarioBase):
                     locator = 'installer={}&version={}'.format(
                         self.installer,
                         self.version)
+                elif item in ['versions']:
+                    locator = 'installer={}'.format(
+                        self.installer)
+
                 self.update_url = '{}/{}?{}'.format(self.scenario_url,
                                                     item,
                                                     locator)
@@ -275,6 +279,15 @@ class TestScenarioUpdate(TestScenarioBase):
         scenario['installers'][0]['versions'][0]['projects'] = [update]
         return [update], scenario
 
+    @update_url_fixture('projects')
+    @update_partial('_update', '_conflict')
+    def test_updateProjects_duplicated(self, scenario):
+        update1 = models.ScenarioProject(project='qtip').format()
+        update2 = models.ScenarioProject(project='qtip').format()
+        scenario['installers'][0]['versions'][0]['projects'] = [update1,
+                                                                update2]
+        return [update1, update2], scenario
+
     @update_url_fixture('projects')
     @update_partial('_update', '_bad_request')
     def test_updateProjects_bad_schema(self, scenario):
@@ -301,6 +314,61 @@ class TestScenarioUpdate(TestScenarioBase):
         scenario['installers'][0]['versions'][0]['owner'] = new_owner
         return update, scenario
 
+    @update_url_fixture('versions')
+    @update_partial('_add', '_success')
+    def test_addVersions_succ(self, scenario):
+        add = models.ScenarioVersion(version='Euphrates').format()
+        scenario['installers'][0]['versions'].append(add)
+        return [add], scenario
+
+    @update_url_fixture('versions')
+    @update_partial('_add', '_conflict')
+    def test_addVersions_already_exist(self, scenario):
+        add = models.ScenarioVersion(version='master').format()
+        scenario['installers'][0]['versions'].append(add)
+        return [add], scenario
+
+    @update_url_fixture('versions')
+    @update_partial('_add', '_bad_request')
+    def test_addVersions_bad_schema(self, scenario):
+        add = models.ScenarioVersion(version='euphrates').format()
+        add['notexist'] = None
+        scenario['installers'][0]['versions'].append(add)
+        return [add], scenario
+
+    @update_url_fixture('versions')
+    @update_partial('_update', '_success')
+    def test_updateVersions_succ(self, scenario):
+        update = models.ScenarioVersion(version='euphrates').format()
+        scenario['installers'][0]['versions'] = [update]
+        return [update], scenario
+
+    @update_url_fixture('versions')
+    @update_partial('_update', '_conflict')
+    def test_updateVersions_duplicated(self, scenario):
+        update1 = models.ScenarioVersion(version='euphrates').format()
+        update2 = models.ScenarioVersion(version='euphrates').format()
+        scenario['installers'][0]['versions'] = [update1, update2]
+        return [update1, update2], scenario
+
+    @update_url_fixture('versions')
+    @update_partial('_update', '_bad_request')
+    def test_updateVersions_bad_schema(self, scenario):
+        update = models.ScenarioVersion(version='euphrates').format()
+        update['not_owner'] = 'Iam'
+        scenario['installers'][0]['versions'] = [update]
+        return [update], scenario
+
+    @update_url_fixture('versions')
+    @update_partial('_delete', '_success')
+    def test_deleteVersions(self, scenario):
+        deletes = ['master']
+        versions = scenario['installers'][0]['versions']
+        scenario['installers'][0]['versions'] = filter(
+            lambda f: f['version'] != 'master',
+            versions)
+        return deletes, scenario
+
     def _add(self, update_req, new_scenario):
         return self.post_direct_url(self.update_url, update_req)