예제 #1
0
파일: api_views.py 프로젝트: aresti/bryn
    def delete(self, request, team_id, tenant_id, pk):
        """
        Instance detail DELETE
        """
        tenant = get_tenant_for_user(request.user,
                                     team_id=team_id,
                                     tenant_id=tenant_id)  # may raise
        lease = get_object_or_404(ServerLease, server_id=pk)
        try:
            lease.delete_server()
        except OpenstackNotFoundError:
            raise drf_exceptions.NotFound
        except OpenstackServiceError as e:
            raise UnexpectedOpenstackException(detail=str(e))

        # Slack notification
        slack_template = "openstack/slack/entity_deleted.txt"
        slack_context = {
            "entity_type": "Server",
            "entity_name": lease.server_name,
            "tenant": tenant,
            "user": request.user,
        }
        slack_post_templated_message(slack_template, slack_context)

        return Response(status=status.HTTP_204_NO_CONTENT)
예제 #2
0
파일: api_views.py 프로젝트: aresti/bryn
    def delete(self, request, team_id, tenant_id, pk):
        tenant = get_tenant_for_user(request.user,
                                     team_id=team_id,
                                     tenant_id=tenant_id)  # may raise

        openstack = OpenstackService(tenant=tenant)
        try:
            _response, deleted = methodcaller("delete", pk)(getattr(
                openstack, self.service.value))
        except OpenstackNotFoundError:
            raise drf_exceptions.NotFound
        except OpenstackServiceError as e:
            raise UnexpectedOpenstackException(detail=str(e))

        # Slack notification
        slack_template = "openstack/slack/entity_deleted.txt"
        slack_context = {
            "entity_type": self.entity_type,
            "entity_name": getattr(deleted, "name", "anonymous"),
            "tenant": tenant,
            "user": request.user,
        }
        slack_post_templated_message(slack_template, slack_context)

        return Response(status=status.HTTP_204_NO_CONTENT)
예제 #3
0
파일: api_views.py 프로젝트: aresti/bryn
    def post(self, request, team_id, tenant_id):
        tenant = get_tenant_for_user(request.user,
                                     team_id=team_id,
                                     tenant_id=tenant_id)  # may raise
        openstack = OpenstackService(tenant=tenant)
        annotation_func = self.annotation_factory(tenant)

        serialized = self.serializer_class(data=request.data)
        serialized.is_valid(raise_exception=True)
        serialized_data = serialized.data
        serialized_data["team"] = team_id
        serialized_data["tenant_id"] = tenant_id

        try:
            response = methodcaller("create", serialized_data)(getattr(
                openstack, self.service.value))
            annotated_response = annotation_func(response)
        except OpenstackServiceError as e:
            raise UnexpectedOpenstackException(detail=str(e))

        # Slack notification
        slack_template = "openstack/slack/entity_created.txt"
        slack_context = {
            "entity_type": self.entity_type,
            "entity_name": serialized_data.get("name", "anonymous"),
            "tenant": tenant,
            "user": request.user,
        }
        slack_post_templated_message(slack_template, slack_context)

        return Response(self.serializer_class(annotated_response).data)
예제 #4
0
파일: api_views.py 프로젝트: aresti/bryn
    def _state_transition(self, target_status, request, team_id, tenant_id,
                          pk):
        """
        Instance detail PATCH: state transition
        """
        tenant = get_tenant_for_user(request.user,
                                     team_id=team_id,
                                     tenant_id=tenant_id)  # may raise
        lease = get_object_or_404(ServerLease, server_id=pk)

        try:
            # Get current server status
            server = lease.get_annotated_server()
            current_status = server.status

            if current_status == target_status:
                # Nothing to do
                return

            # Check unshelving restrictions
            if (current_status in ["SHELVED", "SHELVED_OFFLOADED"]
                    and tenant.region.unshelving_disabled):
                raise drf_exceptions.PermissionDenied(
                    f"Unshelving is disabled at {tenant.region.description}")

            # Lookup action
            action = self.state_transitions[current_status].get(
                target_status, None)
            if action:
                methodcaller(action)(lease)
            else:
                raise ConflictError(
                    f"Action unavailble due to a conflict with the current server status ({current_status})"
                )
        except OpenstackNotFoundError:
            raise drf_exceptions.NotFound
        except OpenstackServiceError as e:
            raise UnexpectedOpenstackException(detail=str(e))

        # Slack notification
        slack_template = "openstack/slack/server_action.txt"
        slack_context = {
            "action": action,
            "entity_type": self.entity_type,
            "entity_name": getattr(server, "name", "anonymous"),
            "tenant": tenant,
            "user": request.user,
        }
        slack_post_templated_message(slack_template, slack_context)
