def create(self, request): """@description-title Create a new device @description Create a new device. @param (string) "hostname" [required=false] A hostname. If not given, one will be generated. @param (string) "domain" [required=false] The domain of the device. If not given the default domain is used. @param (string) "mac_addresses" [required=true] One or more MAC addresses for the device. @param (string) "parent" [required=false] The system id of the parent. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the new device. @success-example "success-json" [exkey=devices-create] placeholder text @error (http-status-code) "400" 400 @error (content) "bad-param" There was a problem with the given parameters. """ form = DeviceWithMACsForm(data=request.data, request=request) if form.is_valid(): device = form.save() parent = device.parent maaslog.info( "%s: Added new device%s", device.hostname, "" if not parent else " (parent: %s)" % parent.hostname) return device else: raise MAASAPIValidationError(form.errors)
def create(self, request): """Create a new device. :param hostname: A hostname. If not given, one will be generated. :type hostname: unicode :param domain: The domain of the device. If not given the default domain is used. :type domain: unicode :param mac_addresses: One or more MAC addresses for the device. :type mac_addresses: unicode :param parent: The system id of the parent. Optional. :type parent: unicode """ form = DeviceWithMACsForm(data=request.data, request=request) if form.is_valid(): device = form.save() parent = device.parent maaslog.info( "%s: Added new device%s", device.hostname, "" if not parent else " (parent: %s)" % parent.hostname) return device else: raise MAASAPIValidationError(form.errors)
def claim_sticky_ip_address(self, request, system_id): """Assign a "sticky" IP address to a Node's MAC. This method is reserved for admin users. :param mac_address: Optional MAC address on the node on which to assign the sticky IP address. If not passed, defaults to the primary MAC for the node. :param requested_address: Optional IP address to claim. Must be in the range defined on a cluster interface to which the context MAC is related, or 403 Forbidden is returned. If the requested address is unavailable for use, 404 Not Found is returned. A sticky IP is one which stays with the node until the IP is disassociated with the node, or the node is deleted. It allows an admin to give a node a stable IP, since normally an automatic IP is allocated to a node only during the time a user has acquired and started a node. Returns 404 if the node is not found. Returns 409 if the node is in an allocated state. Returns 400 if the mac_address is not found on the node. Returns 503 if there are not enough IPs left on the cluster interface to which the mac_address is linked. """ node = get_object_or_404(Node, system_id=system_id) if node.status == NODE_STATUS.ALLOCATED: raise NodeStateViolation( "Sticky IP cannot be assigned to a node that is allocated") raw_mac = request.POST.get('mac_address', None) if raw_mac is None: mac_address = node.get_primary_mac() else: try: mac_address = MACAddress.objects.get( mac_address=raw_mac, node=node) except MACAddress.DoesNotExist: raise MAASAPIBadRequest( "mac_address %s not found on the node" % raw_mac) requested_address = request.POST.get('requested_address', None) sticky_ips = mac_address.claim_static_ips( alloc_type=IPADDRESS_TYPE.STICKY, requested_address=requested_address) claims = [ (static_ip.ip, mac_address.mac_address.get_raw()) for static_ip in sticky_ips] node.update_host_maps(claims) change_dns_zones(node.nodegroup) maaslog.info( "%s: Sticky IP address(es) allocated: %s", node.hostname, ', '.join(allocation.ip for allocation in sticky_ips)) return node
def mark_fixed(self, request, system_id): """Mark a broken node as fixed and set its status as 'ready'. Returns 404 if the node is not found. Returns 403 if the user does not have permission to mark the node broken. """ node = Node.objects.get_node_or_404( user=request.user, system_id=system_id, perm=NODE_PERMISSION.ADMIN) node.mark_fixed() maaslog.info( "%s: User %s marked node as fixed", node.hostname, request.user.username) return node
def acquire(self, request): """Acquire an available node for deployment. Constraints parameters can be used to acquire a node that possesses certain characteristics. All the constraints are optional and when multiple constraints are provided, they are combined using 'AND' semantics. :param name: Hostname of the returned node. :type name: unicode :param arch: Architecture of the returned node (e.g. 'i386/generic', 'amd64', 'armhf/highbank', etc.). :type arch: unicode :param cpu_count: The minium number of CPUs the returned node must have. :type cpu_count: int :param mem: The minimum amount of memory (expressed in MB) the returned node must have. :type mem: float :param tags: List of tags the returned node must have. :type tags: list of unicodes :param not_tags: List of tags the acquired node must not have. :type tags: List of unicodes. :param connected_to: List of routers' MAC addresses the returned node must be connected to. :type connected_to: unicode or list of unicodes :param networks: List of networks (defined in MAAS) to which the node must be attached. A network can be identified by the name assigned to it in MAAS; or by an `ip:` prefix followed by any IP address that falls within the network; or a `vlan:` prefix followed by a numeric VLAN tag, e.g. `vlan:23` for VLAN number 23. Valid VLAN tags must be in the range of 1 to 4095 inclusive. :type networks: list of unicodes :param not_networks: List of networks (defined in MAAS) to which the node must not be attached. The returned noded won't be attached to any of the specified networks. A network can be identified by the name assigned to it in MAAS; or by an `ip:` prefix followed by any IP address that falls within the network; or a `vlan:` prefix followed by a numeric VLAN tag, e.g. `vlan:23` for VLAN number 23. Valid VLAN tags must be in the range of 1 to 4095 inclusive. :type not_networks: list of unicodes :param not_connected_to: List of routers' MAC Addresses the returned node must not be connected to. :type connected_to: list of unicodes :param zone: An optional name for a physical zone the acquired node should be located in. :type zone: unicode :type not_in_zone: Optional list of physical zones from which the node should not be acquired. :type not_in_zone: List of unicodes. :param agent_name: An optional agent name to attach to the acquired node. :type agent_name: unicode Returns 409 if a suitable node matching the constraints could not be found. """ form = AcquireNodeForm(data=request.data) maaslog.info( "Request from user %s to acquire a node with constraints %s", request.user.username, request.data) if not form.is_valid(): raise ValidationError(form.errors) # This lock prevents a node we've picked as available from # becoming unavailable before our transaction commits. with locks.node_acquire: nodes = Node.objects.get_available_nodes_for_acquisition( request.user) nodes = form.filter_nodes(nodes) node = get_first(nodes) if node is None: constraints = form.describe_constraints() if constraints == '': # No constraints. That means no nodes at all were # available. message = "No node available." else: message = ( "No available node matches constraints: %s" % constraints) raise NodesNotAvailable(message) agent_name = request.data.get('agent_name', '') node.acquire( request.user, get_oauth_token(request), agent_name=agent_name) return node
def create_node(request): """Service an http request to create a node. The node will be in the New state. :param request: The http request for this node to be created. :return: A `Node`. :rtype: :class:`maasserver.models.Node`. :raises: ValidationError """ # For backwards compatibilty reasons, requests may be sent with: # architecture with a '/' in it: use normally # architecture without a '/' and no subarchitecture: assume 'generic' # architecture without a '/' and a subarchitecture: use as specified # architecture with a '/' and a subarchitecture: error given_arch = request.data.get('architecture', None) given_subarch = request.data.get('subarchitecture', None) altered_query_data = request.data.copy() if given_arch and '/' in given_arch: if given_subarch: # Architecture with a '/' and a subarchitecture: error. raise ValidationError('Subarchitecture cannot be specified twice.') # Architecture with a '/' in it: use normally. elif given_arch: if given_subarch: # Architecture without a '/' and a subarchitecture: # use as specified. altered_query_data['architecture'] = '/'.join( [given_arch, given_subarch]) del altered_query_data['subarchitecture'] else: # Architecture without a '/' and no subarchitecture: # assume 'generic'. altered_query_data['architecture'] += '/generic' if 'nodegroup' not in altered_query_data: # If 'nodegroup' is not explicitely specified, get the origin of the # request to figure out which nodegroup the new node should be # attached to. if request.data.get('autodetect_nodegroup', None) is None: # We insist on this to protect command-line API users who # are manually enlisting nodes. You can't use the origin's # IP address to indicate in which nodegroup the new node belongs. raise ValidationError( "'autodetect_nodegroup' must be specified if 'nodegroup' " "parameter missing") nodegroup = find_nodegroup(request) if nodegroup is not None: altered_query_data['nodegroup'] = nodegroup Form = get_node_create_form(request.user) form = Form(data=altered_query_data) if form.is_valid(): node = form.save() # Hack in the power parameters here. store_node_power_parameters(node, request) maaslog.info("%s: Enlisted new node", node.hostname) return node else: raise ValidationError(form.errors)