Example #1
0
    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."
            ))
Example #3
0
    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))
Example #4
0
    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')
Example #5
0
    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')
Example #6
0
    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))
Example #7
0
    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')
Example #8
0
    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')
Example #9
0
    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."
        ))
Example #10
0
    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))
Example #11
0
    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))
Example #12
0
    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')
Example #13
0
    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))