blob: b39871d3672c82f79ebaabc48737084998852db1 [file] [log] [blame]
Elena Ezhova1c2ebae2017-07-03 18:29:05 +04001# -*- coding: utf-8 -*-
2"""
3Module extending the salt.modules.glance modules.
4
5This module adds functionality for managing Glance V2 tasks by exposing the
6following functions:
7 - task_create
8 - task_show
9 - task_list
10
11:optdepends: - glanceclient Python adapter
12:configuration: This module is not usable until the following are specified
13 either in a pillar or in the minion's config file::
14
15 keystone.user: admin
16 keystone.password: verybadpass
17 keystone.tenant: admin
18 keystone.insecure: False #(optional)
19 keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
20
21 If configuration for multiple openstack accounts is required, they can be
22 set up as different configuration profiles:
23 For example::
24
25 openstack1:
26 keystone.user: admin
27 keystone.password: verybadpass
28 keystone.tenant: admin
29 keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
30
31 openstack2:
32 keystone.user: admin
33 keystone.password: verybadpass
34 keystone.tenant: admin
35 keystone.auth_url: 'http://127.0.0.2:5000/v2.0/'
36
37 With this configuration in place, any of the glance functions can
38 make use of a configuration profile by declaring it explicitly.
39 For example::
40
41 salt '*' glance.image_list profile=openstack1
42"""
43
44# Import Python libs
45from __future__ import absolute_import
46import logging
47import pprint
48import re
49
50# Import salt libs
51from salt.exceptions import SaltInvocationError
52
53from salt.version import (
54 __version__,
55 SaltStackVersion
56 )
57# is there not SaltStackVersion.current() to get
58# the version of the salt running this code??
59_version_ary = __version__.split('.')
60CUR_VER = SaltStackVersion(_version_ary[0], _version_ary[1])
61BORON = SaltStackVersion.from_name('Boron')
62
63# pylint: disable=import-error
64HAS_GLANCE = False
65try:
66 from glanceclient import client
67 from glanceclient import exc
68 HAS_GLANCE = True
69except ImportError:
70 pass
71
Elena Ezhova1c2ebae2017-07-03 18:29:05 +040072
73logging.basicConfig(level=logging.DEBUG)
74log = logging.getLogger(__name__)
75
76
77def __virtual__():
78 '''
79 Only load this module if glance
80 is installed on this minion.
81 '''
82 if not HAS_GLANCE:
83 return False, ("The glance execution module cannot be loaded: "
84 "the glanceclient python library is not available.")
Elena Ezhova1c2ebae2017-07-03 18:29:05 +040085 return True
86
87
88__opts__ = {}
89
90
91def _auth(profile=None, api_version=2, **connection_args):
92 '''
93 Set up glance credentials, returns
94 `glanceclient.client.Client`. Optional parameter
95 "api_version" defaults to 2.
96
97 Only intended to be used within glance-enabled modules
98 '''
99
Oleg Iurchenko4afbbbb2018-02-20 15:50:47 +0200100 kstone = __salt__['keystoneng.auth'](profile, **connection_args)
Vasyl Saienko0c896de2018-03-09 11:39:06 +0200101 g_endpoint = __salt__['keystoneng.endpoint_get']('glance', profile=profile)
Oleg Iurchenko4afbbbb2018-02-20 15:50:47 +0200102 glance_client = client.Client(api_version, session=kstone.session, endpoint=g_endpoint.get('url'))
103 return glance_client
Elena Ezhova1c2ebae2017-07-03 18:29:05 +0400104
105
106def _validate_image_params(visibility=None, container_format='bare',
107 disk_format='raw', tags=None, **kwargs):
108 # valid options for "visibility":
109 v_list = ['public', 'private', 'shared', 'community']
110 # valid options for "container_format":
111 cf_list = ['ami', 'ari', 'aki', 'bare', 'ovf']
112 # valid options for "disk_format":
113 df_list = ['ami', 'ari', 'aki', 'vhd', 'vmdk',
114 'raw', 'qcow2', 'vdi', 'iso']
115
116 if visibility is not None:
117 if visibility not in v_list:
118 raise SaltInvocationError('"visibility" needs to be one ' +
119 'of the following: {0}'.format(
120 ', '.join(v_list)))
121 if container_format not in cf_list:
122 raise SaltInvocationError('"container_format" needs to be ' +
123 'one of the following: {0}'.format(
124 ', '.join(cf_list)))
125 if disk_format not in df_list:
126 raise SaltInvocationError('"disk_format" needs to be one ' +
127 'of the following: {0}'.format(
128 ', '.join(df_list)))
129 if tags:
130 if not isinstance(tags, list):
131 raise SaltInvocationError('Incorrect input type for the {0} '
132 'parameter: expected: {1}, '
133 'got {2}'.format("tags", list,
134 type(tags)))
135
136
137def _validate_task_params(task_type, input_params):
138 # Only import tasks are currently supported
139 # TODO(eezhova): Add support for "export" and "clone" task types
140 valid_task_types = ["import", ]
141
142 import_required_params = {"import_from", "import_from_format",
143 "image_properties"}
144
145 if task_type not in valid_task_types:
146 raise SaltInvocationError("'task_type' must be one of the following: "
147 "{0}".format(', '.join(valid_task_types)))
148
149 if task_type == "import":
150 valid_import_from_formats = ['ami', 'ari', 'aki', 'vhd', 'vmdk',
151 'raw', 'qcow2', 'vdi', 'iso']
152 missing_params = import_required_params - set(input_params.keys())
153 if missing_params:
154 raise SaltInvocationError(
155 "Missing the following task parameters for the 'import' task: "
156 "{0}".format(', '.join(missing_params)))
157
158 import_from = input_params['import_from']
159 import_from_format = input_params['import_from_format']
160 image_properties = input_params['image_properties']
161 if not import_from.startswith(('http://', 'https://')):
162 raise SaltInvocationError("Only non-local sources of image data "
163 "are supported.")
164 if import_from_format not in valid_import_from_formats:
165 raise SaltInvocationError(
166 "'import_from_format' needs to be one of the following: "
167 "{0}".format(', '.join(valid_import_from_formats)))
168 _validate_image_params(**image_properties)
169
170
171def task_create(task_type, profile=None, input_params=None):
172 """
173 Create a Glance V2 task of a given type
174
175 :param task_type: Task type
176 :param profile: Authentication profile
177 :param input_params: Dictionary with input parameters for a task
178 :return: Dictionary with created task's parameters
179 """
180 g_client = _auth(profile, api_version=2)
181 log.debug(
182 'Task type: {}\nInput params: {}'.format(task_type, input_params)
183 )
184 task = g_client.tasks.create(type=task_type, input=input_params)
185 log.debug("Created task: {}".format(dict(task)))
186 created_task = task_show(task.id, profile=profile)
187 return created_task
188
189
190def task_show(task_id, profile=None):
191 """
192 Show a Glance V2 task
193
194 :param task_id: ID of a task to show
195 :param profile: Authentication profile
196 :return: Dictionary with created task's parameters
197 """
198 g_client = _auth(profile)
199 ret = {}
200 try:
201 task = g_client.tasks.get(task_id)
202 except exc.HTTPNotFound:
203 return {
204 'result': False,
205 'comment': 'No task with ID {0}'.format(task_id)
206 }
207 pformat = pprint.PrettyPrinter(indent=4).pformat
208 log.debug('Properties of task {0}:\n{1}'.format(
209 task_id, pformat(task)))
210
211 schema = image_schema(schema_type='task', profile=profile)
212 if len(schema.keys()) == 1:
213 schema = schema['task']
214 for key in schema.keys():
215 if key in task:
216 ret[key] = task[key]
217 return ret
218
219
220def task_list(profile=None):
221 """
222 List Glance V2 tasks
223
224 :param profile: Authentication profile
225 :return: Dictionary with existing tasks
226 """
227 g_client = _auth(profile)
228 ret = {}
229 tasks = g_client.tasks.list()
230 schema = image_schema(schema_type='task', profile=profile)
231 if len(schema.keys()) == 1:
232 schema = schema['task']
233 for task in tasks:
234 task_dict = {}
235 for key in schema.keys():
236 if key in task:
237 task_dict[key] = task[key]
238 ret[task['id']] = task_dict
239 return ret
240
241
242def get_image_owner_id(name, profile=None):
243 """
244 Mine function to get image owner
245
246 :param name: Name of the image
247 :param profile: Authentication profile
248 :return: Image owner ID or [] if image is not found
249 """
250 g_client = _auth(profile)
251 image_id = None
252 for image in g_client.images.list():
253 if image.name == name:
254 image_id = image.id
255 continue
256 if not image_id:
257 return []
258 try:
259 image = g_client.images.get(image_id)
260 except exc.HTTPNotFound:
261 return []
262 return image['owner']
263
264
265def image_schema(schema_type='image', profile=None):
266 '''
267 Returns names and descriptions of the schema "image"'s
268 properties for this profile's instance of glance
269
270 CLI Example:
271
272 .. code-block:: bash
273
274 salt '*' glance.image_schema
275 '''
276 return schema_get(schema_type, profile)
277
278
279def schema_get(name, profile=None):
280 '''
281 Known valid names of schemas are:
282 - image
283 - images
284 - member
285 - members
286
287 CLI Example:
288
289 .. code-block:: bash
290
291 salt '*' glance.schema_get name=f16-jeos
292 '''
293 g_client = _auth(profile)
294 pformat = pprint.PrettyPrinter(indent=4).pformat
295 schema_props = {}
296 for prop in g_client.schemas.get(name).properties:
297 schema_props[prop.name] = prop.description
298 log.debug('Properties of schema {0}:\n{1}'.format(
299 name, pformat(schema_props)))
300 return {name: schema_props}