diff options
-rw-r--r-- | lcm/pub/config/config.py | 3 | ||||
-rw-r--r-- | lcm/pub/msapi/activiti.py | 16 | ||||
-rw-r--r-- | lcm/pub/nfvi/vim/vimadaptor.py | 5 | ||||
-rw-r--r-- | lcm/pub/utils/restcall.py | 12 | ||||
-rw-r--r-- | lcm/pub/utils/toscaparser/basemodel.py | 70 | ||||
-rw-r--r-- | lcm/pub/utils/toscaparser/nsdmodel.py | 158 | ||||
-rw-r--r-- | lcm/settings.py | 1 | ||||
-rw-r--r-- | lcm/urls.py | 6 | ||||
-rw-r--r-- | lcm/workflows/auto_deploy.py | 38 | ||||
-rw-r--r-- | lcm/workflows/build_in.py | 117 | ||||
-rw-r--r-- | lcm/workflows/tests.py | 46 | ||||
-rw-r--r-- | lcm/workflows/views.py | 31 |
12 files changed, 455 insertions, 48 deletions
diff --git a/lcm/pub/config/config.py b/lcm/pub/config/config.py index 85ba24d8..f501d338 100644 --- a/lcm/pub/config/config.py +++ b/lcm/pub/config/config.py @@ -61,6 +61,9 @@ SDC_BASE_URL = "https://127.0.0.1:1234/api" SDC_USER = "admin" SDC_PASSWD = "admin" +# [workflow] +DEPLOY_WORKFLOW_WHEN_START = True + diff --git a/lcm/pub/msapi/activiti.py b/lcm/pub/msapi/activiti.py index 69320d1c..b3a12205 100644 --- a/lcm/pub/msapi/activiti.py +++ b/lcm/pub/msapi/activiti.py @@ -14,7 +14,7 @@ import json from lcm.pub.exceptions import NSLCMException -from lcm.pub.utils.restcall import req_by_msb +from lcm.pub.utils import restcall """ Input: @@ -29,14 +29,16 @@ Output: "processId": "string" } """ -def deploy_workflow(content): - content_str = json.JSONEncoder().encode(content) - ret = req_by_msb("/api/workflow/v1/package", "POST", content_str) +def deploy_workflow(file_path): + file_name = file_path.split("/")[-1] + file_data = { + 'file': open(file_path, 'rb'), + 'filename': file_name} + ret = restcall.upload_by_msb("api/workflow/v1/package", "POST", file_data) if ret[0] != 0: raise NSLCMException("Status code is %s, detail is %s.", ret[2], ret[1]) return json.JSONDecoder().decode(ret[1]) - """ Input: None @@ -49,7 +51,7 @@ Output: """ def undeploy_workflow(deploy_id): uri = "api/workflow/v1/package/{deployId}".format(deployId=deploy_id) - ret = req_by_msb(uri, "DELETE") + ret = restcall.req_by_msb(uri, "DELETE") if ret[0] != 0: raise NSLCMException("Status code is %s, detail is %s.", ret[2], ret[1]) return json.JSONDecoder().decode(ret[1]) @@ -70,7 +72,7 @@ Output: """ def exec_workflow(content): content_str = json.JSONEncoder().encode(content) - ret = req_by_msb("api/workflow/v1/process/instance", "POST", content_str) + ret = restcall.req_by_msb("api/workflow/v1/process/instance", "POST", content_str) if ret[0] != 0: raise NSLCMException("Status code is %s, detail is %s.", ret[2], ret[1]) return json.JSONDecoder().decode(ret[1]) diff --git a/lcm/pub/nfvi/vim/vimadaptor.py b/lcm/pub/nfvi/vim/vimadaptor.py index 5f08d0b1..6f63a48c 100644 --- a/lcm/pub/nfvi/vim/vimadaptor.py +++ b/lcm/pub/nfvi/vim/vimadaptor.py @@ -35,10 +35,7 @@ class VimAdaptor: def create_api(self, connectInfo): vimtype = connectInfo['vimtype'] if 'vimtype' in connectInfo else None logger.info("call %s, vimtype=%s" % (fun_name(), vimtype)) - if vimtype == const.VIM_OPENSTACK: - from lcm.pub.nfvi.vim.api.openstack.api import OpenstackApi - self.apiImpl = OpenstackApi() - elif vimtype == const.VIM_VMWARE: + if vimtype in (const.VIM_OPENSTACK, const.VIM_VMWARE): from lcm.pub.nfvi.vim.api.multivim.api import MultiVimApi self.apiImpl = MultiVimApi() else: diff --git a/lcm/pub/utils/restcall.py b/lcm/pub/utils/restcall.py index bfb02d6b..0ef20561 100644 --- a/lcm/pub/utils/restcall.py +++ b/lcm/pub/utils/restcall.py @@ -81,6 +81,18 @@ def req_by_msb(resource, method, content=''): base_url = "http://%s:%s/" % (MSB_SERVICE_IP, MSB_SERVICE_PORT) return call_req(base_url, "", "", rest_no_auth, resource, method, content) +def upload_by_msb(resource, method, file_data={}): + headers = {'Content-Type': 'application/octet-stream'} + full_url = "http://%s:%s/%s" % (MSB_SERVICE_IP, MSB_SERVICE_PORT, resource) + http = httplib2.Http() + resp, resp_content = http.request(full_url, + method=method.upper(), body=file_data, headers=headers) + resp_status, resp_body = resp['status'], resp_content.decode('UTF-8') + if resp_status not in status_ok_list: + logger.error("Status code is %s, detail is %s.", resp_status, resp_body) + return [1, "Failed to upload file.", resp_status] + logger.debug("resp_body=%s", resp_body) + return [0, resp_body, resp_status] def combine_url(base_url, resource): full_url = None diff --git a/lcm/pub/utils/toscaparser/basemodel.py b/lcm/pub/utils/toscaparser/basemodel.py index e1f841ff..e624bd08 100644 --- a/lcm/pub/utils/toscaparser/basemodel.py +++ b/lcm/pub/utils/toscaparser/basemodel.py @@ -105,23 +105,6 @@ class BaseInfoModel(object): if 'metadata' in tosca.tpl: self.metadata = copy.deepcopy(tosca.tpl['metadata']) - def buildProperties(self, nodeTemplate, parsed_params): - properties = {} - isMappingParams = parsed_params and len(parsed_params) > 0 - for k, item in nodeTemplate.get_properties().items(): - properties[k] = item.value - if isinstance(item.value, GetInput): - if item.value.result() and isMappingParams: - properties[k] = DataEntityExt.validate_datatype(item.type, item.value.result()) - else: - tmp = {} - tmp[item.value.name] = item.value.input_name - properties[k] = tmp - if 'attributes' in nodeTemplate.entity_tpl: - for k, item in nodeTemplate.entity_tpl['attributes'].items(): - properties[k] = str(item) - return properties - def buildProperties(self, nodeTemplate, parsed_params): properties = {} isMappingParams = parsed_params and len(parsed_params) > 0 @@ -198,6 +181,13 @@ class BaseInfoModel(object): def isPnf(self, node): return node['nodeType'].upper().find('.PNF.') >= 0 or node['nodeType'].upper().endswith('.PNF') + def isCp(self, node): + return node['nodeType'].upper().find('.CP.') >= 0 or node['nodeType'].upper().endswith('.CP') + + def isVl(self, node): + return node['nodeType'].upper().find('.VIRTUALLINK.') >= 0 or node['nodeType'].upper().find('.VL.') >= 0 or \ + node['nodeType'].upper().endswith('.VIRTUALLINK') or node['nodeType'].upper().endswith('.VL') + def get_requirement_node_name(self, req_value): return self.get_prop_from_obj(req_value, 'node') @@ -211,6 +201,13 @@ class BaseInfoModel(object): def getNodeDependencys(self, node): return self.getRequirementByName(node, 'dependency') + def getVirtualLinks(self, node): + return self.getRequirementByName(node, 'virtualLink') + + def getVirtualbindings(self, node): + return self.getRequirementByName(node, 'virtualbinding') + + def getRequirementByName(self, node, requirementName): requirements = [] if 'requirements' in node: @@ -229,3 +226,42 @@ class BaseInfoModel(object): rets.append({"key_name":key, "vl_id":self.get_requirement_node_name(value)}) return rets + def _verify_value(self, value, inputs, parsed_params): + if isinstance(value, str): + return self._verify_string(inputs, parsed_params, value) + if isinstance(value, list) or isinstance(value, dict): + return self._verify_object(value, inputs, parsed_params) + return value + + def _verify_object(self, value, inputs, parsed_params): + s = self._verify_string(inputs, parsed_params, json.dumps(value)) + return json.loads(s) + + def _get_input_name(self, getInput): + input_name = getInput.split(':')[1] + input_name = input_name.strip() + return input_name.replace('"', '').replace('}', '') + + def _verify_string(self, inputs, parsed_params, value): + getInputs = re.findall(r'{"get_input": "[a-zA-Z_0-9]+"}', value) + for getInput in getInputs: + input_name = self._get_input_name(getInput) + if parsed_params and input_name in parsed_params: + value = value.replace(getInput, json.dumps(parsed_params[input_name])) + else: + for input_def in inputs: + if input_def.default and input_name == input_def.name: + value = value.replace(getInput, json.dumps(input_def.default)) + return value + + def get_node_vl_id(self, node): + vl_ids = map(lambda x: self.get_requirement_node_name(x), self.getVirtualLinks(node)) + if len(vl_ids) > 0: + return vl_ids[0] + return "" + + def get_node_by_name(self, node_templates, name): + for node in node_templates: + if node['name'] == name: + return node + return None
\ No newline at end of file diff --git a/lcm/pub/utils/toscaparser/nsdmodel.py b/lcm/pub/utils/toscaparser/nsdmodel.py index 7d7dafad..1838014d 100644 --- a/lcm/pub/utils/toscaparser/nsdmodel.py +++ b/lcm/pub/utils/toscaparser/nsdmodel.py @@ -19,16 +19,11 @@ class EtsiNsdInfoModel(BaseInfoModel): self.vnfs = self._get_all_vnf(nodeTemplates) self.pnfs = self._get_all_pnf(nodeTemplates) - # self.vls = self.get_all_vl(nodeTemplates) - # self.cps = self.get_all_cp(nodeTemplates) - # self.routers = self.get_all_router(nodeTemplates) - # self.fps = self._get_all_fp(nodeTemplates) - # self.vnffgs = self._get_all_vnffg(tosca.topology_template.groups) - # self.server_groups = self.get_all_server_group(tosca.topology_template.groups) - # self.ns_exposed = self.get_all_endpoint_exposed(tosca.topology_template) - # self.policies = self._get_policies_scaling(tosca.topology_template.policies) - # self.ns_flavours = self.get_all_flavour(tosca.topology_template.groups) - # self.nested_ns = self.get_all_nested_ns(nodeTemplates) + self.vls = self.get_all_vl(nodeTemplates) + self.cps = self.get_all_cp(nodeTemplates) + self.routers = self.get_all_router(nodeTemplates) + self.fps = self._get_all_fp(nodeTemplates) + def buildInputs(self, top_inputs): ret = {} @@ -98,4 +93,145 @@ class EtsiNsdInfoModel(BaseInfoModel): req_node_name = self.get_requirement_node_name(value) if req_node_name != None and req_node_name == node['name']: cps.append(tmpnode) - return cps
\ No newline at end of file + return cps + + def get_all_vl(self, nodeTemplates): + vls = [] + for node in nodeTemplates: + if self.isVl(node): + vl = {} + vl['vl_id'] = node['name'] + vl['description'] = node['description'] + vl['properties'] = node['properties'] + vl['route_external'] = False + vl['route_id'] = self._get_vl_route_id(node) + vls.append(vl) + if self._isExternalVL(node): + vl = {} + vl['vl_id'] = node['name'] + vl['description'] = node['description'] + vl['properties'] = node['properties'] + vl['route_external'] = True + vls.append(vl) + return vls + + def _get_vl_route_id(self, node): + route_ids = map(lambda x: self.get_requirement_node_name(x), + self.getRequirementByName(node, 'virtual_route')) + if len(route_ids) > 0: + return route_ids[0] + return "" + + def _isExternalVL(self, node): + return node['nodeType'].upper().find('.ROUTEEXTERNALVL') >= 0 + + def get_all_cp(self, nodeTemplates): + cps = [] + for node in nodeTemplates: + if self.isCp(node): + cp = {} + cp['cp_id'] = node['name'] + cp['cpd_id'] = node['name'] + cp['description'] = node['description'] + cp['properties'] = node['properties'] + cp['vl_id'] = self.get_node_vl_id(node) + binding_node_ids = map(lambda x: self.get_requirement_node_name(x), self.getVirtualbindings(node)) + # cp['vnf_id'] = self._filter_vnf_id(binding_node_ids, nodeTemplates) + cp['pnf_id'] = self._filter_pnf_id(binding_node_ids, nodeTemplates) + vls = self.buil_cp_vls(node) + if len(vls) > 1: + cp['vls'] = vls + cps.append(cp) + return cps + + def buil_cp_vls(self, node): + return map(lambda x: self._build_cp_vl(x), self.getVirtualLinks(node)) + + def _build_cp_vl(self, req): + cp_vl = {} + cp_vl['vl_id'] = self.get_prop_from_obj(req, 'node') + relationship = self.get_prop_from_obj(req, 'relationship') + if relationship != None: + properties = self.get_prop_from_obj(relationship, 'properties') + if properties != None and isinstance(properties, dict): + for key, value in properties.items(): + cp_vl[key] = value + return cp_vl + + def _filter_pnf_id(self, node_ids, node_templates): + for node_id in node_ids: + node = self.get_node_by_name(node_templates, node_id) + if self.isPnf(node): + return node_id + return "" + + def get_all_router(self, nodeTemplates): + rets = [] + for node in nodeTemplates: + if self._isRouter(node): + ret = {} + ret['router_id'] = node['name'] + ret['description'] = node['description'] + ret['properties'] = node['properties'] + ret['external_vl_id'] = self._get_router_external_vl_id(node) + ret['external_ip_addresses'] = self._get_external_ip_addresses(node) + + rets.append(ret) + return rets + + def _isRouter(self, node): + return node['nodeType'].upper().find('.ROUTER.') >= 0 or node['nodeType'].upper().endswith('.ROUTER') + + def _get_router_external_vl(self, node): + return self.getRequirementByName(node, 'external_virtual_link') + + def _get_router_external_vl_id(self, node): + ids = map(lambda x: self.get_requirement_node_name(x), self._get_router_external_vl(node)) + if len(ids) > 0: + return ids[0] + return "" + + def _get_external_ip_addresses(self, node): + external_vls = self._get_router_external_vl(node) + if len(external_vls) > 0: + if 'relationship' in external_vls[0] and 'properties' in external_vls[0]['relationship'] and 'router_ip_address' in external_vls[0]['relationship']['properties']: + return external_vls[0]['relationship']['properties']['router_ip_address'] + return [] + + def _get_all_fp(self, nodeTemplates): + fps = [] + for node in nodeTemplates: + if self._isFp(node): + fp = {} + fp['fp_id'] = node['name'] + fp['description'] = node['description'] + fp['properties'] = node['properties'] + fp['forwarder_list'] = self._getForwarderList(node, nodeTemplates) + + fps.append(fp) + return fps + + def _isFp(self, node): + return node['nodeType'].upper().find('.FP.') >= 0 or node['nodeType'].upper().find('.SFP.') >= 0 or node[ + 'nodeType'].upper().endswith('.FP') or node['nodeType'].upper().endswith('.SFP') + + def _getForwarderList(self, node, node_templates): + forwarderList = [] + if 'requirements' in node: + for item in node['requirements']: + for key, value in item.items(): + if key == 'forwarder': + tmpnode = self.get_node_by_req(node_templates, value) + type = 'cp' if self.isCp(tmpnode) else 'vnf' + req_node_name = self.get_requirement_node_name(value) + if isinstance(value, dict) and 'capability' in value: + forwarderList.append( + {"type": type, "node_name": req_node_name, "capability": value['capability']}) + else: + forwarderList.append({"type": type, "node_name": req_node_name, "capability": ""}) + + return forwarderList + + def get_node_by_req(self, node_templates, req): + req_node_name = self.get_requirement_node_name(req) + return self.get_node_by_name(node_templates, req_node_name) diff --git a/lcm/settings.py b/lcm/settings.py index f338ac29..1cf13714 100644 --- a/lcm/settings.py +++ b/lcm/settings.py @@ -136,6 +136,7 @@ LOGGING = { if 'test' in sys.argv: config.REG_TO_MSB_WHEN_START = False + config.DEPLOY_WORKFLOW_WHEN_START = False DATABASES = {} DATABASES['default'] = { 'ENGINE': 'django.db.backends.sqlite3', diff --git a/lcm/urls.py b/lcm/urls.py index 9471def9..b0333513 100644 --- a/lcm/urls.py +++ b/lcm/urls.py @@ -14,6 +14,7 @@ from django.conf.urls import include, url from lcm.pub.config.config import REG_TO_MSB_WHEN_START, REG_TO_MSB_REG_URL, REG_TO_MSB_REG_PARAM +from lcm.pub.config.config import DEPLOY_WORKFLOW_WHEN_START urlpatterns = [ url(r'^', include('lcm.samples.urls')), @@ -31,3 +32,8 @@ if REG_TO_MSB_WHEN_START: import json from lcm.pub.utils.restcall import req_by_msb req_by_msb(REG_TO_MSB_REG_URL, "POST", json.JSONEncoder().encode(REG_TO_MSB_REG_PARAM)) + +# deploy workflow when startup +if DEPLOY_WORKFLOW_WHEN_START: + from lcm.workflows import auto_deploy + auto_deploy.deploy_workflow_on_startup() diff --git a/lcm/workflows/auto_deploy.py b/lcm/workflows/auto_deploy.py new file mode 100644 index 00000000..8813542b --- /dev/null +++ b/lcm/workflows/auto_deploy.py @@ -0,0 +1,38 @@ +# Copyright 2017 ZTE Corporation. +# +# 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 logging +import traceback + +from lcm.pub.database.models import WFPlanModel +from lcm.pub.msapi import activiti + +logger = logging.getLogger(__name__) + +def deploy_workflow_on_startup(): + try: + if WFPlanModel.objects.filter(): + logger.warn("Workflow is already deployed.") + return + file_path = "TODO:" + deploy_info = activiti.deploy_workflow(file_path) + WFPlanModel( + deployed_id=deploy_info["deployedId"], + process_id=deploy_info["processId"], + status=deploy_info["status"], + message=deploy_info["message"], + plan_name="ns_instantiate").save() + logger.info("Deploy workflow successfully.") + except: + logger.error(traceback.format_exc()) + diff --git a/lcm/workflows/build_in.py b/lcm/workflows/build_in.py new file mode 100644 index 00000000..b24c7af8 --- /dev/null +++ b/lcm/workflows/build_in.py @@ -0,0 +1,117 @@ +# Copyright 2017 ZTE Corporation. +# +# 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 logging +import traceback + +from lcm.pub.utils.syscomm import fun_name +from lcm.pub.utils.values import ignore_case_get +from lcm.pub.utils import restcall +from lcm.pub.exceptions import NSLCMException + +logger = logging.getLogger(__name__) + +RESULT_OK, RESULT_NG = "0", "1" + +""" +format of input_data +{ + "jobId": uuid of job, + "nsInstanceId": id of ns instance, + "object_context": json format of nsd, + "object_additionalParamForNs": json format of additional parameters for ns, + "object_additionalParamForVnf": json format of additional parameters for vnf, + "vlCount": int type of VL count, + "vnfCount: int type of VNF count, + "sfcCount": int type of SFC count, + "sdnControllerId": uuid of SDN controller +} +""" +def run_ns_instantiate(input_data): + logger.debug("Enter %s, input_data is %s", fun_name(), input_data) + job_id = ignore_case_get(input_data, "jobId") + ns_inst_id = ignore_case_get(input_data, "nsInstanceId") + nsd_json = ignore_case_get(input_data, "object_context") + ns_param_json = ignore_case_get(input_data, "object_additionalParamForNs") + vnf_param_json = ignore_case_get(input_data, "object_additionalParamForVnf") + vl_count = ignore_case_get(input_data, "vlCount") + vnf_count = ignore_case_get(input_data, "vnfCount") + sfc_count = ignore_case_get(input_data, "sfcCount") + sdnc_id = ignore_case_get(input_data, "sdnControllerId") + + update_job(job_id, 10, "0", "Start to create VL") + for i in range(vl_count): + create_vl(ns_inst_id, i + 1, nsd_json, ns_param_json) + + update_job(job_id, 30, "0", "Start to create VNF") + for i in range(vnf_count): + create_vnf() + wait_until_job_done() + + update_job(job_id, 70, "0", "Start to create SFC") + for i in range(sfc_count): + create_sfc() + wait_until_job_done() + + update_job(job_id, 90, "0", "Start to post deal") + post_deal() + + update_job(job_id, 100, "0", "Create NS successfully.") + +def create_vl(ns_inst_id, vl_index, nsd, ns_param): + uri = "api/nslcm/v1/ns/vls" + data = json.JSONEncoder().encode({ + "nsInstanceId": ns_inst_id, + "vlIndex": vl_index, + "context": nsd, + "additionalParamForNs": ns_param + }) + + ret = restcall.req_by_msb(uri, "POST", data) + if ret[0] != 0: + logger.error("Failed to call create_vl(%s): %s", vl_index, ret[1]) + raise NSLCMException("Failed to call create_vl(index is %s)" % vl_index) + + result = str(ret[1]["result"]) + detail = ret[1]["detail"] + vl_id = ret[1]["vlId"] + if result != RESULT_OK: + logger.error("Failed to create VL(%s): %s", vl_id, detail) + raise NSLCMException("Failed to create VL(%s)" % vl_id) + + logger.debug("Create VL(%s) successfully.", vl_id) + +def create_vnf(): + # TODO: + pass + +def create_sfc(): + # TODO: + pass + +def post_deal(): + # TODO: + pass + +def update_job(job_id, progress, errcode, desc): + uri = "api/nslcm/v1/jobs/{jobId}".format(jobId=job_id) + data = json.JSONEncoder().encode({ + "progress": progress, + "errcode": errcode, + "desc": desc + }) + restcall.req_by_msb(uri, "POST", data) + +def wait_until_job_done(): + # TODO: + pass diff --git a/lcm/workflows/tests.py b/lcm/workflows/tests.py index 31651624..bba2d7ac 100644 --- a/lcm/workflows/tests.py +++ b/lcm/workflows/tests.py @@ -14,18 +14,60 @@ import unittest import json +import mock +import os from django.test import Client from rest_framework import status +from lcm.pub.utils import restcall +from lcm.pub.database.models import WFPlanModel class WorkflowViewTest(unittest.TestCase): def setUp(self): self.client = Client() + WFPlanModel.objects.filter().delete() def tearDown(self): pass - def test_deploy_workflow(self): + @mock.patch.object(restcall, 'upload_by_msb') + def test_deploy_workflow(self, mock_upload_by_msb): + mock_upload_by_msb.return_value = [0, json.JSONEncoder().encode({ + "status": "1", + "message": "2", + "deployedId": "3", + "processId": "4" + }), '202'] response = self.client.post("/api/nslcm/v1/workflow", - {"filePath": "/home/init.zip"}, format='json') + {"filePath": os.path.abspath(__file__)}, format='json') self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code, response.content) + self.assertEqual(1, len(WFPlanModel.objects.filter(deployed_id="3"))) + + @mock.patch.object(restcall, 'upload_by_msb') + @mock.patch.object(restcall, 'call_req') + def test_force_deploy_workflow(self, mock_call_req, mock_upload_by_msb): + mock_call_req.return_value = [0, json.JSONEncoder().encode({ + "status": "1", + "message": "2" + }), '202'] + mock_upload_by_msb.return_value = [0, json.JSONEncoder().encode({ + "status": "2", + "message": "3", + "deployedId": "4", + "processId": "5" + }), '202'] + WFPlanModel(deployed_id="1", process_id="2", status="3", message="4").save() + response = self.client.post("/api/nslcm/v1/workflow", + {"filePath": os.path.abspath(__file__), "forceDeploy": "True"}, format='json') + self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code, response.content) + self.assertEqual(0, len(WFPlanModel.objects.filter(deployed_id="1"))) + self.assertEqual(1, len(WFPlanModel.objects.filter(deployed_id="4"))) + + def test_deploy_workflow_when_already_deployed(self): + WFPlanModel(deployed_id="1", process_id="2", status="3", message="4").save() + response = self.client.post("/api/nslcm/v1/workflow", + {"filePath": os.path.abspath(__file__)}, format='json') + self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code, response.content) + self.assertEqual({'msg': 'Already deployed.'}, json.loads(response.content)) + + diff --git a/lcm/workflows/views.py b/lcm/workflows/views.py index 92f029fb..41779e87 100644 --- a/lcm/workflows/views.py +++ b/lcm/workflows/views.py @@ -14,14 +14,16 @@ import logging import traceback +import sys from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response -from lcm.pub.database import models +from lcm.pub.database.models import WFPlanModel from lcm.pub.utils.syscomm import fun_name from lcm.pub.utils.values import ignore_case_get +from lcm.pub.msapi import activiti logger = logging.getLogger(__name__) @@ -30,16 +32,31 @@ logger = logging.getLogger(__name__) @api_view(http_method_names=['POST']) def deploy_workflow(request, *args, **kwargs): logger.debug("Enter %s", fun_name()) - file_path = ignore_case_get(request.data, "filePath") - logger.debug("file_path is %s", file_path) - ret = None try: - ret = [0, "TODO"] + file_path = ignore_case_get(request.data, "filePath") + force_deploy = ignore_case_get(request.data, "forceDeploy") + logger.debug("file_path is %s, force_deploy is %s", file_path, force_deploy) + if force_deploy.upper() == "TRUE": + plans = WFPlanModel.objects.filter() + if len(plans) > 0: + activiti.undeploy_workflow(plans[0].deployed_id) + plans.delete() + else: + if WFPlanModel.objects.filter(): + logger.warn("Already deployed.") + return Response(data={'msg': 'Already deployed.'}, status=status.HTTP_202_ACCEPTED) + deploy_info = activiti.deploy_workflow(file_path) + WFPlanModel( + deployed_id=deploy_info["deployedId"], + process_id=deploy_info["processId"], + status=deploy_info["status"], + message=deploy_info["message"], + plan_name="ns_instantiate").save() except: logger.error(traceback.format_exc()) return Response(data={'error': str(sys.exc_info())}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - logger.debug("Leave %s, Return value is %s", fun_name(), ret) - return Response(data=ret[1], status=status.HTTP_202_ACCEPTED) + logger.debug("Leave %s", fun_name()) + return Response(data={'msg': 'OK'}, status=status.HTTP_202_ACCEPTED) |