def test_returns_422_if_async_required_but_not_supported(self): self.broker.update.side_effect = errors.ErrAsyncRequired() response = self.client.patch( "/v2/service_instances/abc?accepts_incomplete=false", data=json.dumps({ "service_id": "service-guid-here", "plan_id": "plan-guid-here", "previous_values": { "plan_id": "old-plan-guid-here", "service_id": "service-guid-here", "organization_id": "org-guid-here", "space_id": "space-guid-here" } }), headers={ 'X-Broker-Api-Version': '2.13', 'Authorization': self.auth_header }) self.assertEqual(response.status_code, http.HTTPStatus.UNPROCESSABLE_ENTITY) self.assertEqual(response.json, dict( error="AsyncRequired", description="This service plan requires client support for asynchronous service operations." ))
def test_returns_422_if_async_required_but_not_supported(self): self.broker.provision.side_effect = errors.ErrAsyncRequired() response = self.client.put("/v2/service_instances/abc", data=json.dumps({ "service_id": "service-guid-here", "plan_id": "plan-guid-here", "organization_guid": "org-guid-here", "space_guid": "space-guid-here", }), headers={ 'X-Broker-Api-Version': '2.13', 'Content-Type': 'application/json', 'Authorization': self.auth_header }) self.assertEqual(response.status_code, http.HTTPStatus.UNPROCESSABLE_ENTITY) self.assertEqual( response.json, dict( error="AsyncRequired", description= "This service plan requires client support for asynchronous service operations." ))
def deprovision( self, instance_id: str, details: DeprovisionDetails, async_allowed: bool, **kwargs, ) -> DeprovisionServiceSpec: if not async_allowed: raise errors.ErrAsyncRequired() instance = ServiceInstance.query.get(instance_id) if not instance: raise errors.ErrInstanceDoesNotExist operation = Operation( state=Operation.States.IN_PROGRESS.value, service_instance=instance, action=Operation.Actions.DEPROVISION.value, ) db.session.add(operation) db.session.commit() queue_all_deprovision_tasks_for_operation( operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) return DeprovisionServiceSpec(is_async=True, operation=str(operation.id))
def unbind(self, instance_id: str, binding_id: str, details: UnbindDetails, async_allowed: bool, **kwargs) -> UnbindSpec: if not async_allowed: raise errors.ErrAsyncRequired() instance = self.service_instances.get(instance_id, {}) if instance and instance.get('state') == self.BOUND: instance['state'] = self.UNBINDING return UnbindSpec(True, 'unbind')
def bind(self, instance_id: str, binding_id: str, details: BindDetails, async_allowed: bool, **kwargs) -> Binding: if not async_allowed: raise errors.ErrAsyncRequired() instance = self.service_instances.get(instance_id, {}) if instance and instance.get('state') == self.CREATED: instance['state'] = self.BINDING return Binding(BindState.IS_ASYNC, operation='bind')
def provision(self, instance_id: str, details: ProvisionDetails, async_allowed: bool, **kwargs) -> ProvisionedServiceSpec: self.logger.info("starting provision request") if not async_allowed: raise errors.ErrAsyncRequired() params = details.parameters or {} domain_names = parse_domain_options(params) if not domain_names: raise errors.ErrBadRequest("'domains' parameter required.") self.logger.info("validating CNAMEs") validators.CNAME(domain_names).validate() self.logger.info("validating unique domains") if not config.IGNORE_DUPLICATE_DOMAINS: validators.UniqueDomains(domain_names).validate() if details.plan_id == CDN_PLAN_ID: instance = provision_cdn_instance(instance_id, domain_names, params) queue = queue_all_cdn_provision_tasks_for_operation elif details.plan_id == ALB_PLAN_ID: instance = ALBServiceInstance(id=instance_id, domain_names=domain_names) queue = queue_all_alb_provision_tasks_for_operation elif details.plan_id == MIGRATION_PLAN_ID: instance = MigrationServiceInstance(id=instance_id, domain_names=domain_names) db.session.add(instance) db.session.commit() return ProvisionedServiceSpec( state=ProvisionState.SUCCESSFUL_CREATED) else: raise NotImplementedError() self.logger.info("setting origin hostname") self.logger.info("creating operation") operation = Operation( state=Operation.States.IN_PROGRESS.value, service_instance=instance, action=Operation.Actions.PROVISION.value, step_description="Queuing tasks", ) db.session.add(instance) db.session.add(operation) self.logger.info("committing db session") db.session.commit() self.logger.info("queueing tasks") queue(operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) self.logger.info("all done. Returning provisioned service spec") return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC, operation=str(operation.id))
def deprovision(self, instance_id: str, details: DeprovisionDetails, async_allowed: bool, **kwargs) -> DeprovisionServiceSpec: if not async_allowed: raise errors.ErrAsyncRequired() instance = self.service_instances.get(instance_id) if instance is None: raise errors.ErrInstanceDoesNotExist() if instance.get('state') == self.CREATED: instance['state'] = self.DELETING return DeprovisionServiceSpec(True, 'deprovision')
def provision(self, instance_id: str, details: ProvisionDetails, async_allowed: bool, **kwargs) -> ProvisionedServiceSpec: if not async_allowed: raise errors.ErrAsyncRequired() self.service_instances[instance_id] = { 'provision_details': details, 'state': self.CREATING } return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC, operation='provision')
def test_returns_422_if_async_not_supported_but_required(self): self.broker.deprovision.side_effect = errors.ErrAsyncRequired() response = self.client.delete( "/v2/service_instances/abc?service_id=service-guid-here&plan_id=plan-id-here", headers={ 'X-Broker-Api-Version': '2.13', 'Authorization': self.auth_header }) self.assertEqual(response.status_code, http.HTTPStatus.UNPROCESSABLE_ENTITY) self.assertEqual(response.json, dict( error="AsyncRequired", description="This service plan requires client support for asynchronous service operations." ))
def provision(self, instance_id: str, details: ProvisionDetails, async_allowed: bool, **kwargs) -> ProvisionedServiceSpec: self.logger.info("starting provision request") if not async_allowed: raise errors.ErrAsyncRequired() params = details.parameters or {} if params.get("domains"): domain_names = [ d.strip().lower() for d in params["domains"].split(",") ] else: raise errors.ErrBadRequest("'domains' parameter required.") self.logger.info("validating CNAMEs") validators.CNAME(domain_names).validate() self.logger.info("validating unique domains") validators.UniqueDomains(domain_names).validate() instance = ServiceInstance(id=instance_id, domain_names=domain_names) self.logger.info("setting origin hostname") instance.cloudfront_origin_hostname = params.get( "origin", config.DEFAULT_CLOUDFRONT_ORIGIN) instance.cloudfront_origin_path = params.get("path", "") self.logger.info("creating operation") operation = Operation( state=Operation.States.IN_PROGRESS.value, service_instance=instance, action=Operation.Actions.PROVISION.value, ) db.session.add(instance) db.session.add(operation) self.logger.info("committing db session") db.session.commit() self.logger.info("queueing tasks") queue_all_provision_tasks_for_operation( operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) self.logger.info("all done. Returning provisioned service spec") return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC, operation=str(operation.id))
def deprovision( self, instance_id: str, details: DeprovisionDetails, async_allowed: bool, **kwargs, ) -> DeprovisionServiceSpec: if not async_allowed: raise errors.ErrAsyncRequired() instance = ServiceInstance.query.get(instance_id) if not instance: raise errors.ErrInstanceDoesNotExist operation = Operation( state=Operation.States.IN_PROGRESS.value, service_instance=instance, action=Operation.Actions.DEPROVISION.value, step_description="Queuing tasks", ) db.session.add(operation) db.session.commit() if details.plan_id == CDN_PLAN_ID: queue_all_cdn_deprovision_tasks_for_operation( operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) elif details.plan_id == ALB_PLAN_ID: queue_all_alb_deprovision_tasks_for_operation( operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) elif details.plan_id == MIGRATION_PLAN_ID: for o in instance.operations: if o.action == Operation.Actions.UPDATE.value: raise errors.ErrBadRequest( msg="Can't delete migration with update operations") queue_all_migration_deprovision_tasks_for_operation( operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) else: raise NotImplementedError() return DeprovisionServiceSpec(is_async=True, operation=str(operation.id))
def provision(self, instance_id: str, details: ProvisionDetails, async_allowed: bool, **kwargs) -> ProvisionedServiceSpec: if not async_allowed: raise errors.ErrAsyncRequired() if instance_id in self.service_instances.keys(): raise errors.ErrInstanceAlreadyExists() self.service_instances[instance_id] = { 'provision_details': details, 'state': self.CREATING } try: project_name = details.parameters.get('project_name') github_id = details.parameters.get('github_id') github_pass = details.parameters.get('github_pass') email_id = details.parameters.get('email') repo_details = jenkins_utils.provision_job(project_name, github_id, github_pass, email_id) # jenkins_utils.provision_job(githib_url) except Exception as e: print(e) raise errors.ServiceException(e) # repo_details = { # 'html_url' : repo.html_url, # 'clone_url': repo.clone_url, # 'hooks_url': repo.hooks_url, # 'hook_id' : hook.id # } self.service_instances[instance_id] = { 'provision_details': details, 'state': self.CREATED, 'project_name': project_name, 'repo_html_url': repo_details.get('html_url'), 'repo_clone_url': repo_details.get('clone_url'), 'repo_hooks_url': repo_details.get('hooks_url'), 'repo_hook_id': repo_details.get('hook_id') } return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC, operation='provision')
def update(self, instance_id: str, details: UpdateDetails, async_allowed: bool, **kwargs) -> UpdateServiceSpec: if not async_allowed: raise errors.ErrAsyncRequired() params = details.parameters or {} instance = ServiceInstance.query.get(instance_id) if not instance: raise errors.ErrBadRequest("Service instance does not exist") if instance.deactivated_at: raise errors.ErrBadRequest( "Cannot update instance because it was already canceled") if instance.has_active_operations(): raise errors.ErrBadRequest( "Instance has an active operation in progress") domain_names = parse_domain_options(params) noop = True if domain_names is not None: self.logger.info("validating CNAMEs") validators.CNAME(domain_names).validate() self.logger.info("validating unique domains") validators.UniqueDomains(domain_names).validate(instance) noop = noop and (sorted(domain_names) == sorted( instance.domain_names)) if instance.instance_type == "cdn_service_instance" and noop: instance.new_certificate = instance.current_certificate instance.domain_names = domain_names if instance.instance_type == "cdn_service_instance": # N.B. we're using "param" in params rather than # params.get("param") because the OSBAPI spec # requires we do not mess with params that were not # specified, so unset and set to None have different meanings noop = False if details.plan_id != CDN_PLAN_ID: raise ClientError("Updating service plan is not supported") if "origin" in params: if params["origin"]: origin_hostname = params["origin"] validators.Hostname(origin_hostname).validate() else: origin_hostname = config.DEFAULT_CLOUDFRONT_ORIGIN instance.cloudfront_origin_hostname = origin_hostname if "path" in params: if params["path"]: cloudfront_origin_path = params["path"] else: cloudfront_origin_path = "" instance.cloudfront_origin_path = cloudfront_origin_path if "forward_cookies" in params: forward_cookie_policy, forwarded_cookies = parse_cookie_options( params) instance.forward_cookie_policy = forward_cookie_policy instance.forwarded_cookies = forwarded_cookies if "forward_headers" in params: forwarded_headers = parse_header_options(params) else: # .copy() so sqlalchemy recognizes the field has changed forwarded_headers = instance.forwarded_headers.copy() if instance.cloudfront_origin_hostname == config.DEFAULT_CLOUDFRONT_ORIGIN: forwarded_headers.append("HOST") forwarded_headers = normalize_header_list(forwarded_headers) instance.forwarded_headers = forwarded_headers if "insecure_origin" in params: origin_protocol_policy = "https-only" if params["insecure_origin"]: if (instance.cloudfront_origin_hostname == config.DEFAULT_CLOUDFRONT_ORIGIN): raise errors.ErrBadRequest( "Cannot use insecure_origin with default origin") origin_protocol_policy = "http-only" instance.origin_protocol_policy = origin_protocol_policy if "error_responses" in params: instance.error_responses = params["error_responses"] validators.ErrorResponseConfig( instance.error_responses).validate() queue = queue_all_cdn_update_tasks_for_operation elif instance.instance_type == "alb_service_instance": if details.plan_id != ALB_PLAN_ID: raise ClientError("Updating service plan is not supported") queue = queue_all_alb_update_tasks_for_operation elif instance.instance_type == "migration_service_instance": if details.plan_id == CDN_PLAN_ID: noop = False validate_migration_to_cdn_params(params) instance = change_instance_type(instance, CDNServiceInstance, db.session) update_cdn_params_for_migration(instance, params) db.session.add(instance.current_certificate) queue = queue_all_cdn_broker_migration_tasks_for_operation elif details.plan_id == ALB_PLAN_ID: noop = False validate_migration_to_alb_params(params) instance = change_instance_type(instance, ALBServiceInstance, db.session) update_alb_params_for_migration(instance, params) db.session.add(instance.current_certificate) queue = queue_all_domain_broker_migration_tasks_for_operation else: raise ClientError( "Updating to this service plan is not supported") if noop: return UpdateServiceSpec(False) operation = Operation( state=Operation.States.IN_PROGRESS.value, service_instance=instance, action=Operation.Actions.UPDATE.value, step_description="Queuing tasks", ) db.session.add(operation) db.session.add(instance) db.session.commit() queue(operation.id, cf_logging.FRAMEWORK.context.get_correlation_id()) return UpdateServiceSpec(True, operation=str(operation.id))