Example #1
0
    def get_next_ip_for_allocation(
        self,
        exclude_addresses: Optional[Iterable] = None,
        avoid_observed_neighbours: bool = True,
    ):
        """Heuristic to return the "best" address from this subnet to use next.

        :param exclude_addresses: Optional list of addresses to exclude.
        :param avoid_observed_neighbours: Optional parameter to specify if
            known observed neighbours should be avoided. This parameter is not
            intended to be specified by a caller in production code; it is used
            internally to recursively call this method if the first allocation
            attempt fails.
        """
        if exclude_addresses is None:
            exclude_addresses = []
        free_ranges = self.get_ipranges_not_in_use(
            exclude_addresses=exclude_addresses,
            with_neighbours=avoid_observed_neighbours,
        )
        if len(free_ranges) == 0 and avoid_observed_neighbours is True:
            # Try again recursively, but this time consider neighbours to be
            # "free" IP addresses. (We'll pick the least recently seen IP.)
            return self.get_next_ip_for_allocation(
                exclude_addresses, avoid_observed_neighbours=False
            )
        elif len(free_ranges) == 0:
            raise StaticIPAddressExhaustion(
                "No more IPs available in subnet: %s." % self.cidr
            )
        # The first time through this function, we aren't trying to avoid
        # observed neighbours. In fact, `free_ranges` only contains completely
        # unused ranges. So we don't need to check for the least recently seen
        # neighbour on the first pass.
        if avoid_observed_neighbours is False:
            # We tried considering neighbours as "in-use" addresses, but the
            # subnet is still full. So make an educated guess about which IP
            # address is least likely to be in-use.
            discovery = self.get_least_recently_seen_unknown_neighbour()
            if discovery is not None:
                maaslog.warning(
                    "Next IP address to allocate from '%s' has been observed "
                    "previously: %s was last claimed by %s via %s at %s."
                    % (
                        self.label,
                        discovery.ip,
                        discovery.mac_address,
                        discovery.observer_interface.get_log_string(),
                        discovery.last_seen,
                    )
                )
                return str(discovery.ip)
        # The purpose of this is to that we ensure we always get an IP address
        # from the *smallest* free contiguous range. This way, larger ranges
        # can be preserved in case they need to be used for applications
        # requiring them.
        free_range = min(free_ranges, key=attrgetter("num_addresses"))
        return str(IPAddress(free_range.first))
Example #2
0
    def power_on(self, request, system_id):
        """@description-title Turn on a node
        @description Turn on the given node with optional user-data and
        comment.

        @param (string) "user_data" [required=false] Base64-encoded blob of
        data to be made available to the nodes through the metadata service.

        @param (string) "comment" [required=false] Comment for the event log.

        @success (http-status-code) "204" 204
        @success (json) "success_json" A JSON object containing the node's
        information.
        @success-example "success_json" [exkey=power-on] placeholder text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested node is not found.
        @error-example "not-found"
            Not Found

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The user is not authorized to power on the
        node.

        @error (http-status-code) "503" 503
        @error (content) "no-ips" Returns 503 if the start-up attempted to
        allocate an IP address, and there were no IP addresses available on the
        relevant cluster interface.
        """
        user_data = request.POST.get('user_data', None)
        comment = get_optional_param(request.POST, 'comment')

        node = self.model.objects.get_node_or_404(
            system_id=system_id, user=request.user,
            perm=NodePermission.edit)
        if node.owner is None and node.node_type != NODE_TYPE.RACK_CONTROLLER:
            raise NodeStateViolation(
                "Can't start node: it hasn't been allocated.")
        if user_data is not None:
            user_data = b64decode(user_data)
        try:
            node.start(request.user, user_data=user_data, comment=comment)
        except StaticIPAddressExhaustion:
            # The API response should contain error text with the
            # system_id in it, as that is the primary API key to a node.
            raise StaticIPAddressExhaustion(
                "%s: Unable to allocate static IP due to address"
                " exhaustion." % system_id)
        return node