예제 #5
0
파일: api_views.py 프로젝트: aresti/bryn
    def post(self, request, team_id, tenant_id):
        # Data
        tenant = get_tenant_for_user(request.user,
                                     tenant_id=tenant_id,
                                     team_id=team_id)  # may raise
        keypair = get_object_or_404(KeyPair, pk=request.data["keypair"])
        assigned_teammember = TeamMember.objects.get(team=tenant.team,
                                                     user=self.request.user)
        serialized = self.serializer_class(data=request.data)
        serialized.is_valid(raise_exception=True)
        serialized_data = serialized.data
        name = serialized_data["name"]
        image = serialized_data["image"]
        flavor = serialized_data["flavor"]

        # Create leased server
        try:
            server = ServerLease.objects.create_leased_server(
                tenant=tenant,
                keypair=keypair,
                name=name,
                image=image,
                flavor=flavor,
                assigned_teammember=assigned_teammember,
            )
        except OpenstackServiceError as exc:
            raise UnexpectedOpenstackException(detail=exc)

        # Annotate
        annotation_func = self.annotation_factory(tenant)
        annotated_response = annotation_func(server)

        # Slack notification
        slack_template = "openstack/slack/entity_created.txt"
        slack_context = {
            "entity_type": self.entity_type,
            "entity_name": serialized_data.get("name", "anonymous"),
            "tenant": tenant,
            "user": request.user,
        }
        slack_post_templated_message(slack_template, slack_context)

        return Response(self.serializer_class(annotated_response).data)
예제 #6
0
def send_server_lease_expiry_reminder_emails():
    """Send server lease expiry reminder emails, on specified days until expiry"""
    reminder_days = settings.SERVER_LEASE_REMINDER_DAYS
    due_leases = ServerLease.objects.active_due()
    sent_count = 0
    for lease in due_leases:
        if lease.time_remaining.days in reminder_days:
            last_reminder = lease.last_reminder_sent_at
            if last_reminder and (timezone.now() - last_reminder).days < 1:
                continue  # Don't send reminder more than once every 24 hours
            lease.send_email_renewal_reminder()
            logger.info(
                f"Sent server lease expiry reminder for '{lease.server_name}' to {lease.assigned_teammember.user.email}"
            )
            sent_count += 1

    # Slack notification
    if sent_count:
        slack_template = "openstack/slack/sent_server_lease_reminder_emails.txt"
        slack_post_templated_message(slack_template, {"sent_count": sent_count})
예제 #7
0
    def delete(self, request, team_id, bucket_name):
        ceph_user = get_object_or_404(CephUser, team_id=team_id, is_owner=True)
        try:
            ceph_user.delete_bucket(bucket_name)
        except CephS3NoSuchBucket:
            raise drf_exceptions.NotFound
        except CephS3BucketNotEmpty:
            raise CephS3BucketNoEmptyException
        except CephS3ServiceError as exc:
            raise CephS3UnexpectedException(exc)

        # Slack notification
        slack_template = "ceph/slack/bucket_deleted.txt"
        slack_context = {
            "bucket_name": bucket_name,
            "bryn_user": self.request.user,
            "team": ceph_user.team,
        }
        slack_post_templated_message(slack_template, slack_context)

        return Response(status=status.HTTP_204_NO_CONTENT)
예제 #8
0
파일: views.py 프로젝트: aresti/bryn
def server_lease_renewal_view(request, server_id, renewal_count):
    """
    GET: Renew server lease and redirect to login (with message)
    POST: Renew server lease and return json serialized representation
    """

    did_renew = False

    # Renew lease
    lease = get_object_or_404(ServerLease, server_id=server_id)
    if lease.renewal_count == renewal_count:
        lease.renew_lease(user=request.user)
        did_renew = True

    # Slack notification
    slack_template = "openstack/slack/server_lease_renewed.txt"
    slack_context = {
        "lease": lease,
        "user": request.user,
    }
    slack_post_templated_message(slack_template, slack_context)

    # GET: Redirect with message
    if request.method == "GET":
        if did_renew:
            messages.success(
                request,
                f"The lease for server {lease.server_name} has been renewed for {settings.SERVER_LEASE_DEFAULT_DAYS} "
                "days.",
            )
        else:
            messages.warning(
                request,
                "This server lease has already been renewed.",
            )
        return HttpResponseRedirect(reverse("home:home"))

    # POST: Return JSON serialized representation
    serialized = ServerLeaseSerializer(lease)
    return JsonResponse(serialized.data)
