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))
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
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
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