def read(self, request, id): """Read a specific notification.""" notification = get_object_or_404(Notification, id=id) if notification.is_relevant_to(request.user): return notification elif request.user.is_superuser: return notification else: raise MAASAPIForbidden()
def dismiss(self, request, id): """Dismiss a specific notification. Returns HTTP 403 FORBIDDEN if this notification is not relevant (targeted) to the invoking user. It is safe to call multiple times for the same notification. """ notification = get_object_or_404(Notification, id=id) if notification.is_relevant_to(request.user): notification.dismiss(request.user) else: raise MAASAPIForbidden()
def create_physical(self, request, system_id): """Create a physical interface on a machine and device. :param name: Name of the interface. :param mac_address: MAC address of the interface. :param tags: Tags for the interface. :param vlan: Untagged VLAN the interface is connected to. If not provided then the interface is considered disconnected. Following are extra parameters that can be set on the interface: :param mtu: Maximum transmission unit. :param accept_ra: Accept router advertisements. (IPv6 only) :param autoconf: Perform stateless autoconfiguration. (IPv6 only) Returns 404 if the node is not found. """ node = Node.objects.get_node_or_404( system_id, request.user, NodePermission.edit) raise_error_if_controller(node, "create") # Machine type nodes require the node needs to be in the correct state # and that the user has admin permissions. if node.node_type == NODE_TYPE.MACHINE: if not request.user.has_perm(NodePermission.admin, node): raise MAASAPIForbidden() raise_error_for_invalid_state_on_allocated_operations( node, request.user, "create") form = PhysicalInterfaceForm(node=node, data=request.data) if form.is_valid(): return form.save() else: # The Interface model validation is so strict that it will cause # the mac_address field to include two messages about it being # required. We clean up this response to not provide duplicate # information. if "mac_address" in form.errors: if (MISSING_FIELD in form.errors["mac_address"] and BLANK_FIELD in form.errors["mac_address"]): form.errors["mac_address"] = ErrorList([ error for error in form.errors["mac_address"] if error != BLANK_FIELD ]) raise MAASAPIValidationError(form.errors)
def update(self, request, id, file_id): """Upload piece of boot resource file.""" resource = get_object_or_404(BootResource, id=id) rfile = get_object_or_404(BootResourceFile, id=file_id) size = int(request.META.get("CONTENT_LENGTH", "0")) data = request.body if size == 0: raise MAASAPIBadRequest("Missing data.") if size != len(data): raise MAASAPIBadRequest( "Content-Length doesn't equal size of recieved data." ) if resource.rtype not in ALLOW_UPLOAD_RTYPES: raise MAASAPIForbidden( "Cannot upload to a resource of type: %s. " % resource.rtype ) if rfile.largefile.complete: raise MAASAPIBadRequest("Cannot upload to a complete file.") with rfile.largefile.content.open("wb") as stream: stream.seek(0, os.SEEK_END) # Check that the uploading data will not make the file larger # than expected. current_size = stream.tell() if current_size + size > rfile.largefile.total_size: raise MAASAPIBadRequest("Too much data recieved.") stream.write(data) rfile.largefile.size = current_size + size rfile.largefile.save() if rfile.largefile.complete: if not rfile.largefile.valid: raise MAASAPIBadRequest( "Saved content does not match given SHA256 value." ) # Avoid circular import. from maasserver.clusterrpc.boot_images import ( RackControllersImporter, ) post_commit_do(RackControllersImporter.schedule) return rc.ALL_OK
def create(self, request): """@description-title Create an IP range @description Create a new IP range. @param (string) "type" [required=true] Type of this range. (``dynamic`` or ``reserved``) @param (string) "start_ip" [required=true] Start IP address of this range (inclusive). @param (string) "end_ip" [required=true] End IP address of this range (inclusive). @param (string) "subnet" [required=true] Subnet associated with this range. @param (string) "comment" [required=false] A description of this range. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the new IP range. @success-example "success-json" [exkey=ipranges-create] placeholder text @error (http-status-code) "403" 403 @error (content) "no-perms" The user does not have the permissions required to create an IP range. """ if ('type' in request.data and request.data['type'] == IPRANGE_TYPE.DYNAMIC and not request.user.is_superuser): raise MAASAPIForbidden("Unable to create dynamic IP range. " "You don't have sufficient privileges.") form = IPRangeForm(data=request.data, request=request) if form.is_valid(): return form.save() else: raise MAASAPIValidationError(form.errors)
def create(self, request): """Create an IP range. :param type: Type of this range. (`dynamic` or `reserved`) :param start_ip: Start IP address of this range (inclusive). :param end_ip: End IP address of this range (inclusive). :param subnet: Subnet this range is associated with. (optional) :param comment: A description of this range. (optional) Returns 403 if standard users tries to create a dynamic IP range. """ if ('type' in request.data and request.data['type'] == IPRANGE_TYPE.DYNAMIC and not request.user.is_superuser): raise MAASAPIForbidden("Unable to create dynamic IP range. " "You don't have sufficient privileges.") form = IPRangeForm(data=request.data, request=request) if form.is_valid(): return form.save() else: raise MAASAPIValidationError(form.errors)
def get_object_by_specifiers_or_raise(self, specifiers, **kwargs): """Gets an object using the given specifier(s). If the specifier is empty, raises Http400. If multiple objects are returned, raises Http403. If the object cannot be found, raises Http404. :param:specifiers: unicode """ object_name = get_model_object_name(self) if isinstance(specifiers, str): specifiers = specifiers.strip() if specifiers is None or specifiers == "": raise MAASAPIBadRequest("%s specifier required." % object_name) try: object = get_one(self.filter_by_specifiers(specifiers, **kwargs)) if object is None: raise Http404('No %s matches the given query.' % object_name) except self.model.MultipleObjectsReturned: raise MAASAPIForbidden( "Too many %s objects match the given query." % object_name) return object
def dismiss(self, request, id): """@description-title Dismiss a notification @description Dismiss a notification with the given id. It is safe to call multiple times for the same notification. @param (int) "{id}" [required=true] The notification id. @success (http-status-code) "server-success" 200 @error (http-status-code) "403" 403 @error (content) "no-perms" The notification is not relevant to the invoking user. @error (http-status-code) "404" 404 @error (content) "not-found" The requested notification is not found. @error-example "not-found" Not Found """ notification = get_object_or_404(Notification, id=id) if notification.is_relevant_to(request.user): notification.dismiss(request.user) else: raise MAASAPIForbidden()
def read(self, request, id): """@description-title Read a notification @description Read a notification with the given id. @param (int) "{id}" [required=true] The notification id. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the requested notification object. @success-example "success-json" [exkey=notifications-read-by-id] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested notification is not found. @error-example "not-found" Not Found """ notification = get_object_or_404(Notification, id=id) if notification.is_relevant_to(request.user): return notification elif request.user.is_superuser: return notification else: raise MAASAPIForbidden()
def raise_error_if_not_owner(iprange, user): if not user.is_superuser and iprange.user_id != user.id: raise MAASAPIForbidden( "Unable to modify IP range. You don't own the IP range.")
def update(self, request, system_id, id): """@description-title Update an interface @description Update an interface with the given system_id and interface id. Note: machines must have a status of Ready or Broken to have access to all options. Machines with Deployed status can only have the name and/or mac_address updated for an interface. This is intented to allow a bad interface to be replaced while the machine remains deployed. @param (string) "{system_id}" [required=true] A system_id. @param (int) "{id}" [required=true] An interface id. @param (string) "name" [required=false] (Physical interfaces) Name of the interface. @param (string) "mac_address" [required=false] (Physical interfaces) MAC address of the interface. @param (string) "tags" [required=false] (Physical interfaces) Tags for the interface. @param (int) "vlan" [required=false] (Physical interfaces) Untagged VLAN id the interface is connected to. If not set then the interface is considered disconnected. @param (string) "name" [required=false] (Bond interfaces) Name of the interface. @param (string) "mac_address" [required=false] (Bond interfaces) MAC address of the interface. @param (string) "tags" [required=false] (Bond interfaces) Tags for the interface. @param (int) "vlan" [required=false] (Bond interfaces) Untagged VLAN id the interface is connected to. If not set then the interface is considered disconnected. @param (int) "parents" [required=false] (Bond interfaces) Parent interface ids that make this bond. @param (string) "tags" [required=false] (VLAN interfaces) Tags for the interface. @param (int) "vlan" [required=false] (VLAN interfaces) Tagged VLAN id the interface is connected to. @param (int) "parent" [required=false] (VLAN interfaces) Parent interface ids for the VLAN interface. @param (string) "name" [required=false] (Bridge interfaces) Name of the interface. @param (string) "mac_address" [required=false] (Bridge interfaces) MAC address of the interface. @param (string) "tags" [required=false] (Bridge interfaces) Tags for the interface. @param (int) "vlan" [required=false] (Bridge interfaces) VLAN id the interface is connected to. @param (int) "parent" [required=false] (Bridge interfaces) Parent interface ids for this bridge interface. @param (string) "bridge_type" [required=false] (Bridge interfaces) Type of bridge to create. Possible values are: ``standard``, ``ovs``. @param (boolean) "bridge_stp" [required=false] (Bridge interfaces) Turn spanning tree protocol on or off. (Default: False). @param (int) "bridge_fd" [required=false] (Bridge interfaces) Set bridge forward delay to time seconds. (Default: 15). @param (int) "bond_miimon" [required=false] (Bonds) The link monitoring freqeuncy in milliseconds. (Default: 100). @param (int) "bond_downdelay" [required=false] (Bonds) Specifies the time, in milliseconds, to wait before disabling a slave after a link failure has been detected. @param (int) "bond_updelay" [required=false] (Bonds) Specifies the time, in milliseconds, to wait before enabling a slave after a link recovery has been detected. @param (string) "bond_lacp_rate" [required=false] (Bonds) Option specifying the rate in which we'll ask our link partner to transmit LACPDU packets in 802.3ad mode. Available options are ``fast`` or ``slow``. (Default: ``slow``). @param (string) "bond_xmit_hash_policy" [required=false] (Bonds) The transmit hash policy to use for slave selection in balance-xor, 802.3ad, and tlb modes. Possible values are: ``layer2``, ``layer2+3``, ``layer3+4``, ``encap2+3``, ``encap3+4``. @param (string) "bond_mode" [required=false,formatting=true] (Bonds) The operating mode of the bond. (Default: ``active-backup``). Supported bonding modes (bond-mode): - ``balance-rr``: Transmit packets in sequential order from the first available slave through the last. This mode provides load balancing and fault tolerance. - ``active-backup``: Only one slave in the bond is active. A different slave becomes active if, and only if, the active slave fails. The bond's MAC address is externally visible on only one port (network adapter) to avoid confusing the switch. - ``balance-xor``: Transmit based on the selected transmit hash policy. The default policy is a simple [(source MAC address XOR'd with destination MAC address XOR packet type ID) modulo slave count]. - ``broadcast``: Transmits everything on all slave interfaces. This mode provides fault tolerance. - ``802.3ad``: IEEE 802.3ad Dynamic link aggregation. Creates aggregation groups that share the same speed and duplex settings. Utilizes all slaves in the active aggregator according to the 802.3ad specification. - ``balance-tlb``: Adaptive transmit load balancing: channel bonding that does not require any special switch support. - ``balance-alb``: Adaptive load balancing: includes balance-tlb plus receive load balancing (rlb) for IPV4 traffic, and does not require any special switch support. The receive load balancing is achieved by ARP negotiation. @param (string) "mtu" [required=false] Maximum transmission unit. @param (string) "accept_ra" [required=false] Accept router advertisements. (IPv6 only) @param (string) "autoconf" [required=false] Perform stateless autoconfiguration. (IPv6 only) @param (boolean) "link_connected" [required=false] (Physical interfaces) Whether or not the interface is physically conntected to an uplink. (Default: True). @param (int) "interface_speed" [required=false] (Physical interfaces) The speed of the interface in Mbit/s. (Default: 0). @param (int) "link_speed" [required=false] (Physical interfaces) The speed of the link in Mbit/s. (Default: 0). @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the new requested interface object. @success-example "success-json" [exkey=interfaces-update] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested machine or interface is not found. @error-example "not-found" Not Found """ interface = Interface.objects.get_interface_or_404( system_id, id, request.user, NodePermission.admin, NodePermission.edit) node = interface.get_node() if node.node_type == NODE_TYPE.MACHINE: # This node needs to be in the correct state to modify # the interface. raise_error_for_invalid_state_on_allocated_operations( node, request.user, "update interface", extra_states=[NODE_STATUS.DEPLOYED]) if node.is_controller: if interface.type == INTERFACE_TYPE.VLAN: raise MAASAPIForbidden( "Cannot modify VLAN interface on controller.") interface_form = ControllerInterfaceForm elif node.status == NODE_STATUS.DEPLOYED: interface_form = DeployedInterfaceForm else: interface_form = InterfaceForm.get_interface_form(interface.type) # For VLAN interface we cast parents to parent. As a VLAN can only # have one parent. if interface.type == INTERFACE_TYPE.VLAN: request.data = request.data.copy() if 'parent' in request.data: request.data['parents'] = request.data['parent'] form = interface_form(instance=interface, data=request.data) if form.is_valid(): return form.save() else: # Replace parents with parent so it matches the API parameter, if # the interface being editted was a VLAN interface. if (interface.type == INTERFACE_TYPE.VLAN and 'parents' in form.errors): form.errors['parent'] = form.errors.pop('parents') raise MAASAPIValidationError(form.errors)
def create_physical(self, request, system_id): """@description-title Create a physical interface @description Create a physical interface on a machine and device. @param (string) "{system_id}" [required=true] A system_id. @param (string) "name" [required=false] Name of the interface. @param (string) "mac_address" [required=true] MAC address of the interface. @param (string) "tags" [required=false] Tags for the interface. @param (string) "vlan" [required=false] Untagged VLAN the interface is connected to. If not provided then the interface is considered disconnected. @param (int) "mtu" [required=false] Maximum transmission unit. @param (boolean) "accept_ra" [required=false] Accept router advertisements. (IPv6 only) @param (boolean) "autoconf" [required=false] Perform stateless autoconfiguration. (IPv6 only) @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the new interface object. @success-example "success-json" [exkey=interfaces-create-physical] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested machine is not found. @error-example "not-found" Not Found """ node = Node.objects.get_node_or_404( system_id, request.user, NodePermission.edit) raise_error_if_controller(node, "create") # Machine type nodes require the node needs to be in the correct state # and that the user has admin permissions. if node.node_type == NODE_TYPE.MACHINE: if not request.user.has_perm(NodePermission.admin, node): raise MAASAPIForbidden() raise_error_for_invalid_state_on_allocated_operations( node, request.user, "create") form = PhysicalInterfaceForm(node=node, data=request.data) if form.is_valid(): return form.save() else: # The Interface model validation is so strict that it will cause # the mac_address field to include two messages about it being # required. We clean up this response to not provide duplicate # information. if "mac_address" in form.errors: if (MISSING_FIELD in form.errors["mac_address"] and BLANK_FIELD in form.errors["mac_address"]): form.errors["mac_address"] = ErrorList([ error for error in form.errors["mac_address"] if error != BLANK_FIELD ]) raise MAASAPIValidationError(form.errors)
def raise_error_if_controller(node, operation): if node.is_controller: raise MAASAPIForbidden( "Cannot %s interface on a controller." % operation)
def update(self, request, system_id, id): """Update interface on node. Machines must have a status of Ready or Broken to have access to all options. Machines with Deployed status can only have the name and/or mac_address updated for an interface. This is intented to allow a bad interface to be replaced while the machine remains deployed. Fields for physical interface: :param name: Name of the interface. :param mac_address: MAC address of the interface. :param tags: Tags for the interface. :param vlan: Untagged VLAN the interface is connected to. If not set then the interface is considered disconnected. Fields for bond interface: :param name: Name of the interface. :param mac_address: MAC address of the interface. :param tags: Tags for the interface. :param vlan: Untagged VLAN the interface is connected to. If not set then the interface is considered disconnected. :param parents: Parent interfaces that make this bond. Fields for VLAN interface: :param tags: Tags for the interface. :param vlan: Tagged VLAN the interface is connected to. :param parent: Parent interface for this VLAN interface. Fields for bridge interface: :param name: Name of the interface. :param mac_address: MAC address of the interface. :param tags: Tags for the interface. :param vlan: VLAN the interface is connected to. :param parent: Parent interface for this bridge interface. Following are extra parameters that can be set on all interface types: :param mtu: Maximum transmission unit. :param accept_ra: Accept router advertisements. (IPv6 only) :param autoconf: Perform stateless autoconfiguration. (IPv6 only) Following are parameters specific to bonds: :param bond_mode: The operating mode of the bond. (Default: active-backup). :param bond_miimon: The link monitoring freqeuncy in milliseconds. (Default: 100). :param bond_downdelay: Specifies the time, in milliseconds, to wait before disabling a slave after a link failure has been detected. :param bond_updelay: Specifies the time, in milliseconds, to wait before enabling a slave after a link recovery has been detected. :param bond_lacp_rate: Option specifying the rate in which we'll ask our link partner to transmit LACPDU packets in 802.3ad mode. Available options are fast or slow. (Default: slow). :param bond_xmit_hash_policy: The transmit hash policy to use for slave selection in balance-xor, 802.3ad, and tlb modes. Supported bonding modes (bond-mode): balance-rr - Transmit packets in sequential order from the first available slave through the last. This mode provides load balancing and fault tolerance. active-backup - Only one slave in the bond is active. A different slave becomes active if, and only if, the active slave fails. The bond's MAC address is externally visible on only one port (network adapter) to avoid confusing the switch. balance-xor - Transmit based on the selected transmit hash policy. The default policy is a simple [(source MAC address XOR'd with destination MAC address XOR packet type ID) modulo slave count]. broadcast - Transmits everything on all slave interfaces. This mode provides fault tolerance. 802.3ad - IEEE 802.3ad Dynamic link aggregation. Creates aggregation groups that share the same speed and duplex settings. Utilizes all slaves in the active aggregator according to the 802.3ad specification. balance-tlb - Adaptive transmit load balancing: channel bonding that does not require any special switch support. balance-alb - Adaptive load balancing: includes balance-tlb plus receive load balancing (rlb) for IPV4 traffic, and does not require any special switch support. The receive load balancing is achieved by ARP negotiation. Following are parameters specific to bridges: :param bridge_stp: Turn spanning tree protocol on or off. (Default: False). :param bridge_fd: Set bridge forward delay to time seconds. (Default: 15). Returns 404 if the node or interface is not found. """ interface = Interface.objects.get_interface_or_404( system_id, id, request.user, NODE_PERMISSION.EDIT) node = interface.get_node() if node.node_type == NODE_TYPE.MACHINE: # This node needs to be in the correct state to modify # the interface. raise_error_for_invalid_state_on_allocated_operations( node, request.user, "update interface", extra_states=[NODE_STATUS.DEPLOYED]) if node.is_controller: if interface.type == INTERFACE_TYPE.VLAN: raise MAASAPIForbidden( "Cannot modify VLAN interface on controller.") interface_form = ControllerInterfaceForm elif node.status == NODE_STATUS.DEPLOYED: interface_form = DeployedInterfaceForm else: interface_form = InterfaceForm.get_interface_form(interface.type) # For VLAN interface we cast parents to parent. As a VLAN can only # have one parent. if interface.type == INTERFACE_TYPE.VLAN: request.data = request.data.copy() if 'parent' in request.data: request.data['parents'] = request.data['parent'] form = interface_form(instance=interface, data=request.data) if form.is_valid(): return form.save() else: # Replace parents with parent so it matches the API parameter, if # the interface being editted was a VLAN interface. if (interface.type == INTERFACE_TYPE.VLAN and 'parents' in form.errors): form.errors['parent'] = form.errors.pop('parents') raise MAASAPIValidationError(form.errors)
def _claim_ip(self, user, subnet, ip_address, mac=None, hostname=None): """Attempt to get a USER_RESERVED StaticIPAddress for `user`. :param subnet: Subnet to use use for claiming the IP. :param ip_address: Any requested address :param mac: MAC address to use :param hostname: The hostname :param domain: The domain to use :type subnet: Subnet :type ip_address: str :type mac: str :type hostname: str :type domain: Domain :raises StaticIPAddressExhaustion: If no IPs available. """ dynamic_range = subnet.get_dynamic_maasipset() if ip_address is not None and ip_address in dynamic_range: raise MAASAPIForbidden( "IP address %s belongs to an existing dynamic range. To " "reserve this IP address, a MAC address is required. (Create " "a device instead.)" % ip_address) if hostname is not None and hostname.find('.') > 0: hostname, domain = hostname.split('.', 1) domain = Domain.objects.get_domain_or_404("name:%s" % domain, user, NODE_PERMISSION.VIEW) else: domain = Domain.objects.get_default_domain() if mac is None: sip = StaticIPAddress.objects.allocate_new( alloc_type=IPADDRESS_TYPE.USER_RESERVED, requested_address=ip_address, subnet=subnet, user=user) if hostname is not None and hostname != '': dnsrr, _ = DNSResource.objects.get_or_create(name=hostname, domain=domain) dnsrr.ip_addresses.add(sip) maaslog.info("User %s was allocated IP %s", user.username, sip.ip) else: # The user has requested a static IP linked to a MAC address, so we # set that up via the Interface model. If the MAC address is part # of a node, then this operation is not allowed. The 'link_subnet' # API should be used on that interface. nic, created = (Interface.objects.get_or_create( mac_address=mac, defaults={ 'type': INTERFACE_TYPE.UNKNOWN, 'name': 'eth0', })) if nic.type != INTERFACE_TYPE.UNKNOWN: raise MAASAPIBadRequest( "MAC address %s already belongs to %s. Use of the " "interface API is required, for an interface that belongs " "to a node." % (nic.mac_address, nic.node.hostname)) # Link the new interface on the same subnet as the # ip_address. sip = nic.link_subnet(INTERFACE_LINK_TYPE.STATIC, subnet, ip_address=ip_address, alloc_type=IPADDRESS_TYPE.USER_RESERVED, user=user) if hostname is not None and hostname != '': dnsrr, _ = DNSResource.objects.get_or_create(name=hostname, domain=domain) dnsrr.ip_addresses.add(sip) maaslog.info("User %s was allocated IP %s for MAC address %s", user.username, sip.ip, nic.mac_address) return sip