def _upload_image( self, name, filename, data, meta, wait, timeout, use_import=False, stores=None, all_stores=None, all_stores_must_succeed=None, **image_kwargs, ): if use_import: raise exceptions.InvalidRequest( "Glance v1 does not support image import") if stores or all_stores or all_stores_must_succeed: raise exceptions.InvalidRequest( "Glance v1 does not support stores") # NOTE(mordred) wait and timeout parameters are unused, but # are present for ease at calling site. if filename and not data: image_data = open(filename, 'rb') else: image_data = data image_kwargs['properties'].update(meta) image_kwargs['name'] = name # TODO(mordred) Convert this to use image Resource image = self._connection._get_and_munchify( 'image', self.post('/images', json=image_kwargs)) checksum = image_kwargs['properties'].get(self._IMAGE_MD5_KEY, '') try: # Let us all take a brief moment to be grateful that this # is not actually how OpenStack APIs work anymore headers = { 'x-glance-registry-purge-props': 'false', } if checksum: headers['x-image-meta-checksum'] = checksum image = self._connection._get_and_munchify( 'image', self.put('/images/{id}'.format(id=image.id), headers=headers, data=image_data)) except exc.OpenStackCloudHTTPError: self.log.debug("Deleting failed upload of image %s", name) try: self.delete('/images/{id}'.format(id=image.id)) except exc.OpenStackCloudHTTPError: # We're just trying to clean up - if it doesn't work - shrug self.log.warning( "Failed deleting image after we failed uploading it.", exc_info=True) raise return self._connection._normalize_image(image)
def role_assignments_filter(self, domain=None, project=None, group=None, user=None): """Retrieve a generator of roles assigned to user/group :param domain: Either the ID of a domain or a :class:`~openstack.identity.v3.domain.Domain` instance. :param project: Either the ID of a project or a :class:`~openstack.identity.v3.project.Project` instance. :param group: Either the ID of a group or a :class:`~openstack.identity.v3.group.Group` instance. :param user: Either the ID of a user or a :class:`~openstack.identity.v3.user.User` instance. :return: A generator of role instances. :rtype: :class:`~openstack.identity.v3.role.Role` """ if domain and project: raise exception.InvalidRequest( 'Only one of domain or project can be specified') if domain is None and project is None: raise exception.InvalidRequest( 'Either domain or project should be specified') if group and user: raise exception.InvalidRequest( 'Only one of group or user can be specified') if group is None and user is None: raise exception.InvalidRequest( 'Either group or user should be specified') if domain: domain = self._get_resource(_domain.Domain, domain) if group: group = self._get_resource(_group.Group, group) return self._list( _role_domain_group_assignment.RoleDomainGroupAssignment, paginated=False, domain_id=domain.id, group_id=group.id) else: user = self._get_resource(_user.User, user) return self._list( _role_domain_user_assignment.RoleDomainUserAssignment, paginated=False, domain_id=domain.id, user_id=user.id) else: project = self._get_resource(_project.Project, project) if group: group = self._get_resource(_group.Group, group) return self._list( _role_project_group_assignment.RoleProjectGroupAssignment, paginated=False, project_id=project.id, group_id=group.id) else: user = self._get_resource(_user.User, user) return self._list( _role_project_user_assignment.RoleProjectUserAssignment, paginated=False, project_id=project.id, user_id=user.id)
def _prepare_request(self, requires_id=True, prepend_key=False): """Prepare a request to be sent to the server Create operations don't require an ID, but all others do, so only try to append an ID when it's needed with requires_id. Create and update operations sometimes require their bodies to be contained within an dict -- if the instance contains a resource_key and prepend_key=True, the body will be wrapped in a dict with that key. Return a _Request object that contains the constructed URI as well a body and headers that are ready to send. Only dirty body and header contents will be returned. """ body = self._body.dirty if prepend_key and self.resource_key is not None: body = {self.resource_key: body} headers = self._header.dirty uri = self.base_path % self._uri.attributes if requires_id: if self.id is None: raise exceptions.InvalidRequest( "Request requires an ID but none was found") uri = utils.urljoin(uri, self.id) return _Request(uri, body, headers)
def _prepare_request(self, requires_id=None, prepend_key=False, patch=False, base_path=None, params=None, **kwargs): """Prepare a request to be sent to the server Create operations don't require an ID, but all others do, so only try to append an ID when it's needed with requires_id. Create and update operations sometimes require their bodies to be contained within an dict -- if the instance contains a resource_key and prepend_key=True, the body will be wrapped in a dict with that key. If patch=True, a JSON patch is prepared instead of the full body. Return a _Request object that contains the constructed URI as well a body and headers that are ready to send. Only dirty body and header contents will be returned. """ if requires_id is None: requires_id = self.requires_id body = self._prepare_request_body(patch, prepend_key) headers = {} if base_path is None: base_path = self.base_path uri = base_path % self._uri.attributes if requires_id: if self.id is None: raise exceptions.InvalidRequest( "Request requires an ID but none was found") uri = utils.urljoin(uri, self.id) uri = utils.urljoin(self.location.project['id'], uri) return resource._Request(uri, body, headers)
def validate_template(self, template, environment=None, template_url=None, ignore_errors=None): """Validates a template. :param template: The stack template on which the validation is performed. :param environment: A JSON environment for the stack, if provided. :param template_url: A URI to the location containing the stack template for validation. This parameter is only required if the ``template`` parameter is None. This parameter is ignored if ``template`` is specified. :param ignore_errors: A string containing comma separated error codes to ignore. Currently the only valid error code is '99001'. :returns: The result of template validation. :raises: :class:`~openstack.exceptions.InvalidRequest` if neither `template` not `template_url` is provided. :raises: :class:`~openstack.exceptions.HttpException` if the template fails the validation. """ if template is None and template_url is None: raise exceptions.InvalidRequest( "'template_url' must be specified when template is None") tmpl = _template.Template.new() return tmpl.validate(self._session, template, environment=environment, template_url=template_url, ignore_errors=ignore_errors)
def delete_host(self, host, segment_id=None, ignore_missing=True): """Delete the host. :param segment_id: The ID of a failover segment. :param host: The value can be the ID of a host or a :class: `~masakariclient.sdk.ha.v1.host.Host` instance. :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be raised when the host does not exist. When set to ``True``, no exception will be set when attempting to delete a nonexistent host. :returns: ``None`` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. :raises: :class:`~openstack.exceptions.InvalidRequest` when segment_id is None. """ if segment_id is None: raise exceptions.InvalidRequest("'segment_id' must be specified.") host_id = resource.Resource._get_id(host) return self._delete(_host.Host, host_id, segment_id=segment_id, ignore_missing=ignore_missing)
def import_image(self, image, method='glance-direct', uri=None): """Import data to an existing image Interoperable image import process are introduced in the Image API v2.6. It mainly allow image importing from an external url and let Image Service download it by itself without sending binary data at image creation. :param image: The value can be the ID of a image or a :class:`~openstack.image.v2.image.Image` instance. :param method: Method to use for importing the image. A valid value is glance-direct or web-download. :param uri: Required only if using the web-download import method. This url is where the data is made available to the Image service. :returns: None """ image = self._get_resource(_image.Image, image) # as for the standard image upload function, container_format and # disk_format are required for using image import process if not all([image.container_format, image.disk_format]): raise exceptions.InvalidRequest( "Both container_format and disk_format are required for" " importing an image") image.import_image(self, method=method, uri=uri)
def import_image(self, session, method='glance-direct', uri=None, store=None, stores=None, all_stores=None, all_stores_must_succeed=None): """Import Image via interoperable image import process""" if all_stores and (store or stores): raise exceptions.InvalidRequest( "all_stores is mutually exclusive with" " store and stores") if store and stores: raise exceptions.InvalidRequest( "store and stores are mutually exclusive." " Please just use stores.") if store: stores = [store] else: stores = stores or [] url = utils.urljoin(self.base_path, self.id, 'import') json = {'method': {'name': method}} if uri: if method == 'web-download': json['method']['uri'] = uri else: raise exceptions.InvalidRequest('URI is only supported with ' 'method: "web-download"') if all_stores is not None: json['all_stores'] = all_stores if all_stores_must_succeed is not None: json['all_stores_must_succeed'] = all_stores_must_succeed for s in stores: json.setdefault('stores', []) json['stores'].append(s.id) headers = {} # Backward compat if store is not None: headers = {'X-Image-Meta-Store': store.id} session.post(url, json=json, headers=headers)
def import_image(self, session, method='glance-direct', uri=None): """Import Image via interoperable image import process""" url = utils.urljoin(self.base_path, self.id, 'import') json = {'method': {'name': method}} if uri: if method == 'web-download': json['method']['uri'] = uri else: raise exceptions.InvalidRequest('URI is only supported with ' 'method: "web-download"') session.post(url, json=json)
def __init__(self, debug=False): utils.enable_logging(debug=debug, stream=sys.stdout) try: self.conn = connection.Connection(auth_url=self.auth_url, user_domain_id=self.userDomainId, project_id=self.projectId, username=self.username, password=self.password, verify=False) except _exceptions.InvalidRequest as e: raise _exceptions.InvalidRequest(message='init connect error')
def _prepare_request(self, requires_id=False, prepend_key=False): body = self._body.dirty if prepend_key and self.resource_key is not None: body = {self.resource_key: body} headers = self._header.dirty if requires_id: if self.id is None: raise exceptions.InvalidRequest( "Request requires an ID but none was found") uri = utils.urljoin(self.base_path, self.id) return _Request(uri, body, headers)
def upload_image(self, container_format=None, disk_format=None, data=None, **attrs): """Create and upload a new image from attributes .. warning: This method is deprecated - and also doesn't work very well. Please stop using it immediately and switch to `create_image`. :param container_format: Format of the container. A valid value is ami, ari, aki, bare, ovf, ova, or docker. :param disk_format: The format of the disk. A valid value is ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, or iso. :param data: The data to be uploaded as an image. :param dict attrs: Keyword arguments which will be used to create a :class:`~openstack.image.v2.image.Image`, comprised of the properties on the Image class. :returns: The results of image creation :rtype: :class:`~openstack.image.v2.image.Image` """ warnings.warn("upload_image is deprecated. Use create_image instead.") # container_format and disk_format are required to be set # on the image by the time upload_image is called, but they're not # required by the _create call. Enforce them here so that we don't # need to handle a failure in _create, as upload_image will # return a 400 with a message about disk_format and container_format # not being set. if not all([container_format, disk_format]): raise exceptions.InvalidRequest( "Both container_format and disk_format are required") img = self._create(_image.Image, disk_format=disk_format, container_format=container_format, **attrs) # TODO(briancurtin): Perhaps we should run img.upload_image # in a background thread and just return what is called by # self._create, especially because the upload_image call doesn't # return anything anyway. Otherwise this blocks while uploading # significant amounts of image data. img.data = data img.upload(self) return img
def tag_action(self, session, **attrs): if not self.allow_create: raise exceptions.MethodNotSupported(self, "create") request = self._prepare_request(requires_id=False) endpoint_override = self.service.get_endpoint_override() #super(TagAction, self).create(session, ) response = session.post(request.uri, endpoint_filter=self.service, endpoint_override=endpoint_override, json=attrs, headers=request.headers) if not response.status_code == 204: _logger.debug( 'request AS service tag action url is %s response code is %s ' % (response.url, response.status_code)) raise exceptions.InvalidRequest( "Request AS service tag action %s failed" % request.uri) else: return self
def delete_notification(self, session): request = self._prepare_request(requires_id=False) endpoint_override = self.service.get_endpoint_override() response = session.delete(request.uri, endpoint_filter=self.service, endpoint_override=endpoint_override, headers=request.headers) if not response.status_code == 204: _logger.debug( 'failed request AS service delete notification url is %s response code is %s ' % (response.url, response.status_code)) raise exceptions.InvalidRequest( "Request AS service delete notification %s failed" % request.uri) else: return self
def get_host(self, host, segment_id=None): """Get a single host. :param segment_id: The ID of a failover segment. :param host: The value can be the ID of a host or a :class: `~masakariclient.sdk.ha.v1.host.Host` instance. :returns: One :class:`~masakariclient.sdk.ha.v1.host.Host` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. :raises: :class:`~openstack.exceptions.InvalidRequest` when segment_id is None. """ if segment_id is None: raise exceptions.InvalidRequest("'segment_id' must be specified.") host_id = resource.Resource._get_id(host) return self._get(_host.Host, host_id, segment_id=segment_id)
def call_back(self, session, **attrs): request = self._prepare_request(requires_id=False) endpoint_override = self.service.get_endpoint_override() response = session.put(request.uri, endpoint_filter=self.service, endpoint_override=endpoint_override, json=attrs, headers=request.headers) if not response.status_code == 204: _logger.debug( 'request AS service lifecycle hook call back url is %s response code is %s ' % (response.url, response.status_code)) raise exceptions.InvalidRequest( "Request AS service lifecycle hook call back %s failed" % request.uri) else: return self
def _prepare_request(self, requires_id=True, prepend_key=False): body = self._body.dirty if prepend_key and self.resource_key is not None: body = {self.resource_key: body} headers = self._header.dirty headers.update({'Content-type': 'application/json'}) # Notes: take cares for create/put, need Content-Length in headers if requires_id is False or len(body) == 0: headers.update({'Content-Length': str(len(str(body)))}) uri = self.base_path % self._uri.attributes if requires_id: if self.id is None: raise exceptions.InvalidRequest( "Request requires an ID but none was found") uri = utils.urljoin(uri, self.id) return resource._Request(uri, body, headers)
def _prepare_request(self, requires_id=True, prepend_key=False): body = self._body.dirty if prepend_key and self.resource_key is not None: body = {self.resource_key: body} headers = self._header.dirty headers.update({ 'Content-type': 'application/json', 'X-Language': 'en-us' }) uri = self.base_path % self._uri.attributes if requires_id: if self.id is None: raise exceptions.InvalidRequest( "Request requires an ID but none was found") uri = utils.urljoin(uri, self.id) return resource._Request(uri, body, headers)
def list(cls, session, paginated=False, **params): """This method is a generator which yields resource objects. This resource object list generator handles pagination and takes query params for response filtering. :param session: The session to use for making this request. :type session: :class:`~openstack.session.Session` :param bool paginated: ``True`` if a GET to this resource returns a paginated series of responses, or ``False`` if a GET returns only one page of data. **When paginated is False only one page of data will be returned regardless of the API's support of pagination.** :param dict params: These keyword arguments are passed through the :meth:`~openstack.resource2.QueryParamter._transpose` method to find if any of them match expected query parameters to be sent in the *params* argument to :meth:`~openstack.session.Session.get`. They are additionally checked against the :data:`~openstack.resource2.Resource.base_path` format string to see if any path fragments need to be filled in by the contents of this argument. :return: A generator of :class:`Resource` objects. :raises: :exc:`~openstack.exceptions.MethodNotSupported` if :data:`Resource.allow_list` is not set to ``True``. """ if not cls.allow_list: raise exceptions.MethodNotSupported(cls, "list") more_data = True query_params = cls._query_mapping._transpose(params) if cls.query_page_size_key and \ cls.query_page_size_key not in query_params: raise exceptions.InvalidRequest('query parameter %s is required.' % cls.query_page_size_key) if cls.query_page_number_key and \ cls.query_page_number_key not in query_params: raise exceptions.InvalidRequest('query parameter %s is required.' % cls.query_page_number_key) uri = cls.get_list_uri(params) while more_data: endpoint_override = cls.service.get_endpoint_override() resp = session.get(uri, endpoint_filter=cls.service, endpoint_override=endpoint_override, headers={"Accept": "application/json"}, params=query_params) response_json = resp.json() cls.check_error(response_json) if cls.resources_key: resources = cls.find_value_by_accessor(response_json, cls.resources_key) else: resources = response_json if resources is None: resources = [] if not resources: return for data in resources: value = cls.existing(**data) yield value if not paginated: return more_data, next_page_num = cls.get_next_pagination( response_json, query_params) query_params[cls.query_page_number_key] = next_page_num
def import_image( self, image, method='glance-direct', uri=None, store=None, stores=None, all_stores=None, all_stores_must_succeed=None, ): """Import data to an existing image Interoperable image import process are introduced in the Image API v2.6. It mainly allow image importing from an external url and let Image Service download it by itself without sending binary data at image creation. :param image: The value can be the ID of a image or a :class:`~openstack.image.v2.image.Image` instance. :param method: Method to use for importing the image. A valid value is glance-direct or web-download. :param uri: Required only if using the web-download import method. This url is where the data is made available to the Image service. :param store: Used when enabled_backends is activated in glance. The value can be the id of a store or a :class:`~openstack.image.v2.service_info.Store` instance. :param stores: List of stores to be used when enabled_backends is activated in glance. List values can be the id of a store or a :class:`~openstack.image.v2.service_info.Store` instance. :param all_stores: Upload to all available stores. Mutually exclusive with ``store`` and ``stores``. :param all_stores_must_succeed: When set to True, if an error occurs during the upload in at least one store, the worfklow fails, the data is deleted from stores where copying is done (not staging), and the state of the image is unchanged. When set to False, the workflow will fail (data deleted from stores, …) only if the import fails on all stores specified by the user. In case of a partial success, the locations added to the image will be the stores where the data has been correctly uploaded. Default is True. :returns: None """ image = self._get_resource(_image.Image, image) if all_stores and (store or stores): raise exceptions.InvalidRequest( "all_stores is mutually exclusive with" " store and stores") if store is not None: if stores: raise exceptions.InvalidRequest( "store and stores are mutually exclusive") store = self._get_resource(_si.Store, store) stores = stores or [] new_stores = [] for s in stores: new_stores.append(self._get_resource(_si.Store, s)) stores = new_stores # as for the standard image upload function, container_format and # disk_format are required for using image import process if not all([image.container_format, image.disk_format]): raise exceptions.InvalidRequest( "Both container_format and disk_format are required for" " importing an image") image.import_image( self, method=method, uri=uri, store=store, stores=stores, all_stores=all_stores, all_stores_must_succeed=all_stores_must_succeed, )