X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fengine.py;h=38bbe776abe4975c3fcd77e0cd488d7c0a8a35ef;hp=f8c74041bc11ef59fcd14256891b5749f8446a98;hb=refs%2Fheads%2Fv3.1;hpb=4a946e4dbbfdd44f2c9af197aaa27f1600abaeca diff --git a/osm_nbi/engine.py b/osm_nbi/engine.py index f8c7404..38bbe77 100644 --- a/osm_nbi/engine.py +++ b/osm_nbi/engine.py @@ -277,48 +277,41 @@ class Engine(object): elif item == "nsrs": pass - elif item == "vims" or item == "sdns": + elif item == "vim_accounts" or item == "sdns": if self.db.get_one(item, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False): raise EngineException("name '{}' already exist for {}".format(indata["name"], item), HTTPStatus.CONFLICT) - - def _format_new_data(self, session, item, indata, admin=None): + def _format_new_data(self, session, item, indata): now = time() if not "_admin" in indata: indata["_admin"] = {} indata["_admin"]["created"] = now indata["_admin"]["modified"] = now if item == "users": - _id = indata["username"] + indata["_id"] = indata["username"] salt = uuid4().hex indata["_admin"]["salt"] = salt indata["password"] = sha256(indata["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest() elif item == "projects": - _id = indata["name"] + indata["_id"] = indata["name"] else: - _id = None - storage = None - if admin: - _id = admin.get("_id") - storage = admin.get("storage") - if not _id: - _id = str(uuid4()) - if item in ("vnfds", "nsds"): + if not indata.get("_id"): + indata["_id"] = str(uuid4()) + if item in ("vnfds", "nsds", "nsrs"): if not indata["_admin"].get("projects_read"): indata["_admin"]["projects_read"] = [session["project_id"]] if not indata["_admin"].get("projects_write"): indata["_admin"]["projects_write"] = [session["project_id"]] + if item in ("vnfds", "nsds"): indata["_admin"]["onboardingState"] = "CREATED" indata["_admin"]["operationalState"] = "DISABLED" indata["_admin"]["usageSate"] = "NOT_IN_USE" - elif item in ("vims", "sdns"): + if item == "nsrs": + indata["_admin"]["nsState"] = "NOT_INSTANTIATED" + if item in ("vim_accounts", "sdns"): indata["_admin"]["operationalState"] = "PROCESSING" - if storage: - indata["_admin"]["storage"] = storage - indata["_id"] = _id - def upload_content(self, session, item, _id, indata, kwargs, headers): """ Used for receiving content by chunks (with a transaction_id header and/or gzip file. It will store and extract) @@ -457,6 +450,8 @@ class Engine(object): HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE) except IOError as e: raise EngineException("invalid upload transaction sequence: '{}'".format(e), HTTPStatus.BAD_REQUEST) + except tarfile.ReadError as e: + raise EngineException("invalid file content {}".format(e), HTTPStatus.BAD_REQUEST) except (ValueError, yaml.YAMLError) as e: raise EngineException(error_text + str(e)) finally: @@ -495,98 +490,169 @@ class Engine(object): "nsd-name-ref": nsd["name"], "operational-events": [], # "id", "timestamp", "description", "event", "nsd-ref": nsd["id"], + "instantiate_params": ns_request, "ns-instance-config-ref": _id, "id": _id, + "_id": _id, # "input-parameter": xpath, value, "ssh-authorized-key": ns_request.get("key-pair-ref"), } ns_request["nsr_id"] = _id - return nsr_descriptor, _id + return nsr_descriptor + + @staticmethod + def _update_descriptor(desc, kwargs): + """ + Update descriptor with the kwargs + :param kwargs: + :return: + """ + if not kwargs: + return + try: + for k, v in kwargs.items(): + update_content = content + kitem_old = None + klist = k.split(".") + for kitem in klist: + if kitem_old is not None: + update_content = update_content[kitem_old] + if isinstance(update_content, dict): + kitem_old = kitem + elif isinstance(update_content, list): + kitem_old = int(kitem) + else: + raise EngineException( + "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem)) + update_content[kitem_old] = v + except KeyError: + raise EngineException( + "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old)) + except ValueError: + raise EngineException("Invalid query string '{}'. Expected integer index list instead of '{}'".format( + k, kitem)) + except IndexError: + raise EngineException( + "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old)) def new_item(self, session, item, indata={}, kwargs=None, headers={}): """ Creates a new entry into database. For nsds and vnfds it creates an almost empty DISABLED entry, that must be completed with a call to method upload_content :param session: contains the used login username and working project - :param item: it can be: users, projects, vims, sdns, nsrs, nsds, vnfds + :param item: it can be: users, projects, vim_accounts, sdns, nsrs, nsds, vnfds :param indata: data to be inserted :param kwargs: used to override the indata descriptor :param headers: http request headers :return: _id, transaction_id: identity of the inserted data. or transaction_id if Content-Range is used """ - transaction = None + try: + item_envelop = item + if item in ("nsds", "vnfds"): + item_envelop = "userDefinedData" + content = self._remove_envelop(item_envelop, indata) - item_envelop = item - if item in ("nsds", "vnfds"): - item_envelop = "userDefinedData" - content = self._remove_envelop(item_envelop, indata) + # Override descriptor with query string kwargs + self._update_descriptor(content, kwargs) + if not indata and item not in ("nsds", "vnfds"): + raise EngineException("Empty payload") - # Override descriptor with query string kwargs - if kwargs: - try: - for k, v in kwargs.items(): - update_content = content - kitem_old = None - klist = k.split(".") - for kitem in klist: - if kitem_old is not None: - update_content = update_content[kitem_old] - if isinstance(update_content, dict): - kitem_old = kitem - elif isinstance(update_content, list): - kitem_old = int(kitem) - else: - raise EngineException( - "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem)) - update_content[kitem_old] = v - except KeyError: - raise EngineException( - "Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old)) - except ValueError: - raise EngineException("Invalid query string '{}'. Expected integer index list instead of '{}'".format( - k, kitem)) - except IndexError: - raise EngineException( - "Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old)) - try: validate_input(content, item, new=True) + + if item == "nsrs": + # in this case the imput descriptor is not the data to be stored + ns_request = content + content = self.new_nsr(session, ns_request) + + self._validate_new_data(session, item_envelop, content) + if item in ("nsds", "vnfds"): + content = {"_admin": {"userDefinedData": content}} + self._format_new_data(session, item, content) + _id = self.db.create(item, content) + if item == "nsrs": + pass + # self.msg.write("ns", "created", _id) # sending just for information. + elif item == "vim_accounts": + msg_data = self.db.get_one(item, {"_id": _id}) + msg_data.pop("_admin", None) + self.msg.write("vim_account", "create", msg_data) + elif item == "sdns": + msg_data = self.db.get_one(item, {"_id": _id}) + msg_data.pop("_admin", None) + self.msg.write("sdn", "create", msg_data) + return _id except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) - if not indata and item not in ("nsds", "vnfds"): - raise EngineException("Empty payload") + def new_nslcmop(self, session, nsInstanceId, action, params): + now = time() + _id = str(uuid4()) + nslcmop = { + "id": _id, + "_id": _id, + "operationState": "PROCESSING", # COMPLETED,PARTIALLY_COMPLETED,FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK + "statusEnteredTime": now, + "nsInstanceId": nsInstanceId, + "lcmOperationType": action, + "startTime": now, + "isAutomaticInvocation": False, + "operationParams": params, + "isCancelPending": False, + "links": { + "self": "/osm/nslcm/v1/ns_lcm_op_occs/" + _id, + "nsInstance": "/osm/nslcm/v1/ns_instances/" + nsInstanceId, + } + } + return nslcmop - if item == "nsrs": - # in this case the imput descriptor is not the data to be stored - ns_request = content - content, _id = self.new_nsr(session, ns_request) - transaction = {"_id": _id} - - self._validate_new_data(session, item_envelop, content) - if item in ("nsds", "vnfds"): - content = {"_admin": {"userDefinedData": content}} - self._format_new_data(session, item, content, transaction) - _id = self.db.create(item, content) - if item == "nsrs": - self.msg.write("ns", "create", _id) - elif item == "vims": - msg_data = self.db.get_one(item, {"_id": _id}) - msg_data.pop("_admin", None) - self.msg.write("vim_account", "create", msg_data) - elif item == "sdns": - msg_data = self.db.get_one(item, {"_id": _id}) - msg_data.pop("_admin", None) - self.msg.write("sdn", "create", msg_data) - return _id + def ns_action(self, session, nsInstanceId, action, indata, kwargs=None): + """ + Performs a new action over a ns + :param session: contains the used login username and working project + :param nsInstanceId: _id of the nsr to perform the action + :param action: it can be: instantiate, terminate, action, TODO: update, heal + :param indata: descriptor with the parameters of the action + :param kwargs: used to override the indata descriptor + :return: id of the nslcmops + """ + try: + # Override descriptor with query string kwargs + self._update_descriptor(indata, kwargs) + validate_input(indata, "ns_" + action, new=True) + # get ns from nsr_id + nsr = self.get_item(session, "nsrs", nsInstanceId) + if not nsr["_admin"].get("nsState") or nsr["_admin"]["nsState"] == "NOT_INSTANTIATED": + if action == "terminate" and indata.get("autoremove"): + # NSR must be deleted + return self.del_item(session, "nsrs", nsInstanceId) + if action != "instantiate": + raise EngineException("ns_instance '{}' cannot be '{}' because it is not instantiated".format( + nsInstanceId, action), HTTPStatus.CONFLICT) + else: + if action == "instantiate" and not indata.get("force"): + raise EngineException("ns_instance '{}' cannot be '{}' because it is already instantiated".format( + nsInstanceId, action), HTTPStatus.CONFLICT) + indata["nsInstanceId"] = nsInstanceId + # TODO + nslcmop = self.new_nslcmop(session, nsInstanceId, action, indata) + self._format_new_data(session, "nslcmops", nslcmop) + _id = self.db.create("nslcmops", nslcmop) + indata["_id"] = _id + self.msg.write("ns", action, nslcmop) + return _id + except ValidationError as e: + raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) + # except DbException as e: + # raise EngineException("Cannot get ns_instance '{}': {}".format(e), HTTPStatus.NOT_FOUND) def _add_read_filter(self, session, item, filter): if session["project_id"] == "admin": # allows all return filter if item == "users": filter["username"] = session["username"] - elif item == "vnfds" or item == "nsds": + elif item in ("vnfds", "nsds", "nsrs"): filter["_admin.projects_read.cont"] = ["ANY", session["project_id"]] def _add_delete_filter(self, session, item, filter): @@ -698,26 +764,35 @@ class Engine(object): self._add_read_filter(session, item, filter) return self.db.del_list(item, filter) - def del_item(self, session, item, _id): + def del_item(self, session, item, _id, force=False): """ Get complete information on an items :param session: contains the used login username and working project :param item: it can be: users, projects, vnfds, nsds, ... :param _id: server id of the item - :return: dictionary, raise exception if not found. + :param force: indicates if deletion must be forced in case of conflict + :return: dictionary with deleted item _id. It raises exception if not found. """ # TODO add admin to filter, validate rights # data = self.get_item(item, _id) filter = {"_id": _id} self._add_delete_filter(session, item, filter) - if item in ("nsrs", "vims", "sdns"): + if item == "nsrs": + nsr = self.db.get_one(item, filter) + if nsr["_admin"]["nsState"] == "INSTANTIATED" and not force: + raise EngineException("nsr '{}' cannot be deleted because it is in 'INSTANTIATED' state. " + "Launch 'terminate' action first; or force deletion".format(_id), + http_code=HTTPStatus.CONFLICT) + v = self.db.del_one(item, {"_id": _id}) + self.db.del_list("nslcmops", {"nsInstanceId": _id}) + self.msg.write("ns", "deleted", {"_id": _id}) + return v + if item in ("vim_accounts", "sdns"): desc = self.db.get_one(item, filter) desc["_admin"]["to_delete"] = True self.db.replace(item, _id, desc) # TODO change to set_one - if item == "nsrs": - self.msg.write("ns", "delete", _id) - elif item == "vims": + if item == "vim_accounts": self.msg.write("vim_account", "delete", {"_id": _id}) elif item == "sdns": self.msg.write("sdn", "delete", {"_id": _id}) @@ -755,7 +830,7 @@ class Engine(object): If empty, it creates a new user admin/admin at 'users' and a new entry at 'version' :return: None if ok, exception if error or if the version is different. """ - version = self.db.get_one("versions", fail_on_empty=False, fail_on_more=False) + version = self.db.get_one("version", fail_on_empty=False, fail_on_more=False) if not version: # create user admin self.create_admin() @@ -817,10 +892,10 @@ class Engine(object): self._validate_new_data(session, item, content, id) # self._format_new_data(session, item, content) self.db.replace(item, id, content) - if item in ("vims", "sdns"): + if item in ("vim_accounts", "sdns"): indata.pop("_admin", None) indata["_id"] = id - if item == "vims": + if item == "vim_accounts": self.msg.write("vim_account", "edit", indata) elif item == "sdns": self.msg.write("sdn", "edit", indata)