def process_request(self, request): # type: (Fulfillment) -> Union[ActivationTemplateResponse, ActivationTileResponse] if request.needs_migration(): # Skip request if it needs migration (migration is performed by an external service) logger.info( 'Skipping request {} because it needs migration.'.format( request.id)) raise SkipRequest() else: logger.info( 'Processing request {} for contract {}, product {}, marketplace {}' .format(request.id, request.contract.id, request.asset.product.name, request.marketplace.name)) # Custom logic if request.type == 'purchase': for item in request.asset.items: if item.quantity > 100000: raise FailRequest( 'Is not possible to purchase product in such quantities' ) for param in request.asset.params: if param.name == 'email' and not param.value: param.value_error = 'Email address has not been provided, ' \ 'please provide one' raise InquireRequest(params=[param]) # Find a param by its id param = request.asset.get_param_by_id('purchase_id') if param: param.value = '...' # We can assign the id given by the external service here self.update_parameters( request.id, [param]) # Update param on the platform else: raise FailRequest( 'The asset is expected to have a "purchase_id" param.') # Approve by Template return ActivationTemplateResponse('TL-497-535-242') # Or # return TemplateResource().get(pk='TEMPLATE_ID') # Approve by ActivationTile # return ActivationTileResponse('\n # Welcome to Fallball!\n\nYes, you decided ' # 'to have an account in our amazing service!') # Or # return TemplateResource().render(pk='TEMPLATE_ID', request_id=request.id) elif request.type == 'change': # Fail raise FailRequest() else: # Skip request raise SkipRequest()
def process_request(self, request): # type: (Fulfillment) -> Union[ActivationTemplateResponse, ActivationTileResponse] if request.needs_migration(): # Skip request if it needs migration (migration is performed by an external service) logger.info( 'Skipping request {} because it needs migration.'.format( request.id)) raise SkipRequest() else: logger.info( 'Processing request {} for contract {}, product {}, marketplace {}' .format(request.id, request.contract.id, request.asset.product.name, request.marketplace.name)) # Custom logic if request.type == 'purchase': for item in request.asset.items: if item.quantity > 100000: raise FailRequest( message='Is Not possible to purchase product') for param in request.asset.params: if param.name == 'email' and not param.value: param.value_error = 'Email address has not been provided, ' \ 'please provide one' raise InquireRequest(params=[param]) # Approve by ActivationTile return ActivationTileResponse( '\n # Welcome to Fallball!\n\nYes, you decided ' 'to have an account in our amazing service!') # Or # return TemplateResource().render(pk='TEMPLATE_ID', request_id=request.id) # Approve by Template # return ActivationTemplateResponse('TL-497-535-242') # Or # return TemplateResource().get(pk='TEMPLATE_ID') elif request.type == 'change': # Fail raise FailRequest() else: # Skip request raise SkipRequest()
def check_login(self, username): try: method = '/users/check_login?username='******'GET', method) except Exception as err: if err.args[0]['code'] == 409: raise InquireRequest(err.args[0]['message']) else: raise FailRequest(err.args[0]['message'])
def get_quota(item, error=FailRequest("ERROR: REQUESTED LIMITS ARE HIGHER THEN HARD LIMITS")): if item is None: return 0 quantity = item.quantity item_limit = get_item_limit(item) if item_limit >= 0: if quantity > item_limit: raise error if quantity < 0: quantity = item_limit return quantity
def __purchase(self, req): logger.info("PURCHASE") setattr(self, 'tenant', None) login_name = req.asset.get_param_by_id('customer_admin').value # Check if logged user exists self.service.check_login(login_name) try: # Create customer account logger.info("Creating tenant...") tenant_created = self.service.provision_customer( req.id, self.get_tier_config(req.asset.tiers.tier1.id, self.config.products[0]).get_param_by_id( 'tenant_id').value) logger.debug(tenant_created) setattr(self, 'tenant', tenant_created['id']) # Set offering based in sku logger.info("Setting offerings...") self.service.set_customer_offerings( tenant_created['id'], self.get_tier_config(req.asset.tiers.tier1.id, self.config.products[0]).get_param_by_id( 'tenant_id').value, [ Utils.sku_to_offering_item(item) for item in req.asset.items if item.quantity > 0 ]) # Create admin for user account logger.info("Creating admin...") admin_created = self.service.create_admin(tenant_created, login_name) logger.debug(admin_created) # Create access policies for created user self.service.access_policies(admin_created, tenant_created) # Send activation email self.service.send_email(login_name, admin_created) params = [ Param(id='customer_tenant_id', value=str(tenant_created['id'])), ] self.update_parameters(req.id, params) logger.info("PURCHASE END") except Exception as err: if self.tenant: logger.error("Rollback tenant creation") suspended = self.service.edit(self.tenant, { 'enabled': False, 'version': 1 }) self.service.remove(self.tenant, suspended['version'], {'version': suspended['version']}) raise FailRequest(err)
def _create_subscription(self, automation, req): try: # Preparing payload for Create Subscription Vendor API data = self._parse_subscription(req) # Get the Vendor API credentials. # The location to save Vendor API credentials can be as desired. Customize to fetch request accordingly config_file = Utils.get_config_file() # Initiating the API client api_client = APIClient(config_file['partnerConfig']['apiUrl'], config_file['partnerConfig']['apiKey']) # Send payload (request) to make the Vendor API call for creating subscription subscription_info = api_client.create_subscription(data=data) # Check if the API call was successful and save the response request in Connect # Change 'tenantId' with relevant key from your response json if subscription_info['tenantId']: # Save the info in the response as fulfilment parameters for Subscription in Connect # The id of param should match with the id of one of the Product's fulfilment parameter in Connect. Change the id accordingly params = [ Param(id='subscriptionId', value=subscription_info['tenantId']) ] # Update the fulfilment parameters in Fulfilment Request in Connect with the corresponding value automation.update_parameters(req.id, params) else: # If API call returned error, Raise the concerned action accordingly if subscription_info['errors'][0].get('errorCode') is None \ or subscription_info['errors'][0].get('errorCode') == 'UNKNOWN_ERROR': # Since the error was unknown, the request will be skipped to be attempted again later raise SkipRequest( subscription_info['errors'][0].get('errorMessage')) else: # Fail the Fulfilment request and provide error message in API response raise FailRequest( subscription_info['errors'][0].get('errorMessage')) except FailRequest as err: # Fail the Fulfilment request if any issue encountered in the above try block # Add proper validations and handle boundary and corner cases appropriately to avoid failure. raise err return subscription_info
def _check_update_response(self, automation, operation_result, req): if Utils.get_status_code(operation_result).lower() == 'success': now = datetime.now() params = [ Param(id='creationDate', value=now), ] automation.update_parameters(req.id, params) else: # If API call returned error, Raise the concerned action accordingly if "errors" in operation_result: if operation_result['errors'][0].get('errorCode') is None \ or operation_result['errors'][0].get('errorCode') == 'UNKNOWN_ERROR': # Since the error was unknown, the request will be skipped to be attempted again later raise SkipRequest( operation_result['errors'][0].get('errorMessage')) else: # Fail the Fulfilment Request if any issue encountered in the above try block # Add proper validations and handle boundary and corner cases appropriately to avoid failure. raise FailRequest(message=operation_result['errors'] [0].get('errorMessage')) else: if "error" in operation_result: raise Exception(operation_result['error'])
def process_request(self, request): """Each new Fulfillment is processed by this function""" conf = Config.get_instance() # store all processed request for debug self.fulfillments.append(request) if request.needs_migration(): # Skip request if it needs migration # (migration is performed by an external service) self.logger.info('Skipping request %s because it needs migration.', request.id) raise SkipRequest() param_partner_id = None if not conf.misc['domainCreation']: self.logger.info('Request "%s" needs domain that created manually. ' 'Lookup for domain name in tier1 configuration data...', request.id) partner_data = self.get_tier_partner_data(request.asset.tiers.tier1.id) if partner_data is None: raise SkipRequest('Misconfiguration: there is no "partner_id" parameter in tier1 "%s" config' % request.asset.tiers.tier1.id) elif partner_data.value is None: raise SkipRequest(message='Please specify "partner_id" parameter value in tier1 "%s" config' % request.asset.tiers.tier1.id) param_partner_id = partner_data.value self.logger.info('Got the following domain data from tier1 config: "%s"' % param_partner_id) project = None params = {p.id: p for p in request.asset.params} # get account parameters from Asset param_domain_name = params.get('domain_name') param_domain_id = params.get('domain_id') param_project_id = params.get('project_id') param_user_id = params.get('user_id') self.logger.info("param_partner_id: %s, param_domain_name: %s, param_domain_id: %s, " "param_project_id: %s, param_user_id: %s", param_partner_id, param_domain_name and param_domain_name.value, param_domain_id and param_domain_id.value, param_project_id and param_project_id.value, param_user_id and param_user_id.value) if request.type in ('purchase', 'resume', 'change'): if not conf.misc['domainCreation']: # if domain creation is set to manual, needs to check: # - if domain with such description exists in the cloud, go to next steps # - if not - return nice message and set request status domain = self.get_existing_domain(partner_id=param_partner_id) if domain is None: raise SkipRequest('Request "%s" needs domain that created manually. ' 'Cannot find any domain with description "%s"' % (request.id, param_partner_id)) else: # create domain customer_id = request.asset.tiers.customer.id customer_name = request.asset.tiers.customer.name domain = self.create_or_update_domain( name=customer_id, description=customer_name, domain_id=param_domain_id and param_domain_id.value) # create project project_description = request.asset.id project_name = params.get('project') and params['project'].value or project_description project = self.create_project( project_id=param_project_id and param_project_id.value, name=project_name, domain=domain, description=project_description, enabled=False) # create user user_description = request.asset.id user_name = params.get('user') and params['user'].value or user_description user_password = params.get('password') and params['password'].value or pwgen() user = self.create_user( user_id=param_user_id and param_user_id.value, name=user_name, domain=domain, description=user_description, password=user_password) # check conflicts conflicts = [] if project is None: if params.get('project'): params['project'].value_error = 'This project name is already taken, please choose a different name' params['project'].constraints = None conflicts.append(params['project']) if user is None: if params.get('user'): params['user'].value_error = 'This user name is already taken, please choose a different name' params['user'].constraints = None conflicts.append(params['user']) if conflicts: if user: user.delete() if project: project.delete() raise InquireRequest(params=conflicts) if user is None: raise Exception('Unable to create a user') if project is None: raise Exception('Unable to create a project') # update params (project_id, user_id) params_update = [] if param_domain_name and param_domain_name.value != domain.name: param_domain_name.value = domain.name param_domain_name.constraints = None params_update.append(param_domain_name) if param_domain_id and param_domain_id.value != domain.id: param_domain_id.value = domain.id param_domain_id.constraints = None params_update.append(param_domain_id) if param_project_id and param_project_id.value != project.id: param_project_id.value = project.id param_project_id.constraints = None params_update.append(param_project_id) if param_user_id and param_user_id.value != user.id: param_user_id.value = user.id param_user_id.constraints = None params_update.append(param_user_id) self.update_parameters(request.id, params_update) # assign roles user_roles = ['project_admin'] if conf.misc['imageUpload']: user_roles.append('image_upload') self.assign_user_roles(user, project, roles=user_roles) # configure quotas def get_item_limit(item): limit_param = next((p for p in item.params if p.id == 'item_limit'), None) try: return int(limit_param.value) except Exception: return -1 def get_quota(item, error=FailRequest("ERROR: REQUESTED LIMITS ARE HIGHER THEN HARD LIMITS")): if item is None: return 0 quantity = item.quantity item_limit = get_item_limit(item) if item_limit >= 0: if quantity > item_limit: raise error if quantity < 0: quantity = item_limit return quantity items = {item.mpn.lower(): item for item in request.asset.items} self.logger.info('VIP requested items %r', items) try: # get quota limits from Asset parameters cpu_quota = get_quota(items.get('cpu_limit', items.get("cpu_consumption", None))) ram_quota = get_quota(items.get('ram_limit', items.get("ram_consumption", None))) vol_quota = get_quota(items.get('storage_limit', items.get("storage_consumption", None))) # fail request if basic limits are missing if 0 in (cpu_quota, ram_quota, vol_quota): raise FailRequest("CPU, RAM, and Storage limits cannot be 0") fip_quota = get_quota(items.get('floating_ip_limit', items.get("floating_ip_consumption", None))) lb_quota = get_quota(items.get('lbaas_limit', items.get("lb_consumption", None))) k8s_quota = get_quota(items.get('k8saas_limit', items.get("k8s_consumption", None))) errors = [] updaters = [] def apply_quota(updater, client, quotas): try: u = updater(client, project.id) u.update(quotas) updaters.append(u) except BadQuota as e: errors.append(e.message) try: # update project quotas apply_quota(CinderQuotaUpdater, self.cinder_client, { 'gigabytes_default': vol_quota}) apply_quota(NovaQuotaUpdater, self.nova_client, { 'cores': cpu_quota, 'ram': (ram_quota * 1024 if ram_quota > 0 else ram_quota)}) apply_quota(NeutronQuotaUpdater, self.neutron_client, { 'floatingip': fip_quota}) apply_quota(OctaviaQuotaUpdater, self.octavia_client, { 'load_balancer': lb_quota}) apply_quota(MagnumQuotaUpdater, self.magnum_client, { 'hard_limit': k8s_quota}) if errors: rollback_error = False for u in updaters: try: u.rollback() except Exception: rollback_error = True self.logger.exception("Unable to rollback quotas") if rollback_error: raise Exception('Unable to setup quotas') raise FailRequest('\n'.join(errors)) except Exception as e: self.logger.exception("Unable to setup quotas") for u in updaters: try: u.rollback() except Exception: self.logger.exception("Unable to rollback quotas") raise e except FailRequest: if request.type == 'purchase': # remove project if we fail to process 'purchase' request if project: project.delete() project = None raise except SkipRequest: raise except Exception: self.logger.exception("Unable to setup quotas") raise rv = self.get_answer(request.asset.product.id, 'grant') if not project.enabled: # if project was suspended a long time ago we open new usage # reporting interval setting start_usage_report_time. But if # stop_report_time is not equal last_report_time then there # was no report closing previous usage reporting interval. # So the gap between stop and start will be ignored. stop_report_time = self.get_stop_report_time(request, project) last_report_time, _ = self.get_last_report_time(request, project) if stop_report_time != last_report_time: stop_report_time = '' report_time = datetime.utcnow().replace(microsecond=0) self.keystone_client.projects.update( project, enabled=True, start_usage_report_time=report_time.isoformat() if stop_report_time else '', stop_usage_report_time=stop_report_time) project.update(enabled=True) if rv: return rv elif request.type in ('suspend', 'cancel'): self.suspend_project( request, param_domain_id and param_domain_id.value or None, param_project_id and param_project_id.value or None, param_user_id and param_user_id.value or None, ) # TODO implement automatic cleanup after Asset cancellation try: pid = param_project_id and param_project_id.value or None if request.type == 'cancel' and pid: self.keystone_client.projects.update( pid, description='SCHEDULED FOR DELETE') except Exception: pass return self.get_answer(request.asset.product.id, 'revoke') or '' self.logger.warning("Do not know what to do with such request") raise SkipRequest()
def check_if_downsize(req): if Utils.is_downsize_request(req.asset.items): raise FailRequest(Message.Shared.NOT_ALLOWED_DOWNSIZE)
def process_request(self, req): try: setattr(self, 'tenant', None) if req.type == 'update': raise FailRequest("Update operations are not allowed") logger.info("Begin tier config process:" + req.configuration.id) logger.debug(req) partner_config = Utils.get_partner_data( {'connection': req.configuration.connection.id, 'tier_1': req.configuration.account.external_id, 'tier_2': 'default', 'req_id': req.id}) logger.info( "Validate if req requires Skip according accountExternalIdsConfigRequestSkipPrefix config parameter") if req.configuration.account.external_id.startswith( tuple(partner_config['accountExternalIdsConfigRequestSkipPrefix'])): raise SkipRequest('Skipped by processor configuration') setattr(self, 'service', Service(partner_config, req.configuration.account)) # Check if current logged user exists self.service.check_login(req.get_param_by_id('admin_login_name').value) subs_start = str(time.time()) internal_tag = 'aps-abc-start-prod-date=%s;aps-abc-tier=%s;aps-abc-billing-model=%s' % ( subs_start, '-1', 'per_gb') # Create tenant subscription logger.info("Creating tenant...") tenant_created = self.service.create_tenant(req.id, internal_tag) logger.debug(tenant_created) setattr(self, 'tenant', tenant_created['id']) # Create admin for subscription logger.info("Creating admin...") admin_created = self.service.create_admin(tenant_created, req.get_param_by_id('admin_login_name').value, True) logger.debug(admin_created) # Add access policies for the user logger.info("Adding access policies...") self.service.access_policies(admin_created, tenant_created, True) # Set offerings available for reseller customers logger.info("Set offerings...") self.service.set_offerings(tenant_created) # Send activation email logger.info("Sending email...") self.service.send_email(req.get_param_by_id('admin_login_name').value, admin_created) params = [ Param(id='tenant_id', value=str(tenant_created['id'])), Param(id='admin_id', value=str(admin_created['id'])), Param(id='reseller_customer_id', value=str(internal_tag)), Param(id='billing_date', value=str(subs_start)) ] logger.info("End tier configuration for " + req.configuration.id) self.update_parameters(req.id, params) return ActivationTemplateResponse(partner_config['templates']['t1_template']) except FailRequest as err: self.__rollback() logger.error("Issue while processing Purchase request. Print Error: %s" % err) raise err except SkipRequest as err: logger.info(err) raise err except InquireRequest as err: self.__rollback() if self.tenant: self.service.remove(self.tenant) logger.error("Issue while processing Purchase request. Print Error: %s" % err) raise err except Exception as err: self.__rollback() if self.tenant: self.service.remove(self.tenant) logger.error("Issue while processing Purchase request. Print Error: %s" % err) raise err