예제 #9
0
파일: tasks.py 프로젝트: aresti/bryn
def send_team_licence_expiry_reminder_emails():
    """Send team licence expiry reminder emails, on specified days until expiry"""
    reminder_days = settings.LICENCE_RENEWAL_REMINDER_DAYS
    licenced_teams = Team.objects.licence_valid()
    sent_count = 0
    for team in licenced_teams:
        time_remaining = team.licence_expiry - timezone.now()
        if time_remaining.days in reminder_days:
            last_reminder = team.licence_last_reminder_sent_at
            if last_reminder and (timezone.now() - last_reminder).days < 1:
                continue  # Don't send reminder more than once every 24 hours
            team.send_team_licence_reminder_emails()
            logger.info(
                f"Sent licence renewal reminder emails for team '{team.name}' with {time_remaining.days} days until "
                "expiry")
            sent_count += 1

    # Slack notification
    if sent_count:
        slack_template = "userdb/slack/sent_team_licence_renewal_reminder_emails.txt"
        slack_post_templated_message(slack_template,
                                     {"sent_count": sent_count})
예제 #10
0
    def perform_create(self, serializer):
        team_id = self.request.resolver_match.kwargs["team_id"]
        team = get_object_or_404(Team, pk=team_id)

        if team.s3_disabled:
            raise drf_exceptions.PermissionDenied(
                f"S3 Buckets are disabled for team {team.name}")

        queryset = CephUser.objects.filter(team=team, is_owner=True)
        if queryset.exists():
            raise drf_exceptions.ValidationError(
                "A CephUser already exists for this team.")

        ceph_user = serializer.save(team=team, is_owner=True)

        # Slack notification
        slack_template = "ceph/slack/ceph_user_created.txt"
        slack_context = {
            "uid": ceph_user.uid,
            "bryn_user": self.request.user,
            "team": team,
        }
        slack_post_templated_message(slack_template, slack_context)
예제 #11
0
    def post(self, request, team_id):
        ceph_user = get_object_or_404(CephUser, team_id=team_id, is_owner=True)
        serialized = BucketSerializer(data=request.data)
        serialized.is_valid(raise_exception=True)
        try:
            bucket = ceph_user.create_namespaced_bucket(**serialized.data)
        except ValueError as exc:
            raise drf_exceptions.ValidationError(exc)
        except CephS3BucketAlreadyExistsError as exc:
            raise drf_exceptions.ValidationError(exc)
        except CephS3ServiceError as exc:
            raise CephS3UnexpectedException(exc)
        serialized = BucketSerializer(bucket)

        # Slack notification
        slack_template = "ceph/slack/bucket_created.txt"
        slack_context = {
            "bucket_name": serialized.data["name"],
            "bryn_user": self.request.user,
            "team": ceph_user.team,
        }
        slack_post_templated_message(slack_template, slack_context)

        return Response(serialized.data, status=status.HTTP_201_CREATED)
예제 #12
0
파일: admin.py 프로젝트: aresti/bryn
    def hard_reboot_servers(self, request, queryset):
        """
        Admin action: ServerLease -> Hard Reboot Servers
        """
        opts = self.model._meta

        if request.POST.get("post"):
            # User clicked submit after confirmation
            rebooted = 0
            for server_lease in queryset:
                try:
                    # Reboot
                    server_lease.reboot_server()
                    rebooted += 1

                    # Slack notification
                    slack_template = "openstack/slack/server_action.txt"
                    slack_context = {
                        "action": "HARD_REBOOT_SERVER",
                        "entity_type": "Server",
                        "entity_name": server_lease.server_name,
                        "tenant": server_lease.tenant,
                        "user": request.user,
                    }
                    slack_post_templated_message(slack_template, slack_context)
                except (OpenstackNotAllowedError, OpenstackNotFoundError,
                        ValueError):
                    pass  # deleted or method not allowed for status

            if rebooted == 0:
                self.message_user(
                    request,
                    "All of the selected servers were in an incompatible state for rebooting.",
                    level=messages.WARNING,
                )
            else:
                self.message_user(
                    request,
                    ngettext(
                        f"Rebooted {rebooted} server.",
                        f"Rebooted {rebooted} servers.",
                        rebooted,
                    ),
                )
            # Return None to display the change list page again.
            return None

        objects_name = ngettext("server", "servers", queryset.count())

        context = {
            **self.admin_site.each_context(request),
            "title": "Are you sure?",
            "action": "hard_reboot_servers",
            "action_verb": "hard reboot",
            "objects_name": str(objects_name),
            "queryset": queryset,
            "opts": opts,
            "action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
            "media": self.media,
        }

        # Render confirmation page
        return TemplateResponse(request, "admin/confirm_action.html", context)
