# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Nova User API client library. """ import base64 import boto import boto.exception import httplib import re import string from boto.ec2.regioninfo import RegionInfo DEFAULT_CLC_URL = 'http://127.0.0.1:8773' DEFAULT_REGION = 'nova' class UserInfo(object): """ Information about a Nova user, as parsed through SAX. **Fields Include** * username * accesskey * secretkey * file (optional) containing zip of X509 cert & rc file """ def __init__(self, connection=None, username=None, endpoint=None): self.connection = connection self.username = username self.endpoint = endpoint def __repr__(self): return 'UserInfo:%s' % self.username def startElement(self, name, attrs, connection): return None def endElement(self, name, value, connection): if name == 'username': self.username = str(value) elif name == 'file': self.file = base64.b64decode(str(value)) elif name == 'accesskey': self.accesskey = str(value) elif name == 'secretkey': self.secretkey = str(value) class UserRole(object): """ Information about a Nova user's role, as parsed through SAX. **Fields include** * role """ def __init__(self, connection=None): self.connection = connection self.role = None def __repr__(self): return 'UserRole:%s' % self.role def startElement(self, name, attrs, connection): return None def endElement(self, name, value, connection): if name == 'role': self.role = value else: setattr(self, name, str(value)) class ProjectInfo(object): """ Information about a Nova project, as parsed through SAX. **Fields include** * projectname * description * projectManagerId * memberIds """ def __init__(self, connection=None): self.connection = connection self.projectname = None self.description = None self.projectManagerId = None self.memberIds = [] def __repr__(self): return 'ProjectInfo:%s' % self.projectname def startElement(self, name, attrs, connection): return None def endElement(self, name, value, connection): if name == 'projectname': self.projectname = value elif name == 'description': self.description = value elif name == 'projectManagerId': self.projectManagerId = value elif name == 'memberId': self.memberIds.append(value) else: setattr(self, name, str(value)) class ProjectMember(object): """ Information about a Nova project member, as parsed through SAX. **Fields include** * memberId """ def __init__(self, connection=None): self.connection = connection self.memberId = None def __repr__(self): return 'ProjectMember:%s' % self.memberId def startElement(self, name, attrs, connection): return None def endElement(self, name, value, connection): if name == 'member': self.memberId = value else: setattr(self, name, str(value)) class HostInfo(object): """ Information about a Nova Host, as parsed through SAX. **Fields Include** * Hostname * Compute service status * Volume service status * Instance count * Volume count """ def __init__(self, connection=None): self.connection = connection self.hostname = None self.compute = None self.volume = None self.instance_count = 0 self.volume_count = 0 def __repr__(self): return 'Host:%s' % self.hostname # this is needed by the sax parser, so ignore the ugly name def startElement(self, name, attrs, connection): return None # this is needed by the sax parser, so ignore the ugly name def endElement(self, name, value, connection): fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) setattr(self, fixed_name, value) class Vpn(object): """ Information about a Vpn, as parsed through SAX **Fields Include** * instance_id * project_id * public_ip * public_port * created_at * internal_ip * state """ def __init__(self, connection=None): self.connection = connection self.instance_id = None self.project_id = None def __repr__(self): return 'Vpn:%s:%s' % (self.project_id, self.instance_id) def startElement(self, name, attrs, connection): return None def endElement(self, name, value, connection): fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) setattr(self, fixed_name, value) class InstanceType(object): """ Information about a Nova instance type, as parsed through SAX. **Fields include** * name * vcpus * disk_gb * memory_mb * flavor_id """ def __init__(self, connection=None): self.connection = connection self.name = None self.vcpus = None self.disk_gb = None self.memory_mb = None self.flavor_id = None def __repr__(self): return 'InstanceType:%s' % self.name def startElement(self, name, attrs, connection): return None def endElement(self, name, value, connection): if name == "memoryMb": self.memory_mb = str(value) elif name == "flavorId": self.flavor_id = str(value) elif name == "diskGb": self.disk_gb = str(value) else: setattr(self, name, str(value)) class NovaAdminClient(object): def __init__( self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION, access_key=None, secret_key=None, **kwargs): parts = self.split_clc_url(clc_url) self.clc_url = clc_url self.region = region self.access = access_key self.secret = secret_key self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, aws_secret_access_key=secret_key, is_secure=parts['is_secure'], region=RegionInfo(None, region, parts['ip']), port=parts['port'], path='/services/Admin', **kwargs) self.apiconn.APIVersion = 'nova' def connection_for(self, username, project, clc_url=None, region=None, **kwargs): """Returns a boto ec2 connection for the given username.""" if not clc_url: clc_url = self.clc_url if not region: region = self.region parts = self.split_clc_url(clc_url) user = self.get_user(username) access_key = '%s:%s' % (user.accesskey, project) return boto.connect_ec2(aws_access_key_id=access_key, aws_secret_access_key=user.secretkey, is_secure=parts['is_secure'], region=RegionInfo(None, self.region, parts['ip']), port=parts['port'], path='/services/Cloud', **kwargs) def split_clc_url(self, clc_url): """Splits a cloud controller endpoint url.""" parts = httplib.urlsplit(clc_url) is_secure = parts.scheme == 'https' ip, port = parts.netloc.split(':') return {'ip': ip, 'port': int(port), 'is_secure': is_secure} def get_users(self): """Grabs the list of all users.""" return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) def get_user(self, name): """Grab a single user by name.""" try: return self.apiconn.get_object('DescribeUser', {'Name': name}, UserInfo) except boto.exception.BotoServerError, e: if e.status == 400 and e.error_code == 'NotFound': return None raise def has_user(self, username): """Determine if user exists.""" return self.get_user(username) != None def create_user(self, username): """Creates a new user, returning the userinfo object with access/secret.""" return self.apiconn.get_object('RegisterUser', {'Name': username}, UserInfo) def delete_user(self, username): """Deletes a user.""" return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo) def get_roles(self, project_roles=True): """Returns a list of available roles.""" return self.apiconn.get_list('DescribeRoles', {'ProjectRoles': project_roles}, [('item', UserRole)]) def get_user_roles(self, user, project=None): """Returns a list of roles for the given user. Omitting project will return any global roles that the user has. Specifying project will return only project specific roles. """ params = {'User': user} if project: params['Project'] = project return self.apiconn.get_list('DescribeUserRoles', params, [('item', UserRole)]) def add_user_role(self, user, role, project=None): """Add a role to a user either globally or for a specific project.""" return self.modify_user_role(user, role, project=project, operation='add') def remove_user_role(self, user, role, project=None): """Remove a role from a user either globally or for a specific project.""" return self.modify_user_role(user, role, project=project, operation='remove') def modify_user_role(self, user, role, project=None, operation='add', **kwargs): """Add or remove a role for a user and project.""" params = {'User': user, 'Role': role, 'Project': project, 'Operation': operation} return self.apiconn.get_status('ModifyUserRole', params) def get_projects(self, user=None): """Returns a list of all projects.""" if user: params = {'User': user} else: params = {} return self.apiconn.get_list('DescribeProjects', params, [('item', ProjectInfo)]) def get_project(self, name): """Returns a single project with the specified name.""" project = self.apiconn.get_object('DescribeProject', {'Name': name}, ProjectInfo) if project.projectname != None: return project def create_project(self, projectname, manager_user, description=None, member_users=None): """Creates a new project.""" params = {'Name': projectname, 'ManagerUser': manager_user, 'Description': description, 'MemberUsers': member_users} return self.apiconn.get_object('RegisterProject', params, ProjectInfo) def modify_project(self, projectname, manager_user=None, description=None): """Modifies an existing project.""" params = {'Name': projectname, 'ManagerUser': manager_user, 'Description': description} return self.apiconn.get_status('ModifyProject', params) def delete_project(self, projectname): """Permanently deletes the specified project.""" return self.apiconn.get_object('DeregisterProject', {'Name': projectname}, ProjectInfo) def get_project_members(self, name): """Returns a list of members of a project.""" return self.apiconn.get_list('DescribeProjectMembers', {'Name': name}, [('item', ProjectMember)]) def add_project_member(self, user, project): """Adds a user to a project.""" return self.modify_project_member(user, project, operation='add') def remove_project_member(self, user, project): """Removes a user from a project.""" return self.modify_project_member(user, project, operation='remove') def modify_project_member(self, user, project, operation='add'): """Adds or removes a user from a project.""" params = {'User': user, 'Project': project, 'Operation': operation} return self.apiconn.get_status('ModifyProjectMember', params) def get_zip(self, user, project): """Returns the content of a zip file containing novarc and access credentials.""" params = {'Name': user, 'Project': project} zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) return zip.file def start_vpn(self, project): """ Starts the vpn for a user """ return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn) def get_vpns(self): """Return a list of vpn with project name""" return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)]) def get_hosts(self): return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) def get_instance_types(self): """Grabs the list of all users.""" return self.apiconn.get_list('DescribeInstanceTypes', {}, [('item', InstanceType)])