Example #3
0
    def power_on(self, request, system_id):
        """Turn on a node.

        :param user_data: If present, this blob of user-data to be made
            available to the nodes through the metadata service.
        :type user_data: base64-encoded unicode
        :param comment: Optional comment for the event log.
        :type comment: unicode

        Ideally we'd have MIME multipart and content-transfer-encoding etc.
        deal with the encapsulation of binary data, but couldn't make it work
        with the framework in reasonable time so went for a dumb, manual
        encoding instead.

        Returns 404 if the node is not found.
        Returns 403 if the user does not have permission to start the machine.
        Returns 503 if the start-up attempted to allocate an IP address,
        and there were no IP addresses available on the relevant cluster
        interface.
        """
        user_data = request.POST.get('user_data', None)
        comment = get_optional_param(request.POST, 'comment')

        node = self.model.objects.get_node_or_404(
            system_id=system_id, user=request.user,
            perm=NodePermission.edit)
        if node.owner is None and node.node_type != NODE_TYPE.RACK_CONTROLLER:
            raise NodeStateViolation(
                "Can't start node: it hasn't been allocated.")
        if user_data is not None:
            user_data = b64decode(user_data)
        try:
            node.start(request.user, user_data=user_data, comment=comment)
        except StaticIPAddressExhaustion:
            # The API response should contain error text with the
            # system_id in it, as that is the primary API key to a node.
            raise StaticIPAddressExhaustion(
                "%s: Unable to allocate static IP due to address"
                " exhaustion." % system_id)
        return node
Example #4
0
    def power_on(self, request, system_id):
        """@description-title Turn on a node
        @description Turn on the given node with optional user-data and
        comment.

        @param (string) "user_data" [required=false] Base64-encoded blob of
        data to be made available to the nodes through the metadata service.

        @param (string) "comment" [required=false] Comment for the event log.

        @success (http-status-code) "204" 204
        @success (json) "success_json" A JSON object containing the node's
        information.
        @success-example "success_json" [exkey=power-on] placeholder text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested node is not found.
        @error-example "not-found"
            Not Found

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The user is not authorized to power on the
        node.

        @error (http-status-code) "503" 503
        @error (content) "no-ips" Returns 503 if the start-up attempted to
        allocate an IP address, and there were no IP addresses available on the
        relevant cluster interface.
        """
        user_data = request.POST.get("user_data", None)
        comment = get_optional_param(request.POST, "comment")

        node = self.model.objects.get_node_or_404(system_id=system_id,
                                                  user=request.user,
                                                  perm=NodePermission.edit)
        if node.owner is None and node.node_type != NODE_TYPE.RACK_CONTROLLER:
            raise NodeStateViolation(
                "Can't start node: it hasn't been allocated.")
        if user_data is not None:
            user_data = b64decode(user_data)
        try:
            # These parameters are passed in the request from
            # maasserver.api.machines.deploy when powering on
            # the node for deployment.
            install_kvm = get_optional_param(
                request.POST,
                "install_kvm",
                default=False,
                validator=StringBool,
            )
            bridge_type = get_optional_param(request.POST,
                                             "bridge_type",
                                             default=None)
            if (bridge_type is not None
                    and bridge_type not in BRIDGE_TYPE_CHOICES_DICT):
                raise MAASAPIValidationError({
                    "bridge_type":
                    compose_invalid_choice_text("bridge_type",
                                                BRIDGE_TYPE_CHOICES)
                })
            bridge_stp = get_optional_param(request.POST,
                                            "bridge_stp",
                                            default=None,
                                            validator=StringBool)
            bridge_fd = get_optional_param(request.POST,
                                           "bridge_fd",
                                           default=None,
                                           validator=Int)
            node.start(
                request.user,
                user_data=user_data,
                comment=comment,
                install_kvm=install_kvm,
                bridge_type=bridge_type,
                bridge_stp=bridge_stp,
                bridge_fd=bridge_fd,
            )
        except StaticIPAddressExhaustion:
            # The API response should contain error text with the
            # system_id in it, as that is the primary API key to a node.
            raise StaticIPAddressExhaustion(
                "%s: Unable to allocate static IP due to address"
                " exhaustion." % system_id)
        return node