예제 #13
0
파일: admin.py 프로젝트: aresti/bryn
    def shelve_servers(self, request, queryset):
        """
        Admin action: ServerLease -> Shelve Servers
        """
        # Logic and template adapted from Django source for delete action
        # https://github.com/django/django/blob/main/django/contrib/admin/actions.py
        # Note: request type is always POST, so must check for 'post' key
        opts = self.model._meta

        if request.POST.get("post"):
            # User clicked submit after confirmation
            shelved = 0
            for server_lease in queryset:
                if server_lease.has_expired:  # safeguard
                    try:
                        server_lease.shelve_server()
                        shelved += 1

                        # Slack notification
                        slack_template = "openstack/slack/server_action.txt"
                        slack_context = {
                            "action": "SHELVE_SERVER",
                            "entity_type": "Server",
                            "entity_name": server_lease.server_name,
                            "tenant": server_lease.tenant,
                            "user": request.user,
                        }
                        slack_post_templated_message(slack_template,
                                                     slack_context)
                    except (
                            OpenstackNotAllowedError,
                            OpenstackNotFoundError,
                            ValueError,
                    ):
                        pass  # deleted or method not allowed for status

            if shelved == 0:
                self.message_user(
                    request,
                    "All of the selected servers were ineligible for shelving.",
                    level=messages.WARNING,
                )
            else:
                self.message_user(
                    request,
                    ngettext(
                        f"Shelved {shelved} server.",
                        f"Shelved {shelved} servers.",
                        shelved,
                    ),
                )
            # Return None to display the change list page again.
            return None

        objects_name = ngettext("server", "servers", queryset.count())

        context = {
            **self.admin_site.each_context(request),
            "title": "Are you sure?",
            "action": "shelve_servers",
            "action_verb": "shelve",
            "objects_name": str(objects_name),
            "queryset": queryset,
            "opts": opts,
            "action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
            "media": self.media,
        }

        # Render confirmation page
        return TemplateResponse(request, "admin/confirm_action.html", context)
예제 #14
0
def setup_openstack_project(team, region, request):
    """
    Setup an openstack project (tenant) for a team at a particular region.
    """
    # No duplicate team/region combinations
    if Tenant.objects.filter(team=team, region=region).count():
        raise ExistingTenantError(
            f"There is an existing project for '{team.name}' at '{region.name}'."
        )

    # Create (but don't save) a tenant
    tenant = Tenant(team=team, region=region)

    # Get an admin client and create the project
    admin_client = OpenstackService(region=region)
    domain = "default"
    project_name = tenant.get_tenant_name()

    with keystone_exception_handling(action="create", entity="project"):
        openstack_project = admin_client.keystone.projects.create(project_name,
                                                                  domain,
                                                                  enabled=True)

    # Update & save Bryn tenant record
    tenant.created_tenant_id = openstack_project.id
    tenant.created_tenant_name = project_name
    tenant.save()

    # Slack notification
    slack_template = "openstack/slack/entity_created.txt"
    slack_context = {
        "entity_type": "Tenant",
        "entity_name": project_name,
        "tenant": tenant,
        "user": request.user,
    }
    slack_post_templated_message(slack_template, slack_context)

    # Set quotas
    with keystone_exception_handling(), nova_exception_handling(
            action="update", entity="quota"):
        admin_client.nova.quotas.update(openstack_project.id,
                                        cores=32,
                                        ram=270000,
                                        instances=8)
        admin_client.cinder.quotas.update(openstack_project.id,
                                          volumes=20,
                                          gigabytes=2200)

    # Grant _member_ role to Bryn 'service user'
    username = admin_client.auth_settings["SERVICE_USERNAME"]
    with keystone_exception_handling(action="grant project role",
                                     entity="service user"):
        service_user = admin_client.keystone.users.list(name=username)[0]
        role = admin_client.keystone.roles.list(name="_member_")[0]
        admin_client.keystone.roles.grant(role,
                                          user=service_user,
                                          project=openstack_project)

    # Create default security rules
    project_client = OpenstackService(tenant=tenant)
    with keystone_exception_handling(), neutron_exception_handling(
            action="update", entity="default security group"):
        security_group_id = project_client.neutron.list_security_groups(
            name="default")["security_groups"][0]["id"]

        ingress_ports = [22, 80, 443]
        rule_defaults = {
            "security_group_id": security_group_id,
            "direction": "ingress",
            "protocol": "tcp",
            "remote_group_id": None,
            "remote_ip_prefix": "0.0.0.0/0",
        }

        for port in ingress_ports:
            rule = rule_defaults
            rule["port_range_min"] = port
            rule["port_range_max"] = port
            project_client.neutron.create_security_group_rule(
                {"security_group_rule": rule})

    # Update team
    team.tenants_available = True
    team.save()