1 # Copyright (C) 2018 Verizon. All Rights Reserved
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
26 from lcm.nf import const
27 from lcm.pub.database.models import SubscriptionModel
28 from lcm.pub.database.models import VmInstModel
29 from lcm.pub.database.models import NetworkInstModel
30 from lcm.pub.database.models import PortInstModel
31 from lcm.pub.database.models import StorageInstModel
32 from lcm.pub.database.models import VNFCInstModel
33 from lcm.pub.database.models import NfInstModel
34 from lcm.pub.utils.timeutil import now_time
35 from lcm.pub.utils.enumutil import enum
37 logger = logging.getLogger(__name__)
39 rest_no_auth, rest_oneway_auth, rest_bothway_auth = 0, 1, 2
40 HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED = '200', '201', '204', '202'
41 status_ok_list = [HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED]
42 HTTP_404_NOTFOUND, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUEST = '404', '403', '401', '400'
44 lCM_OP_OCC="VnfLcmOperationOccurrenceNotification",
45 CREATION="VnfIdentifierCreationNotification",
46 DELETION="VnfIdentifierDeletionNotification"
50 class NotificationsUtil(object):
51 def send_notification(self, notification):
52 logger.info("Send Notifications to the callbackUri")
54 "operationState": "operation_states",
55 "operation": "operation_types",
56 "vnfInstanceId": "vnf_instance_filter"
58 subscriptions_filter = {v + "__contains": notification[k] for k, v in list(filters.items())}
60 subscriptions = SubscriptionModel.objects.filter(**subscriptions_filter)
61 if not subscriptions.exists():
62 logger.info("No subscriptions created for the filters %s" % notification)
64 logger.info("Start sending notifications")
65 for subscription in subscriptions:
67 notification["subscriptionId"] = subscription.subscription_id
68 notification['_links']['subscription'] = {
69 'href': '/api/vnflcm/v1/subscriptions/%s' % subscription.subscription_id
71 callbackUri = subscription.callback_uri
72 auth_info = json.loads(subscription.auth_info)
73 if const.BASIC in auth_info["authType"]:
75 self.post_notification(callbackUri, notification)
76 except Exception as e:
77 logger.error("Failed to post notification: %s", e.args[0])
79 def post_notification(self, callbackUri, notification):
80 logger.info("Sending notification to %s", callbackUri)
81 resp = self.call_req(callbackUri, "", "", "POST", json.dumps(notification))
83 logger.error('Status code is %s, detail is %s.', resp[2], resp[1])
85 def call_req(self, full_url, user, passwd, method, content=''):
86 callid = str(uuid.uuid1())
87 logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s')" % (
88 callid, full_url, user, passwd, rest_no_auth, method, content))
93 headers = {'content-type': 'application/json', 'accept': 'application/json'}
95 headers['Authorization'] = 'Basic %s' % base64.b64encode(
96 bytes('%s:%s' % (user, passwd), "utf-8")).decode()
98 for retry_times in range(3):
99 http = httplib2.Http(ca_certs=ca_certs, disable_ssl_certificate_validation=True)
100 http.follow_all_redirects = True
102 resp, resp_content = http.request(full_url, method=method.upper(), body=content, headers=headers)
103 logger.info("resp=%s,resp_content=%s" % (resp, resp_content))
104 resp_status, resp_body = resp['status'], resp_content.decode('UTF-8')
105 resp_Location = resp.get('Location', "")
106 logger.debug("[%s][%d]status=%s,resp_body=%s)" % (callid, retry_times, resp_status, resp_body))
107 if resp_status in status_ok_list:
108 ret = [0, resp_body, resp_status, resp_Location]
110 ret = [1, resp_body, resp_status, resp_Location]
112 except Exception as ex:
113 if 'httplib.ResponseNotReady' in str(sys.exc_info()):
114 logger.debug("retry_times=%d", retry_times)
115 logger.error(traceback.format_exc())
116 ret = [1, "Unable to connect to %s" % full_url, resp_status, resp_Location]
119 except urllib.error.URLError as err:
120 ret = [2, str(err), resp_status, resp_Location]
121 except Exception as ex:
122 logger.error(traceback.format_exc())
123 logger.error("[%s]ret=%s" % (callid, str(sys.exc_info())))
124 res_info = str(sys.exc_info())
125 if 'httplib.ResponseNotReady' in res_info:
126 res_info = "The URL[%s] request failed or is not responding." % full_url
127 ret = [3, res_info, resp_status, resp_Location]
129 logger.error(traceback.format_exc())
130 ret = [4, str(sys.exc_info()), resp_status, resp_Location]
132 logger.debug("[%s]ret=%s" % (callid, str(ret)))
136 def set_affected_vnfcs(affected_vnfcs, nfinstid, changetype):
137 vnfcs = VNFCInstModel.objects.filter(instid=nfinstid)
141 vm = VmInstModel.objects.filter(vmid=vnfc.vmid)
144 'vimConnectionId': vm[0].vimid,
145 'resourceId': vm[0].resourceid,
146 'resourceProviderId': vm[0].vmname, # TODO: is resourceName mapped to resourceProviderId?
147 'vimLevelResourceType': 'vm'
149 affected_vnfcs.append({
150 'id': vnfc.vnfcinstanceid,
152 'changeType': changetype,
153 'computeResource': vm_resource
155 logger.debug("affected_vnfcs=%s", affected_vnfcs)
156 return affected_vnfcs
159 def set_affected_vls(affected_vls, nfinstid, changetype):
160 networks = NetworkInstModel.objects.filter(instid=nfinstid)
161 for network in networks:
163 'vimConnectionId': network.vimid,
164 'resourceId': network.resourceid,
165 'resourceProviderId': network.name, # TODO: is resourceName mapped to resourceProviderId?
166 'vimLevelResourceType': 'network'
168 affected_vls.append({
169 'id': network.networkid,
170 'virtualLinkDescId': network.nodeId,
171 'changeType': changetype,
172 'networkResource': network_resource
174 logger.debug("affected_vls=%s", affected_vls)
177 def set_ext_connectivity(ext_connectivity, nfinstid):
178 ext_connectivity_map = {}
179 ports = PortInstModel.objects.filter(instid=nfinstid)
181 if port.networkid not in ext_connectivity_map:
182 ext_connectivity_map[port.networkid] = []
183 ext_connectivity_map[port.networkid].append({
184 'id': port.portid, # TODO: port.portid or port.nodeid?
186 'vimConnectionId': port.vimid,
187 'resourceId': port.resourceid,
188 'resourceProviderId': port.name, # TODO: is resourceName mapped to resourceProviderId?
189 'vimLevelResourceType': 'port'
191 'cpInstanceId': port.portid # TODO: port.cpinstanceid is not initiated when create port resource.
193 for network_id, ext_link_ports in list(ext_connectivity_map.items()):
194 networks = NetworkInstModel.objects.filter(networkid=network_id)
195 net_name = networks[0].name if networks else network_id
197 'vimConnectionId': ext_link_ports[0]['resourceHandle']['vimConnectionId'],
198 'resourceId': network_id,
199 'resourceProviderId': net_name, # TODO: is resourceName mapped to resourceProviderId?
200 'vimLevelResourceType': 'network'
202 ext_connectivity.append({
204 'resourceHandle': network_resource,
205 'extLinkPorts': ext_link_ports
207 logger.debug("ext_connectivity=%s", ext_connectivity)
210 def set_affected_vss(affected_vss, nfinstid, changetype):
211 vss = StorageInstModel.objects.filter(instid=nfinstid)
213 affected_vss.append({
215 'virtualStorageDescId': vs.nodeId,
216 'changeType': changetype,
218 'vimConnectionId': vs.vimid,
219 'resourceId': vs.resourceid,
220 'resourceProviderId': vs.name, # TODO: is resourceName mapped to resourceProviderId?
221 'vimLevelResourceType': 'volume'
224 logger.debug("affected_vss=%s", affected_vss)
227 def get_notification_status(operation_state):
228 if operation_state == const.OPERATION_STATE_TYPE.STARTING:
229 return const.LCM_NOTIFICATION_STATUS.START
230 return const.LCM_NOTIFICATION_STATUS.RESULT
233 def prepare_notification(nfinstid, jobid, operation, operation_state):
234 logger.info('Start to prepare notification')
235 notification_content = {
236 'id': str(uuid.uuid4()), # shall be the same if sent multiple times due to multiple subscriptions.
237 'notificationType': NOTIFY_TYPE.lCM_OP_OCC,
238 # set 'subscriptionId' after filtering for subscribers
239 'timeStamp': now_time(),
240 'notificationStatus': get_notification_status(operation_state),
241 'operationState': operation_state,
242 'vnfInstanceId': nfinstid,
243 'operation': operation,
244 'isAutomaticInvocation': False,
245 'vnfLcmOpOccId': jobid,
247 'affectedVirtualLinks': [],
248 'affectedVirtualStorages': [],
249 'changedExtConnectivity': [],
253 'href': '%s/vnf_instances/%s' % (const.URL_PREFIX, nfinstid)
256 'href': '%s/vnf_lcm_op_occs/%s' % (const.URL_PREFIX, jobid)
260 nfInsts = NfInstModel.objects.filter(nfinstid=nfinstid)
262 notification_content['vnfmInstId'] = nfInsts[0].vnfminstid
264 notification_content['vnfmInstId'] = "1"
265 return notification_content
268 def prepare_notification_data(nfinstid, jobid, changetype, operation):
269 data = prepare_notification(
273 operation_state=const.OPERATION_STATE_TYPE.COMPLETED
276 set_affected_vnfcs(data['affectedVnfcs'], nfinstid, changetype)
277 set_affected_vls(data['affectedVirtualLinks'], nfinstid, changetype)
278 set_affected_vss(data['affectedVirtualStorages'], nfinstid, changetype)
279 set_ext_connectivity(data['changedExtConnectivity'], nfinstid)
281 logger.debug('Notification content: %s' % data)
285 def prepare_vnf_identifier_notification(notify_type, nfinstid):
287 "id": str(uuid.uuid4()), # shall be the same if sent multiple times due to multiple subscriptions.
288 "notificationType": notify_type,
289 "timeStamp": now_time(),
290 "vnfInstanceId": nfinstid,
293 'href': '%s/vnf_instances/%s' % (const.URL_PREFIX, nfinstid)
298 logger.debug('Vnf Identifier Notification: %s' % data)