def headnode_connect_network(headnode, hnic, network): """Connect a headnode's hnic to a network. Raises IllegalStateError if the headnode has already been started. Raises ProjectMismatchError if the project does not have access rights to the given network. Raises BadArgumentError if the network is a non-allocated network. This is currently unsupported due to an implementation limitation, but will be supported in a future release. See issue #333. """ headnode = _must_find(model.Headnode, headnode) get_auth_backend().require_project_access(headnode.project) hnic = _must_find_n(headnode, model.Hnic, hnic) network = _must_find(model.Network, network) if not network.allocated: raise BadArgumentError("Headnodes may only be connected to networks " "allocated by the project.") if not headnode.dirty: raise IllegalStateError project = headnode.project if (network.access is not None) and (network.access is not project): raise ProjectMismatchError("Project does not have access to given network.") hnic.network = network db.session.commit()
def project_delete(project): """Delete project. If the project does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() project = _must_find(model.Project, project) if project.nodes: raise BlockedError("Project has nodes still") if project.networks_created: raise BlockedError("Project still has networks") if project.networks_access: ### FIXME: This is not the user's fault, and they cannot fix it. The ### only reason we need to error here is that, with how network access ### is done, the following bad thing happens. If there's a network ### that only the project can access, its "access" field will be the ### project. When you then delete that project, "access" will be set ### to None instead. Counter-intuitively, this then makes that ### network accessible to ALL PROJECTS! Once we use real ACLs, this ### will not be an issue---instead, the network will be accessible by ### NO projects. raise BlockedError("Project can still access networks") if project.headnodes: raise BlockedError("Project still has a headnode") db.session.delete(project) db.session.commit()
def show_headnode(nodename): """Show details of a headnode. Returns a JSON object representing a headnode. The obect will have at least the following fields: * "name", the name/label of the headnode (string). * "project", the project to which the headnode belongs. * "hnics", a JSON array of hnic labels that are attached to this headnode. * "vncport", the vnc port that the headnode VM is listening on; this value can be None if the VM is powered off or has not been created yet. Example: '{"name": "headnode1", "project": "project1", "hnics": ["hnic1", "hnic2"], "vncport": 5900 }' """ headnode = _must_find(model.Headnode, nodename) get_auth_backend().require_project_access(headnode.project) return json.dumps({ 'name': headnode.label, 'project': headnode.project.label, 'hnics': [n.label for n in headnode.hnics], 'vncport': headnode.get_vncport(), 'uuid' : headnode.uuid, 'base_img': headnode.base_img, }, sort_keys = True)
def project_detach_node(project, node): """Remove a node from a project. If the node or project does not exist, a NotFoundError will be raised. If the node has network attachments or pending network actions, a BlockedError will be raised. """ project = _must_find(model.Project, project) get_auth_backend().require_project_access(project) node = _must_find(model.Node, node) if node not in project.nodes: raise NotFoundError("Node not in project") num_attachments = model.NetworkAttachment.query \ .filter(model.Nic.owner == node, model.NetworkAttachment.nic_id == model.Nic.id).count() if num_attachments != 0: raise BlockedError("Node attached to a network") for nic in node.nics: if nic.current_action is not None: raise BlockedError("Node has pending network actions") node.obm.stop_console() node.obm.delete_console() project.nodes.remove(node) db.session.commit()
def headnode_create(headnode, project, base_img): """Create headnode. If a headnode with the same name already exists, a DuplicateError will be raised. If the project does not exist, a NotFoundError will be raised. If the base image does not exist (not specified in haas.cfg) a BadArgumentError will be raised. """ valid_imgs = cfg.get('headnode', 'base_imgs') valid_imgs = [img.strip() for img in valid_imgs.split(',')] if base_img not in valid_imgs: raise BadArgumentError('Provided image is not a valid image.') _assert_absent(model.Headnode, headnode) project = _must_find(model.Project, project) get_auth_backend().require_project_access(project) headnode = model.Headnode(project, headnode, base_img) db.session.add(headnode) db.session.commit()
def node_delete_nic(node, nic): """Delete nic with given name from it's node. If the node or nic does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() nic = _must_find_n(_must_find(model.Node, node), model.Nic, nic) db.session.delete(nic) db.session.commit()
def project_create(project): """Create a project. If the project already exists, a DuplicateError will be raised. """ get_auth_backend().require_admin() _assert_absent(model.Project, project) project = model.Project(project) db.session.add(project) db.session.commit()
def switch_delete(switch): get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) if switch.ports != []: raise BlockedError("Switch %r has ports; delete them first." % switch.label) db.session.delete(switch) db.session.commit()
def headnode_stop(headnode): """Stop the headnode. This powers off the headnode. This is a hard poweroff; the VM is not given the opportunity to shut down cleanly. This does *not* unfreeze the VM; headnode_start will be the only valid API call after the VM is powered off. """ headnode = _must_find(model.Headnode, headnode) get_auth_backend().require_project_access(headnode.project) headnode.stop()
def list_projects(): """List all projects. Returns a JSON array of strings representing a list of projects. Example: '["project1", "project2", "project3"]' """ get_auth_backend().require_admin() projects = model.Project.query.all() projects = sorted([p.label for p in projects]) return json.dumps(projects)
def list_switches(): """List all switches. Returns a JSON array of strings representing a list of switches. Example: '["cisco3", "brocade1", "mock2"]' """ get_auth_backend().require_admin() switches = model.Switch.query.all() snames = sorted([s.label for s in switches]) return json.dumps(snames)
def list_project_nodes(project): """List all nodes belonging the given project. Returns a JSON array of strings representing a list of nodes. Example: '["node1", "node2", "node3"]' """ project = _must_find(model.Project, project) get_auth_backend().require_project_access(project) nodes = project.nodes nodes = [n.label for n in nodes] return json.dumps(nodes)
def list_project_networks(project): """List all private networks the project can access. Returns a JSON array of strings representing a list of networks. Example: '["net1", "net2", "net3"]' """ project = _must_find(model.Project, project) get_auth_backend().require_project_access(project) networks = project.networks_access networks = sorted([n.label for n in networks]) return json.dumps(networks)
def list_project_headnodes(project): """List all headnodes belonging the given project. Returns a JSON array of strings representing a list of headnodes. Example: '["headnode1", "headnode2", "headnode3"]' """ project = _must_find(model.Project, project) get_auth_backend().require_project_access(project) headnodes = project.headnodes headnodes = sorted([hn.label for hn in headnodes]) return json.dumps(headnodes)
def user_add_project(user, project): """Add a user to a project. If the project or user does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() user = api._must_find(User, user) project = api._must_find(model.Project, project) if project in user.projects: raise DuplicateError("User %s is already in project %s" % (user.label, project.label)) user.projects.append(project) db.session.commit()
def user_remove_project(user, project): """Remove a user from a project. If the project or user does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() user = api._must_find(User, user) project = api._must_find(model.Project, project) if project not in user.projects: raise NotFoundError("User %s is not in project %s" % (user.label, project.label)) user.projects.remove(project) db.session.commit()
def switch_register_port(switch, port): """Register a port on a switch. If the port already exists, a DuplicateError will be raised. """ get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) _assert_absent_n(switch, model.Port, port) port = model.Port(port, switch) db.session.add(port) db.session.commit()
def headnode_delete(headnode): """Delete headnode. If the node does not exist, a NotFoundError will be raised. """ headnode = _must_find(model.Headnode, headnode) get_auth_backend().require_project_access(headnode.project) if not headnode.dirty: headnode.delete() for hnic in headnode.hnics: db.session.delete(hnic) db.session.delete(headnode) db.session.commit()
def node_register_nic(node, nic, macaddr): """Register existence of nic attached to given node. If the node does not exist, a NotFoundError will be raised. If there is already an nic with that name, a DuplicateError will be raised. """ get_auth_backend().require_admin() node = _must_find(model.Node, node) _assert_absent_n(node, model.Nic, nic) nic = model.Nic(node, nic, macaddr) db.session.add(nic) db.session.commit()
def user_delete(user): """Delete user. If the user does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() # XXX: We need to do a bit of refactoring, so this is available outside of # haas.api: user = api._must_find(User, user) db.session.delete(user) db.session.commit()
def user_create(user, password, is_admin=False): """Create user with given password. If the user already exists, a DuplicateError will be raised. """ get_auth_backend().require_admin() # XXX: We need to do a bit of refactoring, so this is available outside of # haas.api: api._assert_absent(User, user) user = User(user, password, is_admin=is_admin) db.session.add(user) db.session.commit()
def node_delete(node): """Delete node. If the node does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() node = _must_find(model.Node, node) if node.nics != []: raise BlockedError("Node %r has nics; remove them before deleting %r." % (node.label, node.label)) node.obm.stop_console() node.obm.delete_console() db.session.delete(node) db.session.commit()
def project_connect_node(project, node): """Add a node to a project. If the node or project does not exist, a NotFoundError will be raised. If node is already owned by a project, a BlockedError will be raised. """ project = _must_find(model.Project, project) get_auth_backend().require_project_access(project) node = _must_find(model.Node, node) if node.project is not None: raise BlockedError("Node is already owned by a project.") project.nodes.append(node) db.session.commit()
def headnode_start(headnode): """Start the headnode. This actually boots up the headnode virtual machine. The VM is created within libvirt if needed. Once the VM has been started once, it is "frozen," and all other headnode-related api calls will fail (by raising an IllegalStateError), with the exception of headnode_stop. """ headnode = _must_find(model.Headnode, headnode) get_auth_backend().require_project_access(headnode.project) if headnode.dirty: headnode.create() headnode.start() db.session.commit()
def headnode_detach_network(headnode, hnic): """Detach a heanode's nic from any network it's on. Raises IllegalStateError if the headnode has already been started. """ headnode = _must_find(model.Headnode, headnode) get_auth_backend().require_project_access(headnode.project) hnic = _must_find_n(headnode, model.Hnic, hnic) if not headnode.dirty: raise IllegalStateError hnic.network = None db.session.commit()
def switch_delete_port(switch, port): """Delete a port on a switch. If the port does not exist, a NotFoundError will be raised. """ get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) port = _must_find_n(switch, model.Port, port) if port.nic is not None: raise BlockedError("Port %r is attached to a nic; please detach " "it first." % port.label) db.session.delete(port) db.session.commit()
def switch_register(switch, type, **kwargs): get_auth_backend().require_admin() _assert_absent(model.Switch, switch) cls = concrete_class_for(model.Switch, type) if cls is None: raise BadArgumentError('%r is not a valid switch type.' % type) cls.validate(kwargs) obj = cls(**kwargs) obj.label = switch obj.type = type db.session.add(obj) db.session.commit()
def show_network(network): """Show details of a network. Returns a JSON object representing a network. See `docs/rest_api.md` for a full description of the output. """ allocator = get_network_allocator() auth_backend = get_auth_backend() network = _must_find(model.Network, network) if network.access is not None: auth_backend.require_project_access(network.access) result = { 'name': network.label, 'channels': allocator.legal_channels_for(network.network_id), } if network.creator is None: result['creator'] = 'admin' else: result['creator'] = network.creator.label if network.access is not None: result['access'] = network.access.label return json.dumps(result, sort_keys=True)
def network_delete(network): """Delete network. If the network does not exist, a NotFoundError will be raised. If the network is connected to nodes or headnodes, or there are pending network actions involving it, a BlockedError will be raised. """ auth_backend = get_auth_backend() network = _must_find(model.Network, network) if network.creator is None: auth_backend.require_admin() else: auth_backend.require_project_access(network.creator) if len(network.attachments) != 0: raise BlockedError("Network still connected to nodes") if network.hnics: raise BlockedError("Network still connected to headnodes") if len(network.scheduled_nics) != 0: raise BlockedError("There are pending actions on this network") if network.allocated: get_network_allocator().free_network_id(network.network_id) db.session.delete(network) db.session.commit()
def node_detach_network(node, nic, network): """Detach network ``network`` from physical nic ``nic``. Raises ProjectMismatchError if the node is not in a project. Raises BlockedError if there is already a pending network action. Raises BadArgumentError if the network is not attached to the nic. """ auth_backend = get_auth_backend() node = _must_find(model.Node, node) network = _must_find(model.Network, network) nic = _must_find_n(node, model.Nic, nic) if not node.project: raise ProjectMismatchError("Node not in project") auth_backend.require_project_access(node.project) if nic.current_action: raise BlockedError("A networking operation is already active on the nic.") attachment = model.NetworkAttachment.query \ .filter_by(nic=nic, network=network).first() if attachment is None: raise BadArgumentError("The network is not attached to the nic.") db.session.add(model.NetworkingAction(nic=nic, channel=attachment.channel, new_network=None)) db.session.commit() return '', 202
def node_set_bootdev(node, bootdev): auth_backend = get_auth_backend() node = _must_find(model.Node, node) if node.project is None: auth_backend.require_admin() else: auth_backend.require_project_access(node.project) node.obm.require_legal_bootdev(bootdev) node.obm.set_bootdev(bootdev)
def show_switch(switch): """Show details of a switch. Returns a JSON object regrading the switch. See `docs/rest_api.md` for a full description of the output. FIXME: Ideally this api call should return all detail information about this switch. right now it needs support from the switch backend. """ get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) return json.dumps( { 'name': switch.label, 'ports': [{ 'label': port.label } for port in switch.ports], }, sort_keys=True)
def node_register(node, **kwargs): """Create node. If the node already exists, a DuplicateError will be raised. The node is initially registered with no nics; see the method node_register_nic. """ get_auth_backend().require_admin() _assert_absent(model.Node, node) obm_type = kwargs['obm']['type'] cls = concrete_class_for(model.Obm, obm_type) if cls is None: raise BadArgumentError('%r is not a valid OBM type.' % obm_type) cls.validate(kwargs['obm']) node_obj = model.Node(label=node, obm=cls(**kwargs['obm'])) if 'metadata' in kwargs: for label, value in kwargs['metadata'].items(): metadata_obj = model.Metadata(label, json.dumps(value), node_obj) db.session.add(metadata_obj) db.session.add(node_obj) db.session.commit()
def headnode_create_hnic(headnode, hnic): """Create hnic attached to given headnode. If the node does not exist, a NotFoundError will be raised. If there is already an hnic with that name, a DuplicateError will be raised. If the headnode's VM has already created (headnode is not "dirty"), raises an IllegalStateError """ headnode = _must_find(model.Headnode, headnode) get_auth_backend().require_project_access(headnode.project) _assert_absent_n(headnode, model.Hnic, hnic) if not headnode.dirty: raise IllegalStateError hnic = model.Hnic(headnode, hnic) db.session.add(hnic) db.session.commit()
def wrapper(**kwargs): kwargs = _do_validation(schema, kwargs) init_auth() username = auth.get_auth_backend().get_user() or '(guest)' logger.info('%s - API call: %s(%s)' % (username, f.__name__, _format_arglist(**kwargs))) ret = f(**kwargs) if ret is None: ret = '' return ret
def port_detach_nic(switch, port): """Detach a port from the nic it's attached to If the port does not exist, a NotFoundError will be raised. If the port is not connected to anything, a NotFoundError will be raised. If the port is attached to a node which is not free, a BlockedError will be raised. """ get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) port = _must_find_n(switch, model.Port, port) if port.nic is None: raise NotFoundError(port.label + " not attached") if port.nic.owner.project is not None: raise BlockedError("The port is attached to a node which is not free") port.nic = None db.session.commit()
def port_connect_nic(switch, port, node, nic): """Connect a port on a switch to a nic on a node. If any of the three arguments does not exist, a NotFoundError will be raised. If the port or the nic is already connected to something, a DuplicateError will be raised. """ get_auth_backend().require_admin() switch = _must_find(model.Switch, switch) port = _must_find_n(switch, model.Port, port) node = _must_find(model.Node, node) nic = _must_find_n(node, model.Nic, nic) if nic.port is not None: raise DuplicateError(nic.label) if port.nic is not None: raise DuplicateError(port.label) nic.port = port db.session.commit()
def validate_state(): """Do some sanity checking before kicking things off. In particular: * Make sure we have extensions loaded for: * a network allocator * an auth backend (More checks may be added in the future). If any of the checks fail, ``validate_state`` aborts the program. """ if get_network_allocator() is None: sys.exit("ERROR: No network allocator registered; make sure your " "haas.cfg loads an extension which provides the network " "allocator.") if auth.get_auth_backend() is None: sys.exit("ERROR: No authentication/authorization backend registered; " "make sure your haas.cfg loads an extension which provides " "the auth backend.")
def network_revoke_project_access(project, network): """Remove access to <network> from <project>. If the project or network does not exist, a NotFoundError will be raised. If the project is the owner of the network a BlockedError will be raised. """ auth_backend = get_auth_backend() network = _must_find(model.Network, network) project = _must_find(model.Project, project) # must be admin, the owner of the network, or <project> to remove # <project>. if network.access: if not (auth_backend.have_admin() or (network.owner is not None and auth_backend.have_project_access(network.owner)) or (project in network.access and auth_backend.have_project_access(project))): raise AuthorizationError("You are not authorized to remove the " "specified project from this network.") if project not in network.access: raise NotFoundError("Network %r is not in project %r" % (network.label, project.label)) if project is network.owner: raise BlockedError("Project %r is owner of network %r and " "its access cannot be removed" % (project.label, network.label)) # TODO: Make this and the next loop more SQLAlchemy-friendly for attachment in network.attachments: if attachment.nic.owner.project.label == project.label: raise BlockedError( "Project still has node(s) attached to the network") for hnic in network.hnics: if hnic.owner.project.label == project.label: raise BlockedError( "Project still has headnode(s) attached to the network") network.access.remove(project) db.session.commit()
def network_grant_project_access(project, network): """Add access to <network> to <project>. If the project or network does not exist, a NotFoundError will be raised. If the project already has access to the network a DuplicateError will be raised. """ network = _must_find(model.Network, network) project = _must_find(model.Project, project) auth_backend = get_auth_backend() # Must be admin or the owner of the network to add projects if network.owner is None: auth_backend.require_admin() else: auth_backend.require_project_access(network.owner) if project in network.access: raise DuplicateError('Network %s is already in project %s' % (network.label, project.label)) network.access.append(project) db.session.commit()
def auth_backend(): return get_auth_backend()
def node_connect_network(node, nic, network, channel=None): """Connect a physical NIC to a network, on channel. If channel is ``None``, use the allocator default. Raises ProjectMismatchError if the node is not in a project, or if the project does not have access rights to the given network. Raises BlockedError if there is a pending network action, or if the network is already attached to the nic, or if the channel is in use. Raises BadArgumentError if the channel is invalid for the network. """ def _have_attachment(nic, query): """Return whether there are any attachments matching ``query`` for ``nic``. ``query`` should an argument suitable to pass to db.query(...).filter """ return model.NetworkAttachment.query.filter( model.NetworkAttachment.nic == nic, query, ).count() != 0 auth_backend = get_auth_backend() node = _must_find(model.Node, node) nic = _must_find_n(node, model.Nic, nic) network = _must_find(model.Network, network) if not node.project: raise ProjectMismatchError("Node not in project") auth_backend.require_project_access(node.project) project = node.project allocator = get_network_allocator() if nic.port is None: raise NotFoundError("No port is connected to given nic.") if nic.current_action: raise BlockedError( "A networking operation is already active on the nic.") if (network.access) and (project not in network.access): raise ProjectMismatchError( "Project does not have access to given network.") if _have_attachment(nic, model.NetworkAttachment.network == network): raise BlockedError("The network is already attached to the nic.") if channel is None: channel = allocator.get_default_channel() if _have_attachment(nic, model.NetworkAttachment.channel == channel): raise BlockedError("The channel is already in use on the nic.") if not allocator.is_legal_channel_for(channel, network.network_id): raise BadArgumentError("Channel %r, is not legal for this network." % channel) db.session.add( model.NetworkingAction(nic=nic, new_network=network, channel=channel)) db.session.commit() return '', 202