blob: e8687d990b57135f032be62643512b72391df6a3 [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)
101 endpoint_type = connection_args.get('connection_endpoint_type', 'internal')
102 g_endpoint = __salt__['keystoneng.endpoint_get']('glance', profile=profile, interface=endpoint_type)
103 glance_client = client.Client(api_version, session=kstone.session, endpoint=g_endpoint.get('url'))
104 return glance_client
Elena Ezhova1c2ebae2017-07-03 18:29:05 +0400105
106
107def _validate_image_params(visibility=None, container_format='bare',
108 disk_format='raw', tags=None, **kwargs):
109 # valid options for "visibility":
110 v_list = ['public', 'private', 'shared', 'community']
111 # valid options for "container_format":
112 cf_list = ['ami', 'ari', 'aki', 'bare', 'ovf']
113 # valid options for "disk_format":
114 df_list = ['ami', 'ari', 'aki', 'vhd', 'vmdk',
115 'raw', 'qcow2', 'vdi', 'iso']
116
117 if visibility is not None:
118 if visibility not in v_list:
119 raise SaltInvocationError('"visibility" needs to be one ' +
120 'of the following: {0}'.format(
121 ', '.join(v_list)))
122 if container_format not in cf_list:
123 raise SaltInvocationError('"container_format" needs to be ' +
124 'one of the following: {0}'.format(
125 ', '.join(cf_list)))
126 if disk_format not in df_list:
127 raise SaltInvocationError('"disk_format" needs to be one ' +
128 'of the following: {0}'.format(
129 ', '.join(df_list)))
130 if tags:
131 if not isinstance(tags, list):
132 raise SaltInvocationError('Incorrect input type for the {0} '
133 'parameter: expected: {1}, '
134 'got {2}'.format("tags", list,
135 type(tags)))
136
137
138def _validate_task_params(task_type, input_params):
139 # Only import tasks are currently supported
140 # TODO(eezhova): Add support for "export" and "clone" task types
141 valid_task_types = ["import", ]
142
143 import_required_params = {"import_from", "import_from_format",
144 "image_properties"}
145
146 if task_type not in valid_task_types:
147 raise SaltInvocationError("'task_type' must be one of the following: "
148 "{0}".format(', '.join(valid_task_types)))
149
150 if task_type == "import":
151 valid_import_from_formats = ['ami', 'ari', 'aki', 'vhd', 'vmdk',
152 'raw', 'qcow2', 'vdi', 'iso']
153 missing_params = import_required_params - set(input_params.keys())
154 if missing_params:
155 raise SaltInvocationError(
156 "Missing the following task parameters for the 'import' task: "
157 "{0}".format(', '.join(missing_params)))
158
159 import_from = input_params['import_from']
160 import_from_format = input_params['import_from_format']
161 image_properties = input_params['image_properties']
162 if not import_from.startswith(('http://', 'https://')):
163 raise SaltInvocationError("Only non-local sources of image data "
164 "are supported.")
165 if import_from_format not in valid_import_from_formats:
166 raise SaltInvocationError(
167 "'import_from_format' needs to be one of the following: "
168 "{0}".format(', '.join(valid_import_from_formats)))
169 _validate_image_params(**image_properties)
170
171
172def task_create(task_type, profile=None, input_params=None):
173 """
174 Create a Glance V2 task of a given type
175
176 :param task_type: Task type
177 :param profile: Authentication profile
178 :param input_params: Dictionary with input parameters for a task
179 :return: Dictionary with created task's parameters
180 """
181 g_client = _auth(profile, api_version=2)
182 log.debug(
183 'Task type: {}\nInput params: {}'.format(task_type, input_params)
184 )
185 task = g_client.tasks.create(type=task_type, input=input_params)
186 log.debug("Created task: {}".format(dict(task)))
187 created_task = task_show(task.id, profile=profile)
188 return created_task
189
190
191def task_show(task_id, profile=None):
192 """
193 Show a Glance V2 task
194
195 :param task_id: ID of a task to show
196 :param profile: Authentication profile
197 :return: Dictionary with created task's parameters
198 """
199 g_client = _auth(profile)
200 ret = {}
201 try:
202 task = g_client.tasks.get(task_id)
203 except exc.HTTPNotFound:
204 return {
205 'result': False,
206 'comment': 'No task with ID {0}'.format(task_id)
207 }
208 pformat = pprint.PrettyPrinter(indent=4).pformat
209 log.debug('Properties of task {0}:\n{1}'.format(
210 task_id, pformat(task)))
211
212 schema = image_schema(schema_type='task', profile=profile)
213 if len(schema.keys()) == 1:
214 schema = schema['task']
215 for key in schema.keys():
216 if key in task:
217 ret[key] = task[key]
218 return ret
219
220
221def task_list(profile=None):
222 """
223 List Glance V2 tasks
224
225 :param profile: Authentication profile
226 :return: Dictionary with existing tasks
227 """
228 g_client = _auth(profile)
229 ret = {}
230 tasks = g_client.tasks.list()
231 schema = image_schema(schema_type='task', profile=profile)
232 if len(schema.keys()) == 1:
233 schema = schema['task']
234 for task in tasks:
235 task_dict = {}
236 for key in schema.keys():
237 if key in task:
238 task_dict[key] = task[key]
239 ret[task['id']] = task_dict
240 return ret
241
242
243def get_image_owner_id(name, profile=None):
244 """
245 Mine function to get image owner
246
247 :param name: Name of the image
248 :param profile: Authentication profile
249 :return: Image owner ID or [] if image is not found
250 """
251 g_client = _auth(profile)
252 image_id = None
253 for image in g_client.images.list():
254 if image.name == name:
255 image_id = image.id
256 continue
257 if not image_id:
258 return []
259 try:
260 image = g_client.images.get(image_id)
261 except exc.HTTPNotFound:
262 return []
263 return image['owner']
264
265
266def image_schema(schema_type='image', profile=None):
267 '''
268 Returns names and descriptions of the schema "image"'s
269 properties for this profile's instance of glance
270
271 CLI Example:
272
273 .. code-block:: bash
274
275 salt '*' glance.image_schema
276 '''
277 return schema_get(schema_type, profile)
278
279
280def schema_get(name, profile=None):
281 '''
282 Known valid names of schemas are:
283 - image
284 - images
285 - member
286 - members
287
288 CLI Example:
289
290 .. code-block:: bash
291
292 salt '*' glance.schema_get name=f16-jeos
293 '''
294 g_client = _auth(profile)
295 pformat = pprint.PrettyPrinter(indent=4).pformat
296 schema_props = {}
297 for prop in g_client.schemas.get(name).properties:
298 schema_props[prop.name] = prop.description
299 log.debug('Properties of schema {0}:\n{1}'.format(
300 name, pformat(schema_props)))
301 return {name: schema_props}