1# Copyright 2013 OpenStack Foundation
2# Copyright (C) 2013 Yahoo! Inc.
3# All Rights Reserved.
4#
5#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6#    not use this file except in compliance with the License. You may obtain
7#    a copy of the License at
8#
9#         http://www.apache.org/licenses/LICENSE-2.0
10#
11#    Unless required by applicable law or agreed to in writing, software
12#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14#    License for the specific language governing permissions and limitations
15#    under the License.
16import argparse
17from copy import deepcopy
18import io
19import json
20import os
21from unittest import mock
22
23import sys
24import tempfile
25import testtools
26
27from glanceclient.common import utils
28from glanceclient import exc
29from glanceclient import shell
30
31# NOTE(geguileo): This is very nasty, but I can't find a better way to set
32# command line arguments in glanceclient.v2.shell.do_image_create that are
33# set by decorator utils.schema_args while preserving the spirits of the test
34
35# Backup original decorator
36original_schema_args = utils.schema_args
37
38
39# Set our own decorator that calls the original but with simulated schema
40def schema_args(schema_getter, omit=None):
41    global original_schema_args
42    # We only add the 2 arguments that are required by image-create
43    my_schema_getter = lambda: {
44        'properties': {
45            'container_format': {
46                'enum': [None, 'ami', 'ari', 'aki', 'bare', 'ovf', 'ova',
47                         'docker'],
48                'type': 'string',
49                'description': 'Format of the container'},
50            'disk_format': {
51                'enum': [None, 'ami', 'ari', 'aki', 'vhd', 'vhdx', 'vmdk',
52                         'raw', 'qcow2', 'vdi', 'iso', 'ploop'],
53                'type': 'string',
54                'description': 'Format of the disk'},
55            'location': {'type': 'string'},
56            'locations': {'type': 'string'},
57            'copy_from': {'type': 'string'}}}
58    return original_schema_args(my_schema_getter, omit)
59
60
61utils.schema_args = schema_args
62
63from glanceclient.v2 import shell as test_shell  # noqa
64
65# Return original decorator.
66utils.schema_args = original_schema_args
67
68
69class ShellV2Test(testtools.TestCase):
70    def setUp(self):
71        super(ShellV2Test, self).setUp()
72        self._mock_utils()
73        self.gc = self._mock_glance_client()
74        self.shell = shell.OpenStackImagesShell()
75        os.environ = {
76            'OS_USERNAME': 'username',
77            'OS_PASSWORD': 'password',
78            'OS_TENANT_ID': 'tenant_id',
79            'OS_TOKEN_ID': 'test',
80            'OS_AUTH_URL': 'http://127.0.0.1:5000/v2.0/',
81            'OS_AUTH_TOKEN': 'pass',
82            'OS_IMAGE_API_VERSION': '1',
83            'OS_REGION_NAME': 'test',
84            'OS_IMAGE_URL': 'http://is.invalid'}
85        self.shell = shell.OpenStackImagesShell()
86        self.patched = mock.patch('glanceclient.common.utils.get_data_file',
87                                  autospec=True, return_value=None)
88        self.mock_get_data_file = self.patched.start()
89
90    def tearDown(self):
91        super(ShellV2Test, self).tearDown()
92        self.patched.stop()
93
94    def _make_args(self, args):
95        # NOTE(venkatesh): this conversion from a dict to an object
96        # is required because the test_shell.do_xxx(gc, args) methods
97        # expects the args to be attributes of an object. If passed as
98        # dict directly, it throws an AttributeError.
99        class Args(object):
100            def __init__(self, entries):
101                self.store = None
102                self.__dict__.update(entries)
103
104        return Args(args)
105
106    def _mock_glance_client(self):
107        my_mocked_gc = mock.Mock()
108        my_mocked_gc.schemas.return_value = 'test'
109        my_mocked_gc.get.return_value = {}
110        return my_mocked_gc
111
112    def _mock_utils(self):
113        utils.print_list = mock.Mock()
114        utils.print_dict = mock.Mock()
115        utils.save_image = mock.Mock()
116        utils.print_dict_list = mock.Mock()
117
118    def assert_exits_with_msg(self, func, func_args, err_msg=None):
119        with mock.patch.object(utils, 'exit') as mocked_utils_exit:
120            mocked_utils_exit.return_value = '%s' % err_msg
121
122            func(self.gc, func_args)
123            if err_msg:
124                mocked_utils_exit.assert_called_once_with(err_msg)
125            else:
126                mocked_utils_exit.assert_called_once_with()
127
128    def _run_command(self, cmd):
129        self.shell.main(cmd.split())
130
131    stores_info_response = {
132        "stores": [
133            {
134                "default": "true",
135                "id": "ceph1",
136                "description": "RBD backend for glance."
137            },
138            {
139                "id": "file2",
140                "description": "Filesystem backend for glance."
141            },
142            {
143                "id": "file1",
144                "description": "Filesystem backend for gkance."
145            },
146            {
147                "id": "ceph2",
148                "description": "RBD backend for glance."
149            }
150        ]
151    }
152
153    def test_do_stores_info(self):
154        args = []
155        with mock.patch.object(self.gc.images,
156                               'get_stores_info') as mocked_list:
157            mocked_list.return_value = self.stores_info_response
158
159            test_shell.do_stores_info(self.gc, args)
160
161            mocked_list.assert_called_once_with()
162            utils.print_dict.assert_called_once_with(self.stores_info_response)
163
164    @mock.patch('glanceclient.common.utils.exit')
165    @mock.patch('sys.stdin', autospec=True)
166    def test_neg_stores_info(
167            self, mock_stdin, mock_utils_exit):
168        expected_msg = ('Multi Backend support is not enabled')
169        args = []
170        mock_utils_exit.side_effect = self._mock_utils_exit
171        with mock.patch.object(self.gc.images,
172                               'get_stores_info') as mocked_info:
173            mocked_info.side_effect = exc.HTTPNotFound
174            try:
175                test_shell.do_stores_info(self.gc, args)
176                self.fail("utils.exit should have been called")
177            except SystemExit:
178                pass
179        mock_utils_exit.assert_called_once_with(expected_msg)
180
181    @mock.patch('sys.stderr')
182    def test_image_create_missing_disk_format(self, __):
183        e = self.assertRaises(exc.CommandError, self._run_command,
184                              '--os-image-api-version 2 image-create ' +
185                              '--file fake_src --container-format bare')
186        self.assertEqual('error: Must provide --disk-format when using '
187                         '--file.', e.message)
188
189    @mock.patch('sys.stderr')
190    def test_image_create_missing_container_format(self, __):
191        e = self.assertRaises(exc.CommandError, self._run_command,
192                              '--os-image-api-version 2 image-create ' +
193                              '--file fake_src --disk-format qcow2')
194        self.assertEqual('error: Must provide --container-format when '
195                         'using --file.', e.message)
196
197    @mock.patch('sys.stderr')
198    def test_image_create_missing_container_format_stdin_data(self, __):
199        # Fake that get_data_file method returns data
200        self.mock_get_data_file.return_value = io.StringIO()
201        e = self.assertRaises(exc.CommandError, self._run_command,
202                              '--os-image-api-version 2 image-create'
203                              ' --disk-format qcow2')
204        self.assertEqual('error: Must provide --container-format when '
205                         'using stdin.', e.message)
206
207    @mock.patch('sys.stderr')
208    def test_image_create_missing_disk_format_stdin_data(self, __):
209        # Fake that get_data_file method returns data
210        self.mock_get_data_file.return_value = io.StringIO()
211        e = self.assertRaises(exc.CommandError, self._run_command,
212                              '--os-image-api-version 2 image-create'
213                              ' --container-format bare')
214        self.assertEqual('error: Must provide --disk-format when using stdin.',
215                         e.message)
216
217    @mock.patch('sys.stderr')
218    def test_create_via_import_glance_direct_missing_disk_format(self, __):
219        e = self.assertRaises(exc.CommandError, self._run_command,
220                              '--os-image-api-version 2 '
221                              'image-create-via-import '
222                              '--file fake_src --container-format bare')
223        self.assertEqual('error: Must provide --disk-format when using '
224                         '--file.', e.message)
225
226    @mock.patch('sys.stderr')
227    def test_create_via_import_glance_direct_missing_container_format(
228            self, __):
229        e = self.assertRaises(exc.CommandError, self._run_command,
230                              '--os-image-api-version 2 '
231                              'image-create-via-import '
232                              '--file fake_src --disk-format qcow2')
233        self.assertEqual('error: Must provide --container-format when '
234                         'using --file.', e.message)
235
236    @mock.patch('sys.stderr')
237    def test_create_via_import_web_download_missing_disk_format(self, __):
238        e = self.assertRaises(exc.CommandError, self._run_command,
239                              '--os-image-api-version 2 '
240                              'image-create-via-import ' +
241                              '--import-method web-download ' +
242                              '--uri fake_uri --container-format bare')
243        self.assertEqual('error: Must provide --disk-format when using '
244                         '--uri.', e.message)
245
246    @mock.patch('sys.stderr')
247    def test_create_via_import_web_download_missing_container_format(
248            self, __):
249        e = self.assertRaises(exc.CommandError, self._run_command,
250                              '--os-image-api-version 2 '
251                              'image-create-via-import '
252                              '--import-method web-download '
253                              '--uri fake_uri --disk-format qcow2')
254        self.assertEqual('error: Must provide --container-format when '
255                         'using --uri.', e.message)
256
257    def test_do_image_list(self):
258        input = {
259            'limit': None,
260            'page_size': 18,
261            'visibility': True,
262            'member_status': 'Fake',
263            'owner': 'test',
264            'checksum': 'fake_checksum',
265            'tag': 'fake tag',
266            'properties': [],
267            'sort_key': ['name', 'id'],
268            'sort_dir': ['desc', 'asc'],
269            'sort': None,
270            'verbose': False,
271            'include_stores': False,
272            'os_hash_value': None,
273            'os_hidden': False
274        }
275        args = self._make_args(input)
276        with mock.patch.object(self.gc.images, 'list') as mocked_list:
277            mocked_list.return_value = {}
278
279            test_shell.do_image_list(self.gc, args)
280
281            exp_img_filters = {
282                'owner': 'test',
283                'member_status': 'Fake',
284                'visibility': True,
285                'checksum': 'fake_checksum',
286                'tag': 'fake tag',
287                'os_hidden': False
288            }
289            mocked_list.assert_called_once_with(page_size=18,
290                                                sort_key=['name', 'id'],
291                                                sort_dir=['desc', 'asc'],
292                                                filters=exp_img_filters)
293            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
294
295    def test_do_image_list_verbose(self):
296        input = {
297            'limit': None,
298            'page_size': 18,
299            'visibility': True,
300            'member_status': 'Fake',
301            'owner': 'test',
302            'checksum': 'fake_checksum',
303            'tag': 'fake tag',
304            'properties': [],
305            'sort_key': ['name', 'id'],
306            'sort_dir': ['desc', 'asc'],
307            'sort': None,
308            'verbose': True,
309            'include_stores': False,
310            'os_hash_value': None,
311            'os_hidden': False
312        }
313        args = self._make_args(input)
314        with mock.patch.object(self.gc.images, 'list') as mocked_list:
315            mocked_list.return_value = {}
316
317            test_shell.do_image_list(self.gc, args)
318            utils.print_list.assert_called_once_with(
319                {}, ['ID', 'Name', 'Disk_format', 'Container_format',
320                     'Size', 'Status', 'Owner'])
321
322    def test_do_image_list_with_include_stores_true(self):
323        input = {
324            'limit': None,
325            'page_size': 18,
326            'visibility': True,
327            'member_status': 'Fake',
328            'owner': 'test',
329            'checksum': 'fake_checksum',
330            'tag': 'fake tag',
331            'properties': [],
332            'sort_key': ['name', 'id'],
333            'sort_dir': ['desc', 'asc'],
334            'sort': None,
335            'verbose': False,
336            'include_stores': True,
337            'os_hash_value': None,
338            'os_hidden': False
339        }
340        args = self._make_args(input)
341        with mock.patch.object(self.gc.images, 'list') as mocked_list:
342            mocked_list.return_value = {}
343
344            test_shell.do_image_list(self.gc, args)
345            utils.print_list.assert_called_once_with(
346                {}, ['ID', 'Name', 'Stores'])
347
348    def test_do_image_list_verbose_with_include_stores_true(self):
349        input = {
350            'limit': None,
351            'page_size': 18,
352            'visibility': True,
353            'member_status': 'Fake',
354            'owner': 'test',
355            'checksum': 'fake_checksum',
356            'tag': 'fake tag',
357            'properties': [],
358            'sort_key': ['name', 'id'],
359            'sort_dir': ['desc', 'asc'],
360            'sort': None,
361            'verbose': True,
362            'include_stores': True,
363            'os_hash_value': None,
364            'os_hidden': False
365        }
366        args = self._make_args(input)
367        with mock.patch.object(self.gc.images, 'list') as mocked_list:
368            mocked_list.return_value = {}
369
370            test_shell.do_image_list(self.gc, args)
371            utils.print_list.assert_called_once_with(
372                {}, ['ID', 'Name', 'Disk_format', 'Container_format',
373                     'Size', 'Status', 'Owner', 'Stores'])
374
375    def test_do_image_list_with_hidden_true(self):
376        input = {
377            'limit': None,
378            'page_size': 18,
379            'visibility': True,
380            'member_status': 'Fake',
381            'owner': 'test',
382            'checksum': 'fake_checksum',
383            'tag': 'fake tag',
384            'properties': [],
385            'sort_key': ['name', 'id'],
386            'sort_dir': ['desc', 'asc'],
387            'sort': None,
388            'verbose': False,
389            'include_stores': False,
390            'os_hash_value': None,
391            'os_hidden': True
392        }
393        args = self._make_args(input)
394        with mock.patch.object(self.gc.images, 'list') as mocked_list:
395            mocked_list.return_value = {}
396
397            test_shell.do_image_list(self.gc, args)
398
399            exp_img_filters = {
400                'owner': 'test',
401                'member_status': 'Fake',
402                'visibility': True,
403                'checksum': 'fake_checksum',
404                'tag': 'fake tag',
405                'os_hidden': True
406            }
407            mocked_list.assert_called_once_with(page_size=18,
408                                                sort_key=['name', 'id'],
409                                                sort_dir=['desc', 'asc'],
410                                                filters=exp_img_filters)
411            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
412
413    def test_do_image_list_with_single_sort_key(self):
414        input = {
415            'limit': None,
416            'page_size': 18,
417            'visibility': True,
418            'member_status': 'Fake',
419            'owner': 'test',
420            'checksum': 'fake_checksum',
421            'tag': 'fake tag',
422            'properties': [],
423            'sort_key': ['name'],
424            'sort_dir': ['desc'],
425            'sort': None,
426            'verbose': False,
427            'include_stores': False,
428            'os_hash_value': None,
429            'os_hidden': False
430        }
431        args = self._make_args(input)
432        with mock.patch.object(self.gc.images, 'list') as mocked_list:
433            mocked_list.return_value = {}
434
435            test_shell.do_image_list(self.gc, args)
436
437            exp_img_filters = {
438                'owner': 'test',
439                'member_status': 'Fake',
440                'visibility': True,
441                'checksum': 'fake_checksum',
442                'tag': 'fake tag',
443                'os_hidden': False
444            }
445            mocked_list.assert_called_once_with(page_size=18,
446                                                sort_key=['name'],
447                                                sort_dir=['desc'],
448                                                filters=exp_img_filters)
449            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
450
451    def test_do_image_list_new_sorting_syntax(self):
452        input = {
453            'limit': None,
454            'page_size': 18,
455            'visibility': True,
456            'member_status': 'Fake',
457            'owner': 'test',
458            'checksum': 'fake_checksum',
459            'tag': 'fake tag',
460            'properties': [],
461            'sort': 'name:desc,size:asc',
462            'sort_key': [],
463            'sort_dir': [],
464            'verbose': False,
465            'include_stores': False,
466            'os_hash_value': None,
467            'os_hidden': False
468        }
469        args = self._make_args(input)
470        with mock.patch.object(self.gc.images, 'list') as mocked_list:
471            mocked_list.return_value = {}
472
473            test_shell.do_image_list(self.gc, args)
474
475            exp_img_filters = {
476                'owner': 'test',
477                'member_status': 'Fake',
478                'visibility': True,
479                'checksum': 'fake_checksum',
480                'tag': 'fake tag',
481                'os_hidden': False
482            }
483            mocked_list.assert_called_once_with(
484                page_size=18,
485                sort='name:desc,size:asc',
486                filters=exp_img_filters)
487            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
488
489    def test_do_image_list_with_property_filter(self):
490        input = {
491            'limit': None,
492            'page_size': 1,
493            'visibility': True,
494            'member_status': 'Fake',
495            'owner': 'test',
496            'checksum': 'fake_checksum',
497            'tag': 'fake tag',
498            'properties': ['os_distro=NixOS', 'architecture=x86_64'],
499            'sort_key': ['name'],
500            'sort_dir': ['desc'],
501            'sort': None,
502            'verbose': False,
503            'include_stores': False,
504            'os_hash_value': None,
505            'os_hidden': False
506        }
507        args = self._make_args(input)
508        with mock.patch.object(self.gc.images, 'list') as mocked_list:
509            mocked_list.return_value = {}
510
511            test_shell.do_image_list(self.gc, args)
512
513            exp_img_filters = {
514                'owner': 'test',
515                'member_status': 'Fake',
516                'visibility': True,
517                'checksum': 'fake_checksum',
518                'tag': 'fake tag',
519                'os_distro': 'NixOS',
520                'architecture': 'x86_64',
521                'os_hidden': False
522            }
523
524            mocked_list.assert_called_once_with(page_size=1,
525                                                sort_key=['name'],
526                                                sort_dir=['desc'],
527                                                filters=exp_img_filters)
528            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
529
530    def test_do_image_show_human_readable(self):
531        args = self._make_args({'id': 'pass', 'page_size': 18,
532                                'human_readable': True,
533                                'max_column_width': 120})
534        with mock.patch.object(self.gc.images, 'get') as mocked_list:
535            ignore_fields = ['self', 'access', 'file', 'schema']
536            expect_image = dict([(field, field) for field in ignore_fields])
537            expect_image['id'] = 'pass'
538            expect_image['size'] = 1024
539            mocked_list.return_value = expect_image
540
541            test_shell.do_image_show(self.gc, args)
542
543            mocked_list.assert_called_once_with('pass')
544            utils.print_dict.assert_called_once_with({'id': 'pass',
545                                                      'size': '1kB'},
546                                                     max_column_width=120)
547
548    def test_do_image_show(self):
549        args = self._make_args({'id': 'pass', 'page_size': 18,
550                                'human_readable': False,
551                                'max_column_width': 120})
552        with mock.patch.object(self.gc.images, 'get') as mocked_list:
553            ignore_fields = ['self', 'access', 'file', 'schema']
554            expect_image = dict([(field, field) for field in ignore_fields])
555            expect_image['id'] = 'pass'
556            expect_image['size'] = 1024
557            mocked_list.return_value = expect_image
558
559            test_shell.do_image_show(self.gc, args)
560
561            mocked_list.assert_called_once_with('pass')
562            utils.print_dict.assert_called_once_with({'id': 'pass',
563                                                      'size': 1024},
564                                                     max_column_width=120)
565
566    def _test_do_image_tasks(self, verbose=False, supported=True):
567        args = self._make_args({'id': 'pass', 'verbose': verbose})
568        expected_columns = ["Message", "Status", "Updated at"]
569        expected_output = {
570            "tasks": [
571                {
572                    "image_id": "pass",
573                    "id": "task_1",
574                    "user_id": "user_1",
575                    "request_id": "request_id_1",
576                    "message": "fake_message",
577                    "status": "status",
578                }
579            ]
580        }
581
582        if verbose:
583            columns_to_prepend = ['Image Id', 'Task Id']
584            columns_to_extend = ['User Id', 'Request Id',
585                                 'Result', 'Owner', 'Input', 'Expires at']
586            expected_columns = (columns_to_prepend + expected_columns +
587                                columns_to_extend)
588            expected_output["tasks"][0]["Result"] = "Fake Result"
589            expected_output["tasks"][0]["Owner"] = "Fake Owner"
590            expected_output["tasks"][0]["Input"] = "Fake Input"
591            expected_output["tasks"][0]["Expires at"] = "Fake Expiry"
592
593        with mock.patch.object(self.gc.images,
594                               'get_associated_image_tasks') as mocked_tasks:
595            if supported:
596                mocked_tasks.return_value = expected_output
597            else:
598                mocked_tasks.side_effect = exc.HTTPNotImplemented
599            test_shell.do_image_tasks(self.gc, args)
600            mocked_tasks.assert_called_once_with('pass')
601            if supported:
602                utils.print_dict_list.assert_called_once_with(
603                    expected_output['tasks'], expected_columns)
604
605    def test_do_image_tasks_without_verbose(self):
606        self._test_do_image_tasks()
607
608    def test_do_image_tasks_with_verbose(self):
609        self._test_do_image_tasks(verbose=True)
610
611    def test_do_image_tasks_unsupported(self):
612        with mock.patch('glanceclient.common.utils.exit') as mock_exit:
613            self._test_do_image_tasks(supported=False)
614            mock_exit.assert_called_once_with(
615                'Server does not support image tasks API (v2.12)')
616
617    @mock.patch('sys.stdin', autospec=True)
618    def test_do_image_create_no_user_props(self, mock_stdin):
619        args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
620                                'container_format': 'bare',
621                                'file': None})
622        with mock.patch.object(self.gc.images, 'create') as mocked_create:
623            ignore_fields = ['self', 'access', 'file', 'schema']
624            expect_image = dict([(field, field) for field in ignore_fields])
625            expect_image['id'] = 'pass'
626            expect_image['name'] = 'IMG-01'
627            expect_image['disk_format'] = 'vhd'
628            expect_image['container_format'] = 'bare'
629            mocked_create.return_value = expect_image
630
631            # Ensure that the test stdin is not considered
632            # to be supplying image data
633            mock_stdin.isatty = lambda: True
634            test_shell.do_image_create(self.gc, args)
635
636            mocked_create.assert_called_once_with(name='IMG-01',
637                                                  disk_format='vhd',
638                                                  container_format='bare')
639            utils.print_dict.assert_called_once_with({
640                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
641                'container_format': 'bare'})
642
643    @mock.patch('sys.stdin', autospec=True)
644    def test_do_image_create_for_none_multi_hash(self, mock_stdin):
645        args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
646                                'container_format': 'bare',
647                                'file': None})
648        with mock.patch.object(self.gc.images, 'create') as mocked_create:
649            ignore_fields = ['self', 'access', 'file', 'schema']
650            expect_image = dict([(field, field) for field in ignore_fields])
651            expect_image['id'] = 'pass'
652            expect_image['name'] = 'IMG-01'
653            expect_image['disk_format'] = 'vhd'
654            expect_image['container_format'] = 'bare'
655            expect_image['os_hash_algo'] = None
656            expect_image['os_hash_value'] = None
657            mocked_create.return_value = expect_image
658
659            # Ensure that the test stdin is not considered
660            # to be supplying image data
661            mock_stdin.isatty = lambda: True
662            test_shell.do_image_create(self.gc, args)
663
664            mocked_create.assert_called_once_with(name='IMG-01',
665                                                  disk_format='vhd',
666                                                  container_format='bare')
667            utils.print_dict.assert_called_once_with({
668                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
669                'container_format': 'bare', 'os_hash_algo': None,
670                'os_hash_value': None})
671
672    def test_do_image_create_with_multihash(self):
673        self.mock_get_data_file.return_value = io.StringIO()
674        try:
675            with open(tempfile.mktemp(), 'w+') as f:
676                f.write('Some data here')
677                f.flush()
678                f.seek(0)
679                file_name = f.name
680            temp_args = {'name': 'IMG-01',
681                         'disk_format': 'vhd',
682                         'container_format': 'bare',
683                         'file': file_name,
684                         'progress': False}
685            args = self._make_args(temp_args)
686            with mock.patch.object(self.gc.images, 'create') as mocked_create:
687                with mock.patch.object(self.gc.images, 'get') as mocked_get:
688
689                    ignore_fields = ['self', 'access', 'schema']
690                    expect_image = dict([(field, field) for field in
691                                         ignore_fields])
692                    expect_image['id'] = 'pass'
693                    expect_image['name'] = 'IMG-01'
694                    expect_image['disk_format'] = 'vhd'
695                    expect_image['container_format'] = 'bare'
696                    expect_image['checksum'] = 'fake-checksum'
697                    expect_image['os_hash_algo'] = 'fake-hash_algo'
698                    expect_image['os_hash_value'] = 'fake-hash_value'
699                    mocked_create.return_value = expect_image
700                    mocked_get.return_value = expect_image
701
702                    test_shell.do_image_create(self.gc, args)
703
704                    temp_args.pop('file', None)
705                    mocked_create.assert_called_once_with(**temp_args)
706                    mocked_get.assert_called_once_with('pass')
707                    utils.print_dict.assert_called_once_with({
708                        'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
709                        'container_format': 'bare',
710                        'checksum': 'fake-checksum',
711                        'os_hash_algo': 'fake-hash_algo',
712                        'os_hash_value': 'fake-hash_value'})
713        finally:
714            try:
715                os.remove(f.name)
716            except Exception:
717                pass
718
719    @mock.patch('sys.stdin', autospec=True)
720    def test_do_image_create_hidden_image(self, mock_stdin):
721        args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
722                                'container_format': 'bare',
723                                'file': None,
724                                'os_hidden': True})
725        with mock.patch.object(self.gc.images, 'create') as mocked_create:
726            ignore_fields = ['self', 'access', 'file', 'schema']
727            expect_image = dict([(field, field) for field in ignore_fields])
728            expect_image['id'] = 'pass'
729            expect_image['name'] = 'IMG-01'
730            expect_image['disk_format'] = 'vhd'
731            expect_image['container_format'] = 'bare'
732            expect_image['os_hidden'] = True
733            mocked_create.return_value = expect_image
734
735            # Ensure that the test stdin is not considered
736            # to be supplying image data
737            mock_stdin.isatty = lambda: True
738            test_shell.do_image_create(self.gc, args)
739
740            mocked_create.assert_called_once_with(name='IMG-01',
741                                                  disk_format='vhd',
742                                                  container_format='bare',
743                                                  os_hidden=True)
744            utils.print_dict.assert_called_once_with({
745                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
746                'container_format': 'bare', 'os_hidden': True})
747
748    def test_do_image_create_with_file(self):
749        self.mock_get_data_file.return_value = io.StringIO()
750        try:
751            file_name = None
752            with open(tempfile.mktemp(), 'w+') as f:
753                f.write('Some data here')
754                f.flush()
755                f.seek(0)
756                file_name = f.name
757            temp_args = {'name': 'IMG-01',
758                         'disk_format': 'vhd',
759                         'container_format': 'bare',
760                         'file': file_name,
761                         'progress': False}
762            args = self._make_args(temp_args)
763            with mock.patch.object(self.gc.images, 'create') as mocked_create:
764                with mock.patch.object(self.gc.images, 'get') as mocked_get:
765
766                    ignore_fields = ['self', 'access', 'schema']
767                    expect_image = dict([(field, field) for field in
768                                         ignore_fields])
769                    expect_image['id'] = 'pass'
770                    expect_image['name'] = 'IMG-01'
771                    expect_image['disk_format'] = 'vhd'
772                    expect_image['container_format'] = 'bare'
773                    mocked_create.return_value = expect_image
774                    mocked_get.return_value = expect_image
775
776                    test_shell.do_image_create(self.gc, args)
777
778                    temp_args.pop('file', None)
779                    mocked_create.assert_called_once_with(**temp_args)
780                    mocked_get.assert_called_once_with('pass')
781                    utils.print_dict.assert_called_once_with({
782                        'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
783                        'container_format': 'bare'})
784        finally:
785            try:
786                os.remove(f.name)
787            except Exception:
788                pass
789
790    @mock.patch('sys.stdin', autospec=True)
791    def test_do_image_create_with_unicode(self, mock_stdin):
792        name = u'\u041f\u0420\u0418\u0412\u0415\u0422\u0418\u041a'
793
794        args = self._make_args({'name': name,
795                                'file': None})
796        with mock.patch.object(self.gc.images, 'create') as mocked_create:
797            ignore_fields = ['self', 'access', 'file', 'schema']
798            expect_image = dict((field, field) for field in ignore_fields)
799            expect_image['id'] = 'pass'
800            expect_image['name'] = name
801            mocked_create.return_value = expect_image
802
803            mock_stdin.isatty = lambda: True
804            test_shell.do_image_create(self.gc, args)
805
806            mocked_create.assert_called_once_with(name=name)
807            utils.print_dict.assert_called_once_with({
808                'id': 'pass', 'name': name})
809
810    @mock.patch('sys.stdin', autospec=True)
811    def test_do_image_create_with_user_props(self, mock_stdin):
812        args = self._make_args({'name': 'IMG-01',
813                                'property': ['myprop=myval'],
814                                'file': None,
815                                'container_format': 'bare',
816                                'disk_format': 'qcow2'})
817        with mock.patch.object(self.gc.images, 'create') as mocked_create:
818            ignore_fields = ['self', 'access', 'file', 'schema']
819            expect_image = dict([(field, field) for field in ignore_fields])
820            expect_image['id'] = 'pass'
821            expect_image['name'] = 'IMG-01'
822            expect_image['myprop'] = 'myval'
823            mocked_create.return_value = expect_image
824
825            # Ensure that the test stdin is not considered
826            # to be supplying image data
827            mock_stdin.isatty = lambda: True
828            test_shell.do_image_create(self.gc, args)
829
830            mocked_create.assert_called_once_with(name='IMG-01',
831                                                  myprop='myval',
832                                                  container_format='bare',
833                                                  disk_format='qcow2')
834            utils.print_dict.assert_called_once_with({
835                'id': 'pass', 'name': 'IMG-01', 'myprop': 'myval'})
836
837    @mock.patch('glanceclient.common.utils.exit')
838    @mock.patch('os.access')
839    @mock.patch('sys.stdin', autospec=True)
840    def test_neg_do_image_create_no_file_and_stdin_with_store(
841            self, mock_stdin, mock_access, mock_utils_exit):
842        expected_msg = ('--store option should only be provided with --file '
843                        'option or stdin.')
844        mock_utils_exit.side_effect = self._mock_utils_exit
845        mock_stdin.isatty = lambda: True
846        mock_access.return_value = False
847        args = self._make_args({'name': 'IMG-01',
848                                'property': ['myprop=myval'],
849                                'file': None,
850                                'store': 'file1',
851                                'container_format': 'bare',
852                                'disk_format': 'qcow2'})
853
854        try:
855            test_shell.do_image_create(self.gc, args)
856            self.fail("utils.exit should have been called")
857        except SystemExit:
858            pass
859
860        mock_utils_exit.assert_called_once_with(expected_msg)
861
862    @mock.patch('glanceclient.common.utils.exit')
863    def test_neg_do_image_create_invalid_store(
864            self, mock_utils_exit):
865        expected_msg = ("Store 'dummy' is not valid for this cloud. "
866                        "Valid values can be retrieved with stores-info "
867                        "command.")
868        mock_utils_exit.side_effect = self._mock_utils_exit
869        args = self._make_args({'name': 'IMG-01',
870                                'property': ['myprop=myval'],
871                                'file': "somefile.txt",
872                                'store': 'dummy',
873                                'container_format': 'bare',
874                                'disk_format': 'qcow2'})
875
876        with mock.patch.object(self.gc.images,
877                               'get_stores_info') as mock_stores_info:
878            mock_stores_info.return_value = self.stores_info_response
879            try:
880                test_shell.do_image_create(self.gc, args)
881                self.fail("utils.exit should have been called")
882            except SystemExit:
883                pass
884
885        mock_utils_exit.assert_called_once_with(expected_msg)
886
887    # NOTE(rosmaita): have to explicitly set to None the declared but unused
888    # arguments (the configparser does that for us normally)
889    base_args = {'name': 'Mortimer',
890                 'disk_format': 'raw',
891                 'container_format': 'bare',
892                 'progress': False,
893                 'file': None,
894                 'uri': None,
895                 'import_method': None}
896
897    import_info_response = {'import-methods': {
898        'type': 'array',
899        'description': 'Import methods available.',
900        'value': ['glance-direct', 'web-download', 'copy-image']}}
901
902    def _mock_utils_exit(self, msg):
903        sys.exit(msg)
904
905    @mock.patch('glanceclient.common.utils.exit')
906    @mock.patch('os.access')
907    @mock.patch('sys.stdin', autospec=True)
908    def test_neg_image_create_via_import_no_method_with_file_and_stdin(
909            self, mock_stdin, mock_access, mock_utils_exit):
910        expected_msg = ('You cannot use both --file and stdin with the '
911                        'glance-direct import method.')
912        my_args = self.base_args.copy()
913        my_args['file'] = 'some.file'
914        args = self._make_args(my_args)
915        mock_stdin.isatty = lambda: False
916        mock_access.return_value = True
917        mock_utils_exit.side_effect = self._mock_utils_exit
918        with mock.patch.object(self.gc.images,
919                               'get_import_info') as mocked_info:
920            mocked_info.return_value = self.import_info_response
921            try:
922                test_shell.do_image_create_via_import(self.gc, args)
923                self.fail("utils.exit should have been called")
924            except SystemExit:
925                pass
926        mock_utils_exit.assert_called_once_with(expected_msg)
927
928    @mock.patch('glanceclient.common.utils.exit')
929    def test_neg_image_create_via_import_copy_image(
930            self, mock_utils_exit):
931        expected_msg = ("Import method 'copy-image' cannot be used "
932                        "while creating the image.")
933        mock_utils_exit.side_effect = self._mock_utils_exit
934        my_args = self.base_args.copy()
935        my_args.update(
936            {'id': 'IMG-01', 'import_method': 'copy-image'})
937        args = self._make_args(my_args)
938
939        with mock.patch.object(self.gc.images,
940                               'get_import_info') as mocked_info:
941            mocked_info.return_value = self.import_info_response
942            try:
943                test_shell.do_image_create_via_import(self.gc, args)
944                self.fail("utils.exit should have been called")
945            except SystemExit:
946                pass
947        mock_utils_exit.assert_called_once_with(expected_msg)
948
949    @mock.patch('glanceclient.common.utils.exit')
950    def test_neg_image_create_via_import_stores_all_stores_specified(
951            self, mock_utils_exit):
952        expected_msg = ('Only one of --store, --stores and --all-stores can '
953                        'be provided')
954        mock_utils_exit.side_effect = self._mock_utils_exit
955        my_args = self.base_args.copy()
956        my_args.update(
957            {'id': 'IMG-01', 'import_method': 'glance-direct',
958             'stores': 'file1,file2', 'os_all_stores': True,
959             'file': 'some.mufile',
960             'disk_format': 'raw',
961             'container_format': 'bare',
962             })
963        args = self._make_args(my_args)
964
965        with mock.patch.object(self.gc.images,
966                               'get_import_info') as mocked_info:
967            mocked_info.return_value = self.import_info_response
968            try:
969                test_shell.do_image_create_via_import(self.gc, args)
970                self.fail("utils.exit should have been called")
971            except SystemExit:
972                pass
973        mock_utils_exit.assert_called_once_with(expected_msg)
974
975    @mock.patch('glanceclient.common.utils.exit')
976    @mock.patch('sys.stdin', autospec=True)
977    def test_neg_image_create_via_import_stores_without_file(
978            self, mock_stdin, mock_utils_exit):
979        expected_msg = ('--stores option should only be provided with --file '
980                        'option or stdin for the glance-direct import method.')
981        mock_utils_exit.side_effect = self._mock_utils_exit
982        mock_stdin.isatty = lambda: True
983        my_args = self.base_args.copy()
984        my_args.update(
985            {'id': 'IMG-01', 'import_method': 'glance-direct',
986             'stores': 'file1,file2',
987             'disk_format': 'raw',
988             'container_format': 'bare',
989             })
990        args = self._make_args(my_args)
991
992        with mock.patch.object(self.gc.images,
993                               'get_import_info') as mocked_info:
994            with mock.patch.object(self.gc.images,
995                                   'get_stores_info') as mocked_stores_info:
996                mocked_stores_info.return_value = self.stores_info_response
997                mocked_info.return_value = self.import_info_response
998                try:
999                    test_shell.do_image_create_via_import(self.gc, args)
1000                    self.fail("utils.exit should have been called")
1001                except SystemExit:
1002                    pass
1003        mock_utils_exit.assert_called_once_with(expected_msg)
1004
1005    @mock.patch('glanceclient.common.utils.exit')
1006    @mock.patch('sys.stdin', autospec=True)
1007    def test_neg_image_create_via_import_all_stores_without_file(
1008            self, mock_stdin, mock_utils_exit):
1009        expected_msg = ('--all-stores option should only be provided with '
1010                        '--file option or stdin for the glance-direct import '
1011                        'method.')
1012        mock_utils_exit.side_effect = self._mock_utils_exit
1013        mock_stdin.isatty = lambda: True
1014        my_args = self.base_args.copy()
1015        my_args.update(
1016            {'id': 'IMG-01', 'import_method': 'glance-direct',
1017             'os_all_stores': True,
1018             'disk_format': 'raw',
1019             'container_format': 'bare',
1020             })
1021        args = self._make_args(my_args)
1022
1023        with mock.patch.object(self.gc.images,
1024                               'get_import_info') as mocked_info:
1025            mocked_info.return_value = self.import_info_response
1026            try:
1027                test_shell.do_image_create_via_import(self.gc, args)
1028                self.fail("utils.exit should have been called")
1029            except SystemExit:
1030                pass
1031        mock_utils_exit.assert_called_once_with(expected_msg)
1032
1033    @mock.patch('glanceclient.common.utils.exit')
1034    @mock.patch('os.access')
1035    @mock.patch('sys.stdin', autospec=True)
1036    def test_neg_image_create_via_import_no_file_and_stdin_with_store(
1037            self, mock_stdin, mock_access, mock_utils_exit):
1038        expected_msg = ('--store option should only be provided with --file '
1039                        'option or stdin for the glance-direct import method.')
1040        my_args = self.base_args.copy()
1041        my_args['import_method'] = 'glance-direct'
1042        my_args['store'] = 'file1'
1043        args = self._make_args(my_args)
1044
1045        mock_stdin.isatty = lambda: True
1046        mock_access.return_value = False
1047        mock_utils_exit.side_effect = self._mock_utils_exit
1048        with mock.patch.object(self.gc.images,
1049                               'get_import_info') as mocked_info:
1050            with mock.patch.object(self.gc.images,
1051                                   'get_stores_info') as mocked_stores_info:
1052                mocked_stores_info.return_value = self.stores_info_response
1053                mocked_info.return_value = self.import_info_response
1054                try:
1055                    test_shell.do_image_create_via_import(self.gc, args)
1056                    self.fail("utils.exit should have been called")
1057                except SystemExit:
1058                    pass
1059        mock_utils_exit.assert_called_once_with(expected_msg)
1060
1061    @mock.patch('glanceclient.common.utils.exit')
1062    @mock.patch('sys.stdin', autospec=True)
1063    def test_neg_image_create_via_import_no_uri_with_store(
1064            self, mock_stdin, mock_utils_exit):
1065        expected_msg = ('--store option should only be provided with --uri '
1066                        'option for the web-download import method.')
1067        my_args = self.base_args.copy()
1068        my_args['import_method'] = 'web-download'
1069        my_args['store'] = 'file1'
1070        args = self._make_args(my_args)
1071        mock_utils_exit.side_effect = self._mock_utils_exit
1072        with mock.patch.object(self.gc.images,
1073                               'get_import_info') as mocked_info:
1074            with mock.patch.object(self.gc.images,
1075                                   'get_stores_info') as mocked_stores_info:
1076                mocked_stores_info.return_value = self.stores_info_response
1077                mocked_info.return_value = self.import_info_response
1078                try:
1079                    test_shell.do_image_create_via_import(self.gc, args)
1080                    self.fail("utils.exit should have been called")
1081                except SystemExit:
1082                    pass
1083        mock_utils_exit.assert_called_once_with(expected_msg)
1084
1085    @mock.patch('glanceclient.common.utils.exit')
1086    @mock.patch('os.access')
1087    @mock.patch('sys.stdin', autospec=True)
1088    def test_neg_image_create_via_import_invalid_store(
1089            self, mock_stdin, mock_access, mock_utils_exit):
1090        expected_msg = ("Store 'dummy' is not valid for this cloud. "
1091                        "Valid values can be retrieved with stores-info"
1092                        " command.")
1093        my_args = self.base_args.copy()
1094        my_args['import_method'] = 'glance-direct'
1095        my_args['store'] = 'dummy'
1096        args = self._make_args(my_args)
1097
1098        mock_stdin.isatty = lambda: True
1099        mock_access.return_value = False
1100        mock_utils_exit.side_effect = self._mock_utils_exit
1101        with mock.patch.object(self.gc.images,
1102                               'get_import_info') as mocked_info:
1103            with mock.patch.object(self.gc.images,
1104                                   'get_stores_info') as mocked_stores_info:
1105                mocked_stores_info.return_value = self.stores_info_response
1106                mocked_info.return_value = self.import_info_response
1107                try:
1108                    test_shell.do_image_create_via_import(self.gc, args)
1109                    self.fail("utils.exit should have been called")
1110                except SystemExit:
1111                    pass
1112        mock_utils_exit.assert_called_once_with(expected_msg)
1113
1114    @mock.patch('glanceclient.common.utils.exit')
1115    @mock.patch('sys.stdin', autospec=True)
1116    def test_neg_image_create_via_import_no_method_passing_uri(
1117            self, mock_stdin, mock_utils_exit):
1118        expected_msg = ('You cannot use --uri without specifying an import '
1119                        'method.')
1120        my_args = self.base_args.copy()
1121        my_args['uri'] = 'http://example.com/whatever'
1122        args = self._make_args(my_args)
1123        mock_stdin.isatty = lambda: True
1124        mock_utils_exit.side_effect = self._mock_utils_exit
1125        with mock.patch.object(self.gc.images,
1126                               'get_import_info') as mocked_info:
1127            mocked_info.return_value = self.import_info_response
1128            try:
1129                test_shell.do_image_create_via_import(self.gc, args)
1130                self.fail("utils.exit should have been called")
1131            except SystemExit:
1132                pass
1133        mock_utils_exit.assert_called_once_with(expected_msg)
1134
1135    @mock.patch('glanceclient.common.utils.exit')
1136    @mock.patch('sys.stdin', autospec=True)
1137    def test_neg_image_create_via_import_glance_direct_no_data(
1138            self, mock_stdin, mock_utils_exit):
1139        expected_msg = ('You must specify a --file or provide data via stdin '
1140                        'for the glance-direct import method.')
1141        my_args = self.base_args.copy()
1142        my_args['import_method'] = 'glance-direct'
1143        args = self._make_args(my_args)
1144        mock_stdin.isatty = lambda: True
1145        mock_utils_exit.side_effect = self._mock_utils_exit
1146        with mock.patch.object(self.gc.images,
1147                               'get_import_info') as mocked_info:
1148            mocked_info.return_value = self.import_info_response
1149            try:
1150                test_shell.do_image_create_via_import(self.gc, args)
1151                self.fail("utils.exit should have been called")
1152            except SystemExit:
1153                pass
1154        mock_utils_exit.assert_called_once_with(expected_msg)
1155
1156    @mock.patch('glanceclient.common.utils.exit')
1157    @mock.patch('sys.stdin', autospec=True)
1158    def test_neg_image_create_via_import_glance_direct_with_uri(
1159            self, mock_stdin, mock_utils_exit):
1160        expected_msg = ('You cannot specify a --uri with the glance-direct '
1161                        'import method.')
1162        my_args = self.base_args.copy()
1163        my_args['import_method'] = 'glance-direct'
1164        my_args['uri'] = 'https://example.com/some/stuff'
1165        args = self._make_args(my_args)
1166        mock_stdin.isatty = lambda: True
1167        mock_utils_exit.side_effect = self._mock_utils_exit
1168        with mock.patch.object(self.gc.images,
1169                               'get_import_info') as mocked_info:
1170            mocked_info.return_value = self.import_info_response
1171            try:
1172                test_shell.do_image_create_via_import(self.gc, args)
1173                self.fail("utils.exit should have been called")
1174            except SystemExit:
1175                pass
1176        mock_utils_exit.assert_called_once_with(expected_msg)
1177
1178    @mock.patch('glanceclient.common.utils.exit')
1179    @mock.patch('os.access')
1180    @mock.patch('sys.stdin', autospec=True)
1181    def test_neg_image_create_via_import_glance_direct_with_file_and_uri(
1182            self, mock_stdin, mock_access, mock_utils_exit):
1183        expected_msg = ('You cannot specify a --uri with the glance-direct '
1184                        'import method.')
1185        my_args = self.base_args.copy()
1186        my_args['import_method'] = 'glance-direct'
1187        my_args['uri'] = 'https://example.com/some/stuff'
1188        my_args['file'] = 'my.browncow'
1189        args = self._make_args(my_args)
1190        mock_stdin.isatty = lambda: True
1191        mock_access.return_value = True
1192        mock_utils_exit.side_effect = self._mock_utils_exit
1193        with mock.patch.object(self.gc.images,
1194                               'get_import_info') as mocked_info:
1195            mocked_info.return_value = self.import_info_response
1196            try:
1197                test_shell.do_image_create_via_import(self.gc, args)
1198                self.fail("utils.exit should have been called")
1199            except SystemExit:
1200                pass
1201        mock_utils_exit.assert_called_once_with(expected_msg)
1202
1203    @mock.patch('glanceclient.common.utils.exit')
1204    @mock.patch('sys.stdin', autospec=True)
1205    def test_neg_image_create_via_import_glance_direct_with_data_and_uri(
1206            self, mock_stdin, mock_utils_exit):
1207        expected_msg = ('You cannot specify a --uri with the glance-direct '
1208                        'import method.')
1209        my_args = self.base_args.copy()
1210        my_args['import_method'] = 'glance-direct'
1211        my_args['uri'] = 'https://example.com/some/stuff'
1212        args = self._make_args(my_args)
1213        mock_stdin.isatty = lambda: False
1214        mock_utils_exit.side_effect = self._mock_utils_exit
1215        with mock.patch.object(self.gc.images,
1216                               'get_import_info') as mocked_info:
1217            mocked_info.return_value = self.import_info_response
1218            try:
1219                test_shell.do_image_create_via_import(self.gc, args)
1220                self.fail("utils.exit should have been called")
1221            except SystemExit:
1222                pass
1223        mock_utils_exit.assert_called_once_with(expected_msg)
1224
1225    @mock.patch('glanceclient.common.utils.exit')
1226    @mock.patch('sys.stdin', autospec=True)
1227    def test_neg_image_create_via_import_web_download_no_uri(
1228            self, mock_stdin, mock_utils_exit):
1229        expected_msg = ('URI is required for web-download import method. '
1230                        'Please use \'--uri <uri>\'.')
1231        my_args = self.base_args.copy()
1232        my_args['import_method'] = 'web-download'
1233        args = self._make_args(my_args)
1234        mock_stdin.isatty = lambda: True
1235        mock_utils_exit.side_effect = self._mock_utils_exit
1236        with mock.patch.object(self.gc.images,
1237                               'get_import_info') as mocked_info:
1238            mocked_info.return_value = self.import_info_response
1239            try:
1240                test_shell.do_image_create_via_import(self.gc, args)
1241                self.fail("utils.exit should have been called")
1242            except SystemExit:
1243                pass
1244        mock_utils_exit.assert_called_once_with(expected_msg)
1245
1246    @mock.patch('glanceclient.common.utils.exit')
1247    def test_neg_image_create_via_import_stores_without_uri(
1248            self, mock_utils_exit):
1249        expected_msg = ('--stores option should only be provided with --uri '
1250                        'option for the web-download import method.')
1251        mock_utils_exit.side_effect = self._mock_utils_exit
1252        my_args = self.base_args.copy()
1253        my_args.update(
1254            {'id': 'IMG-01', 'import_method': 'web-download',
1255             'stores': 'file1,file2',
1256             'disk_format': 'raw',
1257             'container_format': 'bare',
1258             })
1259        args = self._make_args(my_args)
1260
1261        with mock.patch.object(self.gc.images,
1262                               'get_import_info') as mocked_info:
1263            with mock.patch.object(self.gc.images,
1264                                   'get_stores_info') as mocked_stores_info:
1265                mocked_stores_info.return_value = self.stores_info_response
1266                mocked_info.return_value = self.import_info_response
1267                try:
1268                    test_shell.do_image_create_via_import(self.gc, args)
1269                    self.fail("utils.exit should have been called")
1270                except SystemExit:
1271                    pass
1272        mock_utils_exit.assert_called_once_with(expected_msg)
1273
1274    @mock.patch('glanceclient.common.utils.exit')
1275    def test_neg_image_create_via_import_all_stores_without_uri(
1276            self, mock_utils_exit):
1277        expected_msg = ('--all-stores option should only be provided with '
1278                        '--uri option for the web-download import '
1279                        'method.')
1280        mock_utils_exit.side_effect = self._mock_utils_exit
1281        my_args = self.base_args.copy()
1282        my_args.update(
1283            {'id': 'IMG-01', 'import_method': 'web-download',
1284             'os_all_stores': True,
1285             'disk_format': 'raw',
1286             'container_format': 'bare',
1287             })
1288        args = self._make_args(my_args)
1289
1290        with mock.patch.object(self.gc.images,
1291                               'get_import_info') as mocked_info:
1292            mocked_info.return_value = self.import_info_response
1293            try:
1294                test_shell.do_image_create_via_import(self.gc, args)
1295                self.fail("utils.exit should have been called")
1296            except SystemExit:
1297                pass
1298        mock_utils_exit.assert_called_once_with(expected_msg)
1299
1300    @mock.patch('glanceclient.common.utils.exit')
1301    @mock.patch('sys.stdin', autospec=True)
1302    def test_neg_image_create_via_import_web_download_no_uri_with_file(
1303            self, mock_stdin, mock_utils_exit):
1304        expected_msg = ('URI is required for web-download import method. '
1305                        'Please use \'--uri <uri>\'.')
1306        my_args = self.base_args.copy()
1307        my_args['import_method'] = 'web-download'
1308        my_args['file'] = 'my.browncow'
1309        args = self._make_args(my_args)
1310        mock_stdin.isatty = lambda: True
1311        mock_utils_exit.side_effect = self._mock_utils_exit
1312        with mock.patch.object(self.gc.images,
1313                               'get_import_info') as mocked_info:
1314            mocked_info.return_value = self.import_info_response
1315            try:
1316                test_shell.do_image_create_via_import(self.gc, args)
1317                self.fail("utils.exit should have been called")
1318            except SystemExit:
1319                pass
1320        mock_utils_exit.assert_called_once_with(expected_msg)
1321
1322    @mock.patch('glanceclient.common.utils.exit')
1323    @mock.patch('sys.stdin', autospec=True)
1324    def test_neg_image_create_via_import_web_download_no_uri_with_data(
1325            self, mock_stdin, mock_utils_exit):
1326        expected_msg = ('URI is required for web-download import method. '
1327                        'Please use \'--uri <uri>\'.')
1328        my_args = self.base_args.copy()
1329        my_args['import_method'] = 'web-download'
1330        my_args['file'] = 'my.browncow'
1331        args = self._make_args(my_args)
1332        mock_stdin.isatty = lambda: False
1333        mock_utils_exit.side_effect = self._mock_utils_exit
1334        with mock.patch.object(self.gc.images,
1335                               'get_import_info') as mocked_info:
1336            mocked_info.return_value = self.import_info_response
1337            try:
1338                test_shell.do_image_create_via_import(self.gc, args)
1339                self.fail("utils.exit should have been called")
1340            except SystemExit:
1341                pass
1342        mock_utils_exit.assert_called_once_with(expected_msg)
1343
1344    @mock.patch('glanceclient.common.utils.exit')
1345    @mock.patch('sys.stdin', autospec=True)
1346    def test_neg_image_create_via_import_web_download_with_data_and_uri(
1347            self, mock_stdin, mock_utils_exit):
1348        expected_msg = ('You cannot pass data via stdin with the web-download '
1349                        'import method.')
1350        my_args = self.base_args.copy()
1351        my_args['import_method'] = 'web-download'
1352        my_args['uri'] = 'https://example.com/some/stuff'
1353        args = self._make_args(my_args)
1354        mock_stdin.isatty = lambda: False
1355        mock_utils_exit.side_effect = self._mock_utils_exit
1356        with mock.patch.object(self.gc.images,
1357                               'get_import_info') as mocked_info:
1358            mocked_info.return_value = self.import_info_response
1359            try:
1360                test_shell.do_image_create_via_import(self.gc, args)
1361                self.fail("utils.exit should have been called")
1362            except SystemExit:
1363                pass
1364        mock_utils_exit.assert_called_once_with(expected_msg)
1365
1366    @mock.patch('glanceclient.common.utils.exit')
1367    @mock.patch('sys.stdin', autospec=True)
1368    def test_neg_image_create_via_import_web_download_with_file_and_uri(
1369            self, mock_stdin, mock_utils_exit):
1370        expected_msg = ('You cannot specify a --file with the web-download '
1371                        'import method.')
1372        my_args = self.base_args.copy()
1373        my_args['import_method'] = 'web-download'
1374        my_args['uri'] = 'https://example.com/some/stuff'
1375        my_args['file'] = 'my.browncow'
1376        args = self._make_args(my_args)
1377        mock_stdin.isatty = lambda: True
1378        mock_utils_exit.side_effect = self._mock_utils_exit
1379        with mock.patch.object(self.gc.images,
1380                               'get_import_info') as mocked_info:
1381            mocked_info.return_value = self.import_info_response
1382            try:
1383                test_shell.do_image_create_via_import(self.gc, args)
1384                self.fail("utils.exit should have been called")
1385            except SystemExit:
1386                pass
1387        mock_utils_exit.assert_called_once_with(expected_msg)
1388
1389    @mock.patch('glanceclient.common.utils.exit')
1390    @mock.patch('sys.stdin', autospec=True)
1391    def test_neg_image_create_via_import_bad_method(
1392            self, mock_stdin, mock_utils_exit):
1393        expected_msg = ('Import method \'swift-party-time\' is not valid '
1394                        'for this cloud. Valid values can be retrieved with '
1395                        'import-info command.')
1396        my_args = self.base_args.copy()
1397        my_args['import_method'] = 'swift-party-time'
1398        args = self._make_args(my_args)
1399        mock_stdin.isatty = lambda: True
1400        mock_utils_exit.side_effect = self._mock_utils_exit
1401        with mock.patch.object(self.gc.images,
1402                               'get_import_info') as mocked_info:
1403            mocked_info.return_value = self.import_info_response
1404            try:
1405                test_shell.do_image_create_via_import(self.gc, args)
1406                self.fail("utils.exit should have been called")
1407            except SystemExit:
1408                pass
1409        mock_utils_exit.assert_called_once_with(expected_msg)
1410
1411    @mock.patch('glanceclient.common.utils.exit')
1412    @mock.patch('sys.stdin', autospec=True)
1413    def test_neg_image_create_via_import_no_method_with_data_and_method_NA(
1414            self, mock_stdin, mock_utils_exit):
1415        expected_msg = ('Import method \'glance-direct\' is not valid '
1416                        'for this cloud. Valid values can be retrieved with '
1417                        'import-info command.')
1418        args = self._make_args(self.base_args)
1419        # need to fake some data, or this is "just like" a
1420        # create-image-record-only call
1421        mock_stdin.isatty = lambda: False
1422        mock_utils_exit.side_effect = self._mock_utils_exit
1423        my_import_info_response = deepcopy(self.import_info_response)
1424        my_import_info_response['import-methods']['value'] = ['web-download']
1425        with mock.patch.object(self.gc.images,
1426                               'get_import_info') as mocked_info:
1427            mocked_info.return_value = my_import_info_response
1428            try:
1429                test_shell.do_image_create_via_import(self.gc, args)
1430                self.fail("utils.exit should have been called")
1431            except SystemExit:
1432                pass
1433        mock_utils_exit.assert_called_once_with(expected_msg)
1434
1435    @mock.patch('glanceclient.common.utils.exit')
1436    @mock.patch('sys.stdin', autospec=True)
1437    def test_neg_image_create_via_import_good_method_not_available(
1438            self, mock_stdin, mock_utils_exit):
1439        """Make sure the good method names aren't hard coded somewhere"""
1440        expected_msg = ('Import method \'glance-direct\' is not valid for '
1441                        'this cloud. Valid values can be retrieved with '
1442                        'import-info command.')
1443        my_args = self.base_args.copy()
1444        my_args['import_method'] = 'glance-direct'
1445        args = self._make_args(my_args)
1446        mock_stdin.isatty = lambda: True
1447        mock_utils_exit.side_effect = self._mock_utils_exit
1448        my_import_info_response = deepcopy(self.import_info_response)
1449        my_import_info_response['import-methods']['value'] = ['bad-bad-method']
1450        with mock.patch.object(self.gc.images,
1451                               'get_import_info') as mocked_info:
1452            mocked_info.return_value = my_import_info_response
1453            try:
1454                test_shell.do_image_create_via_import(self.gc, args)
1455                self.fail("utils.exit should have been called")
1456            except SystemExit:
1457                pass
1458        mock_utils_exit.assert_called_once_with(expected_msg)
1459
1460    @mock.patch('glanceclient.v2.shell.do_image_import')
1461    @mock.patch('glanceclient.v2.shell.do_image_stage')
1462    @mock.patch('sys.stdin', autospec=True)
1463    def test_image_create_via_import_no_method_with_stdin(
1464            self, mock_stdin, mock_do_stage, mock_do_import):
1465        """Backward compat -> handle this like a glance-direct"""
1466        mock_stdin.isatty = lambda: False
1467        self.mock_get_data_file.return_value = io.StringIO()
1468        args = self._make_args(self.base_args)
1469        with mock.patch.object(self.gc.images, 'create') as mocked_create:
1470            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1471                with mock.patch.object(self.gc.images,
1472                                       'get_import_info') as mocked_info:
1473
1474                    ignore_fields = ['self', 'access', 'schema']
1475                    expect_image = dict([(field, field) for field in
1476                                         ignore_fields])
1477                    expect_image['id'] = 'via-stdin'
1478                    expect_image['name'] = 'Mortimer'
1479                    expect_image['disk_format'] = 'raw'
1480                    expect_image['container_format'] = 'bare'
1481                    mocked_create.return_value = expect_image
1482                    mocked_get.return_value = expect_image
1483                    mocked_info.return_value = self.import_info_response
1484
1485                    test_shell.do_image_create_via_import(self.gc, args)
1486                    mocked_create.assert_called_once()
1487                    mock_do_stage.assert_called_once()
1488                    mock_do_import.assert_called_once()
1489                    mocked_get.assert_called_with('via-stdin')
1490                    utils.print_dict.assert_called_with({
1491                        'id': 'via-stdin', 'name': 'Mortimer',
1492                        'disk_format': 'raw', 'container_format': 'bare'})
1493
1494    @mock.patch('glanceclient.v2.shell.do_image_import')
1495    @mock.patch('glanceclient.v2.shell.do_image_stage')
1496    @mock.patch('os.access')
1497    @mock.patch('sys.stdin', autospec=True)
1498    def test_image_create_via_import_no_method_passing_file(
1499            self, mock_stdin, mock_access, mock_do_stage, mock_do_import):
1500        """Backward compat -> handle this like a glance-direct"""
1501        mock_stdin.isatty = lambda: True
1502        self.mock_get_data_file.return_value = io.StringIO()
1503        mock_access.return_value = True
1504        my_args = self.base_args.copy()
1505        my_args['file'] = 'fake-image-file.browncow'
1506        args = self._make_args(my_args)
1507        with mock.patch.object(self.gc.images, 'create') as mocked_create:
1508            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1509                with mock.patch.object(self.gc.images,
1510                                       'get_import_info') as mocked_info:
1511
1512                    ignore_fields = ['self', 'access', 'schema']
1513                    expect_image = dict([(field, field) for field in
1514                                         ignore_fields])
1515                    expect_image['id'] = 'via-file'
1516                    expect_image['name'] = 'Mortimer'
1517                    expect_image['disk_format'] = 'raw'
1518                    expect_image['container_format'] = 'bare'
1519                    mocked_create.return_value = expect_image
1520                    mocked_get.return_value = expect_image
1521                    mocked_info.return_value = self.import_info_response
1522
1523                    test_shell.do_image_create_via_import(self.gc, args)
1524                    mocked_create.assert_called_once()
1525                    mock_do_stage.assert_called_once()
1526                    mock_do_import.assert_called_once()
1527                    mocked_get.assert_called_with('via-file')
1528                    utils.print_dict.assert_called_with({
1529                        'id': 'via-file', 'name': 'Mortimer',
1530                        'disk_format': 'raw', 'container_format': 'bare'})
1531
1532    @mock.patch('glanceclient.v2.shell.do_image_import')
1533    @mock.patch('glanceclient.v2.shell.do_image_stage')
1534    @mock.patch('sys.stdin', autospec=True)
1535    def test_do_image_create_via_import_with_no_method_no_data(
1536            self, mock_stdin, mock_do_image_stage, mock_do_image_import):
1537        """Create an image record without calling do_stage or do_import"""
1538        img_create_args = {'name': 'IMG-11',
1539                           'os_architecture': 'powerpc',
1540                           'id': 'watch-out-for-ossn-0075',
1541                           'progress': False}
1542        client_args = {'import_method': None,
1543                       'file': None,
1544                       'uri': None}
1545        temp_args = img_create_args.copy()
1546        temp_args.update(client_args)
1547        args = self._make_args(temp_args)
1548        with mock.patch.object(self.gc.images, 'create') as mocked_create:
1549            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1550                with mock.patch.object(self.gc.images,
1551                                       'get_import_info') as mocked_info:
1552
1553                    ignore_fields = ['self', 'access', 'schema']
1554                    expect_image = dict([(field, field) for field in
1555                                         ignore_fields])
1556                    expect_image['name'] = 'IMG-11'
1557                    expect_image['id'] = 'watch-out-for-ossn-0075'
1558                    expect_image['os_architecture'] = 'powerpc'
1559                    mocked_create.return_value = expect_image
1560                    mocked_get.return_value = expect_image
1561                    mocked_info.return_value = self.import_info_response
1562                    mock_stdin.isatty = lambda: True
1563
1564                    test_shell.do_image_create_via_import(self.gc, args)
1565                    mocked_create.assert_called_once_with(**img_create_args)
1566                    mocked_get.assert_called_with('watch-out-for-ossn-0075')
1567                    mock_do_image_stage.assert_not_called()
1568                    mock_do_image_import.assert_not_called()
1569                    utils.print_dict.assert_called_with({
1570                        'name': 'IMG-11', 'os_architecture': 'powerpc',
1571                        'id': 'watch-out-for-ossn-0075'})
1572
1573    @mock.patch('glanceclient.v2.shell.do_image_import')
1574    @mock.patch('glanceclient.v2.shell.do_image_stage')
1575    @mock.patch('sys.stdin', autospec=True)
1576    def test_do_image_create_via_import_with_web_download(
1577            self, mock_stdin, mock_do_image_stage, mock_do_image_import):
1578        temp_args = {'name': 'IMG-01',
1579                     'disk_format': 'vhd',
1580                     'container_format': 'bare',
1581                     'uri': 'http://example.com/image.qcow',
1582                     'import_method': 'web-download',
1583                     'progress': False}
1584        args = self._make_args(temp_args)
1585        with mock.patch.object(self.gc.images, 'create') as mocked_create:
1586            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1587                with mock.patch.object(self.gc.images,
1588                                       'get_import_info') as mocked_info:
1589
1590                    ignore_fields = ['self', 'access', 'schema']
1591                    expect_image = dict([(field, field) for field in
1592                                         ignore_fields])
1593                    expect_image['id'] = 'pass'
1594                    expect_image['name'] = 'IMG-01'
1595                    expect_image['disk_format'] = 'vhd'
1596                    expect_image['container_format'] = 'bare'
1597                    expect_image['status'] = 'queued'
1598                    mocked_create.return_value = expect_image
1599                    mocked_get.return_value = expect_image
1600                    mocked_info.return_value = self.import_info_response
1601                    mock_stdin.isatty = lambda: True
1602
1603                    test_shell.do_image_create_via_import(self.gc, args)
1604                    mock_do_image_stage.assert_not_called()
1605                    mock_do_image_import.assert_called_once()
1606                    mocked_create.assert_called_once_with(**temp_args)
1607                    mocked_get.assert_called_with('pass')
1608                    utils.print_dict.assert_called_with({
1609                        'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
1610                        'container_format': 'bare', 'status': 'queued'})
1611
1612    @mock.patch('glanceclient.v2.shell.do_image_import')
1613    @mock.patch('glanceclient.v2.shell.do_image_stage')
1614    @mock.patch('sys.stdin', autospec=True)
1615    def test_do_image_create_via_import_with_web_download_with_stores(
1616            self, mock_stdin, mock_do_image_stage, mock_do_image_import):
1617        temp_args = {'name': 'IMG-01',
1618                     'disk_format': 'vhd',
1619                     'container_format': 'bare',
1620                     'uri': 'http://example.com/image.qcow',
1621                     'import_method': 'web-download',
1622                     'progress': False,
1623                     'stores': 'file1,file2'}
1624        tmp2_args = {'name': 'IMG-01',
1625                     'disk_format': 'vhd',
1626                     'container_format': 'bare',
1627                     'uri': 'http://example.com/image.qcow',
1628                     'import_method': 'web-download',
1629                     'progress': False}
1630        args = self._make_args(temp_args)
1631        with mock.patch.object(self.gc.images, 'create') as mocked_create:
1632            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1633                with mock.patch.object(self.gc.images,
1634                                       'get_import_info') as mocked_info:
1635                    with mock.patch.object(self.gc.images,
1636                                           'get_stores_info') as m_stores_info:
1637
1638                        ignore_fields = ['self', 'access', 'schema']
1639                        expect_image = dict([(field, field) for field in
1640                                             ignore_fields])
1641                        expect_image['id'] = 'pass'
1642                        expect_image['name'] = 'IMG-01'
1643                        expect_image['disk_format'] = 'vhd'
1644                        expect_image['container_format'] = 'bare'
1645                        expect_image['status'] = 'queued'
1646                        mocked_create.return_value = expect_image
1647                        mocked_get.return_value = expect_image
1648                        mocked_info.return_value = self.import_info_response
1649                        m_stores_info.return_value = self.stores_info_response
1650                        mock_stdin.isatty = lambda: True
1651
1652                        test_shell.do_image_create_via_import(self.gc, args)
1653                        mock_do_image_stage.assert_not_called()
1654                        mock_do_image_import.assert_called_once()
1655                        mocked_create.assert_called_once_with(**tmp2_args)
1656                        mocked_get.assert_called_with('pass')
1657                        utils.print_dict.assert_called_with({
1658                            'id': 'pass', 'name': 'IMG-01',
1659                            'disk_format': 'vhd',
1660                            'container_format': 'bare', 'status': 'queued'})
1661
1662    def test_do_image_update_no_user_props(self):
1663        args = self._make_args({'id': 'pass', 'name': 'IMG-01',
1664                                'disk_format': 'vhd',
1665                                'container_format': 'bare'})
1666        with mock.patch.object(self.gc.images, 'update') as mocked_update:
1667            ignore_fields = ['self', 'access', 'file', 'schema']
1668            expect_image = dict([(field, field) for field in ignore_fields])
1669            expect_image['id'] = 'pass'
1670            expect_image['name'] = 'IMG-01'
1671            expect_image['disk_format'] = 'vhd'
1672            expect_image['container_format'] = 'bare'
1673            mocked_update.return_value = expect_image
1674
1675            test_shell.do_image_update(self.gc, args)
1676
1677            mocked_update.assert_called_once_with('pass',
1678                                                  None,
1679                                                  name='IMG-01',
1680                                                  disk_format='vhd',
1681                                                  container_format='bare')
1682            utils.print_dict.assert_called_once_with({
1683                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
1684                'container_format': 'bare'})
1685
1686    def test_do_image_update_hide_image(self):
1687        args = self._make_args({'id': 'pass', 'os_hidden': 'true'})
1688        with mock.patch.object(self.gc.images, 'update') as mocked_update:
1689            ignore_fields = ['self', 'access', 'file', 'schema']
1690            expect_image = dict([(field, field) for field in ignore_fields])
1691            expect_image['id'] = 'pass'
1692            expect_image['name'] = 'IMG-01'
1693            expect_image['disk_format'] = 'vhd'
1694            expect_image['container_format'] = 'bare'
1695            expect_image['os_hidden'] = True
1696            mocked_update.return_value = expect_image
1697
1698            test_shell.do_image_update(self.gc, args)
1699
1700            mocked_update.assert_called_once_with('pass',
1701                                                  None,
1702                                                  os_hidden='true')
1703            utils.print_dict.assert_called_once_with({
1704                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
1705                'container_format': 'bare', 'os_hidden': True})
1706
1707    def test_do_image_update_revert_hide_image(self):
1708        args = self._make_args({'id': 'pass', 'os_hidden': 'false'})
1709        with mock.patch.object(self.gc.images, 'update') as mocked_update:
1710            ignore_fields = ['self', 'access', 'file', 'schema']
1711            expect_image = dict([(field, field) for field in ignore_fields])
1712            expect_image['id'] = 'pass'
1713            expect_image['name'] = 'IMG-01'
1714            expect_image['disk_format'] = 'vhd'
1715            expect_image['container_format'] = 'bare'
1716            expect_image['os_hidden'] = False
1717            mocked_update.return_value = expect_image
1718
1719            test_shell.do_image_update(self.gc, args)
1720
1721            mocked_update.assert_called_once_with('pass',
1722                                                  None,
1723                                                  os_hidden='false')
1724            utils.print_dict.assert_called_once_with({
1725                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
1726                'container_format': 'bare', 'os_hidden': False})
1727
1728    def test_do_image_update_with_user_props(self):
1729        args = self._make_args({'id': 'pass', 'name': 'IMG-01',
1730                                'property': ['myprop=myval']})
1731        with mock.patch.object(self.gc.images, 'update') as mocked_update:
1732            ignore_fields = ['self', 'access', 'file', 'schema']
1733            expect_image = dict([(field, field) for field in ignore_fields])
1734            expect_image['id'] = 'pass'
1735            expect_image['name'] = 'IMG-01'
1736            expect_image['myprop'] = 'myval'
1737            mocked_update.return_value = expect_image
1738
1739            test_shell.do_image_update(self.gc, args)
1740
1741            mocked_update.assert_called_once_with('pass',
1742                                                  None,
1743                                                  name='IMG-01',
1744                                                  myprop='myval')
1745            utils.print_dict.assert_called_once_with({
1746                'id': 'pass', 'name': 'IMG-01', 'myprop': 'myval'})
1747
1748    def test_do_image_update_with_remove_props(self):
1749        args = self._make_args({'id': 'pass', 'name': 'IMG-01',
1750                                'disk_format': 'vhd',
1751                                'remove-property': ['container_format']})
1752        with mock.patch.object(self.gc.images, 'update') as mocked_update:
1753            ignore_fields = ['self', 'access', 'file', 'schema']
1754            expect_image = dict([(field, field) for field in ignore_fields])
1755            expect_image['id'] = 'pass'
1756            expect_image['name'] = 'IMG-01'
1757            expect_image['disk_format'] = 'vhd'
1758
1759            mocked_update.return_value = expect_image
1760
1761            test_shell.do_image_update(self.gc, args)
1762
1763            mocked_update.assert_called_once_with('pass',
1764                                                  ['container_format'],
1765                                                  name='IMG-01',
1766                                                  disk_format='vhd')
1767            utils.print_dict.assert_called_once_with({
1768                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd'})
1769
1770    def test_do_explain(self):
1771        input = {
1772            'page_size': 18,
1773            'id': 'pass',
1774            'schemas': 'test',
1775            'model': 'test',
1776        }
1777        args = self._make_args(input)
1778        with mock.patch.object(utils, 'print_list'):
1779            test_shell.do_explain(self.gc, args)
1780
1781            self.gc.schemas.get.assert_called_once_with('test')
1782
1783    def test_do_location_add(self):
1784        gc = self.gc
1785        loc = {'url': 'http://foo.com/',
1786               'metadata': {'foo': 'bar'},
1787               'validation_data': {'checksum': 'csum',
1788                                   'os_hash_algo': 'algo',
1789                                   'os_hash_value': 'value'}}
1790        args = {'id': 'pass',
1791                'url': loc['url'],
1792                'metadata': json.dumps(loc['metadata']),
1793                'checksum': 'csum',
1794                'hash_algo': 'algo',
1795                'hash_value': 'value'}
1796        with mock.patch.object(gc.images, 'add_location') as mocked_addloc:
1797            expect_image = {'id': 'pass', 'locations': [loc]}
1798            mocked_addloc.return_value = expect_image
1799
1800            test_shell.do_location_add(self.gc, self._make_args(args))
1801            mocked_addloc.assert_called_once_with(
1802                'pass', loc['url'], loc['metadata'],
1803                validation_data=loc['validation_data'])
1804            utils.print_dict.assert_called_once_with(expect_image)
1805
1806    def test_do_location_delete(self):
1807        gc = self.gc
1808        loc_set = set(['http://foo/bar', 'http://spam/ham'])
1809        args = self._make_args({'id': 'pass', 'url': loc_set})
1810
1811        with mock.patch.object(gc.images, 'delete_locations') as mocked_rmloc:
1812            test_shell.do_location_delete(self.gc, args)
1813            mocked_rmloc.assert_called_once_with('pass', loc_set)
1814
1815    def test_do_location_update(self):
1816        gc = self.gc
1817        loc = {'url': 'http://foo.com/', 'metadata': {'foo': 'bar'}}
1818        args = self._make_args({'id': 'pass',
1819                                'url': loc['url'],
1820                                'metadata': json.dumps(loc['metadata'])})
1821        with mock.patch.object(gc.images, 'update_location') as mocked_modloc:
1822            expect_image = {'id': 'pass', 'locations': [loc]}
1823            mocked_modloc.return_value = expect_image
1824
1825            test_shell.do_location_update(self.gc, args)
1826            mocked_modloc.assert_called_once_with('pass',
1827                                                  loc['url'],
1828                                                  loc['metadata'])
1829            utils.print_dict.assert_called_once_with(expect_image)
1830
1831    def test_image_upload(self):
1832        args = self._make_args(
1833            {'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False})
1834
1835        with mock.patch.object(self.gc.images, 'upload') as mocked_upload:
1836            utils.get_data_file = mock.Mock(return_value='testfile')
1837            mocked_upload.return_value = None
1838            test_shell.do_image_upload(self.gc, args)
1839            mocked_upload.assert_called_once_with('IMG-01', 'testfile', 1024,
1840                                                  backend=None)
1841
1842    @mock.patch('glanceclient.common.utils.exit')
1843    def test_image_upload_invalid_store(self, mock_utils_exit):
1844        expected_msg = ("Store 'dummy' is not valid for this cloud. "
1845                        "Valid values can be retrieved with stores-info "
1846                        "command.")
1847        mock_utils_exit.side_effect = self._mock_utils_exit
1848
1849        args = self._make_args(
1850            {'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False,
1851             'store': 'dummy'})
1852
1853        with mock.patch.object(self.gc.images,
1854                               'get_stores_info') as mock_stores_info:
1855            mock_stores_info.return_value = self.stores_info_response
1856            try:
1857                test_shell.do_image_upload(self.gc, args)
1858                self.fail("utils.exit should have been called")
1859            except SystemExit:
1860                pass
1861
1862        mock_utils_exit.assert_called_once_with(expected_msg)
1863
1864    @mock.patch('glanceclient.common.utils.exit')
1865    def test_neg_image_import_not_available(self, mock_utils_exit):
1866        expected_msg = 'Target Glance does not support Image Import workflow'
1867        mock_utils_exit.side_effect = self._mock_utils_exit
1868        args = self._make_args(
1869            {'id': 'IMG-01', 'import_method': 'smarty-pants', 'uri': None})
1870        with mock.patch.object(self.gc.images, 'import') as mocked_import:
1871            with mock.patch.object(self.gc.images,
1872                                   'get_import_info') as mocked_info:
1873                mocked_info.side_effect = exc.HTTPNotFound
1874                try:
1875                    test_shell.do_image_import(self.gc, args)
1876                    self.fail("utils.exit should have been called")
1877                except SystemExit:
1878                    pass
1879        mock_utils_exit.assert_called_once_with(expected_msg)
1880        mocked_import.assert_not_called()
1881
1882    @mock.patch('glanceclient.common.utils.exit')
1883    def test_neg_image_import_bad_method(self, mock_utils_exit):
1884        expected_msg = ('Import method \'smarty-pants\' is not valid for this '
1885                        'cloud. Valid values can be retrieved with '
1886                        'import-info command.')
1887        mock_utils_exit.side_effect = self._mock_utils_exit
1888        args = self._make_args(
1889            {'id': 'IMG-01', 'import_method': 'smarty-pants', 'uri': None})
1890        with mock.patch.object(self.gc.images,
1891                               'get_import_info') as mocked_info:
1892            mocked_info.return_value = self.import_info_response
1893            try:
1894                test_shell.do_image_import(self.gc, args)
1895                self.fail("utils.exit should have been called")
1896            except SystemExit:
1897                pass
1898        mock_utils_exit.assert_called_once_with(expected_msg)
1899
1900    @mock.patch('glanceclient.common.utils.exit')
1901    def test_neg_image_import_no_methods_configured(self, mock_utils_exit):
1902        expected_msg = ('Import method \'glance-direct\' is not valid for '
1903                        'this cloud. Valid values can be retrieved with '
1904                        'import-info command.')
1905        mock_utils_exit.side_effect = self._mock_utils_exit
1906        args = self._make_args(
1907            {'id': 'IMG-01', 'import_method': 'glance-direct', 'uri': None})
1908        with mock.patch.object(self.gc.images,
1909                               'get_import_info') as mocked_info:
1910            mocked_info.return_value = {"import-methods": {"value": []}}
1911            try:
1912                test_shell.do_image_import(self.gc, args)
1913                self.fail("utils.exit should have been called")
1914            except SystemExit:
1915                pass
1916        mock_utils_exit.assert_called_once_with(expected_msg)
1917
1918    @mock.patch('glanceclient.common.utils.exit')
1919    def test_neg_image_import_glance_direct_image_not_uploading_status(
1920            self, mock_utils_exit):
1921        expected_msg = ('The \'glance-direct\' import method can only be '
1922                        'applied to an image in status \'uploading\'')
1923        mock_utils_exit.side_effect = self._mock_utils_exit
1924        args = self._make_args(
1925            {'id': 'IMG-01', 'import_method': 'glance-direct', 'uri': None})
1926        with mock.patch.object(self.gc.images,
1927                               'get_import_info') as mocked_info:
1928            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1929                mocked_get.return_value = {'status': 'queued',
1930                                           'container_format': 'bare',
1931                                           'disk_format': 'raw'}
1932                mocked_info.return_value = self.import_info_response
1933                try:
1934                    test_shell.do_image_import(self.gc, args)
1935                    self.fail("utils.exit should have been called")
1936                except SystemExit:
1937                    pass
1938        mock_utils_exit.assert_called_once_with(expected_msg)
1939
1940    @mock.patch('glanceclient.common.utils.exit')
1941    def test_neg_image_import_web_download_image_not_queued_status(
1942            self, mock_utils_exit):
1943        expected_msg = ('The \'web-download\' import method can only be '
1944                        'applied to an image in status \'queued\'')
1945        mock_utils_exit.side_effect = self._mock_utils_exit
1946        args = self._make_args(
1947            {'id': 'IMG-01', 'import_method': 'web-download',
1948             'uri': 'http://joes-image-shack.com/funky.qcow2'})
1949        with mock.patch.object(self.gc.images,
1950                               'get_import_info') as mocked_info:
1951            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1952                mocked_get.return_value = {'status': 'uploading',
1953                                           'container_format': 'bare',
1954                                           'disk_format': 'raw'}
1955                mocked_info.return_value = self.import_info_response
1956                try:
1957                    test_shell.do_image_import(self.gc, args)
1958                    self.fail("utils.exit should have been called")
1959                except SystemExit:
1960                    pass
1961        mock_utils_exit.assert_called_once_with(expected_msg)
1962
1963    @mock.patch('glanceclient.common.utils.exit')
1964    def test_neg_image_import_image_no_container_format(
1965            self, mock_utils_exit):
1966        expected_msg = ('The \'container_format\' and \'disk_format\' '
1967                        'properties must be set on an image before it can be '
1968                        'imported.')
1969        mock_utils_exit.side_effect = self._mock_utils_exit
1970        args = self._make_args(
1971            {'id': 'IMG-01', 'import_method': 'web-download',
1972             'uri': 'http://joes-image-shack.com/funky.qcow2'})
1973        with mock.patch.object(self.gc.images,
1974                               'get_import_info') as mocked_info:
1975            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1976                mocked_get.return_value = {'status': 'uploading',
1977                                           'disk_format': 'raw'}
1978                mocked_info.return_value = self.import_info_response
1979                try:
1980                    test_shell.do_image_import(self.gc, args)
1981                    self.fail("utils.exit should have been called")
1982                except SystemExit:
1983                    pass
1984        mock_utils_exit.assert_called_once_with(expected_msg)
1985
1986    @mock.patch('glanceclient.common.utils.exit')
1987    def test_neg_image_import_image_no_disk_format(
1988            self, mock_utils_exit):
1989        expected_msg = ('The \'container_format\' and \'disk_format\' '
1990                        'properties must be set on an image before it can be '
1991                        'imported.')
1992        mock_utils_exit.side_effect = self._mock_utils_exit
1993        args = self._make_args(
1994            {'id': 'IMG-01', 'import_method': 'web-download',
1995             'uri': 'http://joes-image-shack.com/funky.qcow2'})
1996        with mock.patch.object(self.gc.images,
1997                               'get_import_info') as mocked_info:
1998            with mock.patch.object(self.gc.images, 'get') as mocked_get:
1999                mocked_get.return_value = {'status': 'uploading',
2000                                           'container_format': 'bare'}
2001                mocked_info.return_value = self.import_info_response
2002                try:
2003                    test_shell.do_image_import(self.gc, args)
2004                    self.fail("utils.exit should have been called")
2005                except SystemExit:
2006                    pass
2007        mock_utils_exit.assert_called_once_with(expected_msg)
2008
2009    @mock.patch('glanceclient.common.utils.exit')
2010    def test_image_import_invalid_store(self, mock_utils_exit):
2011        expected_msg = ("Store 'dummy' is not valid for this cloud. "
2012                        "Valid values can be retrieved with stores-info "
2013                        "command.")
2014        mock_utils_exit.side_effect = self._mock_utils_exit
2015
2016        args = self._make_args(
2017            {'id': 'IMG-01', 'import_method': 'glance-direct', 'uri': None,
2018             'store': 'dummy'})
2019
2020        with mock.patch.object(self.gc.images, 'get') as mocked_get:
2021            with mock.patch.object(self.gc.images,
2022                                   'get_import_info') as mocked_info:
2023                mocked_get.return_value = {'status': 'uploading',
2024                                           'container_format': 'bare',
2025                                           'disk_format': 'raw'}
2026                with mock.patch.object(self.gc.images,
2027                                       'get_stores_info') as mock_stores_info:
2028                    mocked_info.return_value = self.import_info_response
2029                    mock_stores_info.return_value = self.stores_info_response
2030                    try:
2031                        test_shell.do_image_import(self.gc, args)
2032                        self.fail("utils.exit should have been called")
2033                    except SystemExit:
2034                        pass
2035
2036        mock_utils_exit.assert_called_once_with(expected_msg)
2037
2038    def test_image_import_glance_direct(self):
2039        args = self._make_args(
2040            {'id': 'IMG-01', 'import_method': 'glance-direct', 'uri': None})
2041        with mock.patch.object(self.gc.images, 'image_import') as mock_import:
2042            with mock.patch.object(self.gc.images, 'get') as mocked_get:
2043                with mock.patch.object(self.gc.images,
2044                                       'get_import_info') as mocked_info:
2045                    mocked_get.return_value = {'status': 'uploading',
2046                                               'container_format': 'bare',
2047                                               'disk_format': 'raw'}
2048                    mocked_info.return_value = self.import_info_response
2049                    mock_import.return_value = None
2050                    test_shell.do_image_import(self.gc, args)
2051                    mock_import.assert_called_once_with(
2052                        'IMG-01', 'glance-direct', None, backend=None,
2053                        all_stores=None, allow_failure=True, stores=None)
2054
2055    def test_image_import_web_download(self):
2056        args = self._make_args(
2057            {'id': 'IMG-01', 'uri': 'http://example.com/image.qcow',
2058             'import_method': 'web-download'})
2059        with mock.patch.object(self.gc.images, 'image_import') as mock_import:
2060            with mock.patch.object(self.gc.images, 'get') as mocked_get:
2061                with mock.patch.object(self.gc.images,
2062                                       'get_import_info') as mocked_info:
2063                    mocked_get.return_value = {'status': 'queued',
2064                                               'container_format': 'bare',
2065                                               'disk_format': 'raw'}
2066                    mocked_info.return_value = self.import_info_response
2067                    mock_import.return_value = None
2068                    test_shell.do_image_import(self.gc, args)
2069                    mock_import.assert_called_once_with(
2070                        'IMG-01', 'web-download',
2071                        'http://example.com/image.qcow',
2072                        all_stores=None, allow_failure=True,
2073                        backend=None, stores=None)
2074
2075    @mock.patch('glanceclient.common.utils.print_image')
2076    def test_image_import_no_print_image(self, mocked_utils_print_image):
2077        args = self._make_args(
2078            {'id': 'IMG-02', 'uri': None, 'import_method': 'glance-direct',
2079             'from_create': True})
2080        with mock.patch.object(self.gc.images, 'image_import') as mock_import:
2081            with mock.patch.object(self.gc.images, 'get') as mocked_get:
2082                with mock.patch.object(self.gc.images,
2083                                       'get_import_info') as mocked_info:
2084                    mocked_get.return_value = {'status': 'uploading',
2085                                               'container_format': 'bare',
2086                                               'disk_format': 'raw'}
2087                    mocked_info.return_value = self.import_info_response
2088                    mock_import.return_value = None
2089                    test_shell.do_image_import(self.gc, args)
2090                    mock_import.assert_called_once_with(
2091                        'IMG-02', 'glance-direct', None, stores=None,
2092                        all_stores=None, allow_failure=True, backend=None)
2093                    mocked_utils_print_image.assert_not_called()
2094
2095    @mock.patch('glanceclient.common.utils.print_image')
2096    @mock.patch('glanceclient.v2.shell._validate_backend')
2097    def test_image_import_multiple_stores(self, mocked_utils_print_image,
2098                                          msvb):
2099        args = self._make_args(
2100            {'id': 'IMG-02', 'uri': None, 'import_method': 'glance-direct',
2101                'from_create': False, 'stores': 'site1,site2'})
2102        with mock.patch.object(self.gc.images, 'image_import') as mock_import:
2103            with mock.patch.object(self.gc.images, 'get') as mocked_get:
2104                with mock.patch.object(self.gc.images,
2105                                       'get_import_info') as mocked_info:
2106                    mocked_get.return_value = {'status': 'uploading',
2107                                               'container_format': 'bare',
2108                                               'disk_format': 'raw'}
2109                    mocked_info.return_value = self.import_info_response
2110                    mock_import.return_value = None
2111                    test_shell.do_image_import(self.gc, args)
2112                    mock_import.assert_called_once_with(
2113                        'IMG-02', 'glance-direct', None, all_stores=None,
2114                        allow_failure=True, stores=['site1', 'site2'],
2115                        backend=None)
2116
2117    @mock.patch('glanceclient.common.utils.print_image')
2118    @mock.patch('glanceclient.v2.shell._validate_backend')
2119    def test_image_import_copy_image(self, mocked_utils_print_image,
2120                                     msvb):
2121        args = self._make_args(
2122            {'id': 'IMG-02', 'uri': None, 'import_method': 'copy-image',
2123                'from_create': False, 'stores': 'file1,file2'})
2124        with mock.patch.object(self.gc.images, 'image_import') as mock_import:
2125            with mock.patch.object(self.gc.images, 'get') as mocked_get:
2126                with mock.patch.object(self.gc.images,
2127                                       'get_import_info') as mocked_info:
2128                    mocked_get.return_value = {'status': 'active',
2129                                               'container_format': 'bare',
2130                                               'disk_format': 'raw'}
2131                    mocked_info.return_value = self.import_info_response
2132                    mock_import.return_value = None
2133                    test_shell.do_image_import(self.gc, args)
2134                    mock_import.assert_called_once_with(
2135                        'IMG-02', 'copy-image', None, all_stores=None,
2136                        allow_failure=True, stores=['file1', 'file2'],
2137                        backend=None)
2138
2139    @mock.patch('glanceclient.common.utils.exit')
2140    def test_neg_image_import_copy_image_not_active(
2141            self, mock_utils_exit):
2142        expected_msg = ("The 'copy-image' import method can only be used on "
2143                        "an image with status 'active'.")
2144        mock_utils_exit.side_effect = self._mock_utils_exit
2145        args = self._make_args(
2146            {'id': 'IMG-02', 'uri': None, 'import_method': 'copy-image',
2147             'disk_format': 'raw',
2148             'container_format': 'bare',
2149             'from_create': False, 'stores': 'file1,file2'})
2150        with mock.patch.object(
2151                self.gc.images,
2152                'get_stores_info') as mocked_stores_info:
2153            with mock.patch.object(self.gc.images, 'get') as mocked_get:
2154                with mock.patch.object(self.gc.images,
2155                                       'get_import_info') as mocked_info:
2156
2157                    mocked_stores_info.return_value = self.stores_info_response
2158                    mocked_get.return_value = {'status': 'uploading',
2159                                               'container_format': 'bare',
2160                                               'disk_format': 'raw'}
2161                    mocked_info.return_value = self.import_info_response
2162                    try:
2163                        test_shell.do_image_import(self.gc, args)
2164                        self.fail("utils.exit should have been called")
2165                    except SystemExit:
2166                        pass
2167            mock_utils_exit.assert_called_once_with(expected_msg)
2168
2169    @mock.patch('glanceclient.common.utils.exit')
2170    def test_neg_image_import_stores_all_stores_not_specified(
2171            self, mock_utils_exit):
2172        expected_msg = ("Provide either --stores or --all-stores for "
2173                        "'copy-image' import method.")
2174        mock_utils_exit.side_effect = self._mock_utils_exit
2175        my_args = self.base_args.copy()
2176        my_args.update(
2177            {'id': 'IMG-01', 'import_method': 'copy-image',
2178             'disk_format': 'raw',
2179             'container_format': 'bare',
2180             })
2181        args = self._make_args(my_args)
2182
2183        with mock.patch.object(self.gc.images,
2184                               'get_import_info') as mocked_info:
2185            mocked_info.return_value = self.import_info_response
2186            try:
2187                test_shell.do_image_import(self.gc, args)
2188                self.fail("utils.exit should have been called")
2189            except SystemExit:
2190                pass
2191        mock_utils_exit.assert_called_once_with(expected_msg)
2192
2193    def test_image_download(self):
2194        args = self._make_args(
2195            {'id': 'IMG-01', 'file': 'test', 'progress': True,
2196             'allow_md5_fallback': False})
2197
2198        with mock.patch.object(self.gc.images, 'data') as mocked_data, \
2199                mock.patch.object(utils, '_extract_request_id'):
2200            mocked_data.return_value = utils.RequestIdProxy(
2201                [c for c in 'abcdef'])
2202
2203            test_shell.do_image_download(self.gc, args)
2204            mocked_data.assert_called_once_with('IMG-01',
2205                                                allow_md5_fallback=False)
2206
2207        # check that non-default value is being passed correctly
2208        args.allow_md5_fallback = True
2209        with mock.patch.object(self.gc.images, 'data') as mocked_data, \
2210                mock.patch.object(utils, '_extract_request_id'):
2211            mocked_data.return_value = utils.RequestIdProxy(
2212                [c for c in 'abcdef'])
2213
2214            test_shell.do_image_download(self.gc, args)
2215            mocked_data.assert_called_once_with('IMG-01',
2216                                                allow_md5_fallback=True)
2217
2218    @mock.patch.object(utils, 'exit')
2219    @mock.patch('sys.stdout', autospec=True)
2220    def test_image_download_no_file_arg(self, mocked_stdout,
2221                                        mocked_utils_exit):
2222        # Indicate that no file name was given as command line argument
2223        args = self._make_args({'id': '1234', 'file': None, 'progress': False,
2224                                'allow_md5_fallback': False})
2225        # Indicate that no file is specified for output redirection
2226        mocked_stdout.isatty = lambda: True
2227        test_shell.do_image_download(self.gc, args)
2228        mocked_utils_exit.assert_called_once_with(
2229            'No redirection or local file specified for downloaded image'
2230            ' data. Please specify a local file with --file to save'
2231            ' downloaded image or redirect output to another source.')
2232
2233    def test_do_image_delete(self):
2234        args = argparse.Namespace(id=['image1', 'image2'])
2235        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
2236            mocked_delete.return_value = 0
2237
2238            test_shell.do_image_delete(self.gc, args)
2239            self.assertEqual(2, mocked_delete.call_count)
2240
2241    def test_do_image_deactivate(self):
2242        args = argparse.Namespace(id='image1')
2243        with mock.patch.object(self.gc.images,
2244                               'deactivate') as mocked_deactivate:
2245            mocked_deactivate.return_value = 0
2246
2247            test_shell.do_image_deactivate(self.gc, args)
2248            self.assertEqual(1, mocked_deactivate.call_count)
2249
2250    def test_do_image_reactivate(self):
2251        args = argparse.Namespace(id='image1')
2252        with mock.patch.object(self.gc.images,
2253                               'reactivate') as mocked_reactivate:
2254            mocked_reactivate.return_value = 0
2255
2256            test_shell.do_image_reactivate(self.gc, args)
2257            self.assertEqual(1, mocked_reactivate.call_count)
2258
2259    @mock.patch.object(utils, 'exit')
2260    @mock.patch.object(utils, 'print_err')
2261    def test_do_image_delete_with_invalid_ids(self, mocked_print_err,
2262                                              mocked_utils_exit):
2263        args = argparse.Namespace(id=['image1', 'image2'])
2264        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
2265            mocked_delete.side_effect = exc.HTTPNotFound
2266
2267            test_shell.do_image_delete(self.gc, args)
2268
2269            self.assertEqual(2, mocked_delete.call_count)
2270            self.assertEqual(2, mocked_print_err.call_count)
2271            mocked_utils_exit.assert_called_once_with()
2272
2273    @mock.patch.object(utils, 'exit')
2274    def test_do_image_delete_from_store_not_found(self, mocked_utils_exit):
2275        args = argparse.Namespace(id='image1', store='store1')
2276        with mock.patch.object(self.gc.images,
2277                               'delete_from_store') as mocked_delete:
2278            mocked_delete.side_effect = exc.HTTPNotFound
2279
2280            test_shell.do_stores_delete(self.gc, args)
2281
2282            self.assertEqual(1, mocked_delete.call_count)
2283            mocked_utils_exit.assert_called_once_with('Multi Backend support '
2284                                                      'is not enabled or '
2285                                                      'Image/store not found.')
2286
2287    def test_do_image_delete_from_store(self):
2288        args = argparse.Namespace(id='image1', store='store1')
2289        with mock.patch.object(self.gc.images,
2290                               'delete_from_store') as mocked_delete:
2291            test_shell.do_stores_delete(self.gc, args)
2292
2293            mocked_delete.assert_called_once_with('store1',
2294                                                  'image1')
2295
2296    @mock.patch.object(utils, 'exit')
2297    @mock.patch.object(utils, 'print_err')
2298    def test_do_image_delete_with_forbidden_ids(self, mocked_print_err,
2299                                                mocked_utils_exit):
2300        args = argparse.Namespace(id=['image1', 'image2'])
2301        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
2302            mocked_delete.side_effect = exc.HTTPForbidden
2303
2304            test_shell.do_image_delete(self.gc, args)
2305
2306            self.assertEqual(2, mocked_delete.call_count)
2307            self.assertEqual(2, mocked_print_err.call_count)
2308            mocked_utils_exit.assert_called_once_with()
2309
2310    @mock.patch.object(utils, 'exit')
2311    @mock.patch.object(utils, 'print_err')
2312    def test_do_image_delete_with_image_in_use(self, mocked_print_err,
2313                                               mocked_utils_exit):
2314        args = argparse.Namespace(id=['image1', 'image2'])
2315        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
2316            mocked_delete.side_effect = exc.HTTPConflict
2317
2318            test_shell.do_image_delete(self.gc, args)
2319
2320            self.assertEqual(2, mocked_delete.call_count)
2321            self.assertEqual(2, mocked_print_err.call_count)
2322            mocked_utils_exit.assert_called_once_with()
2323
2324    def test_do_image_delete_deleted(self):
2325        image_id = 'deleted-img'
2326        args = argparse.Namespace(id=[image_id])
2327        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
2328            mocked_delete.side_effect = exc.HTTPNotFound
2329
2330            self.assert_exits_with_msg(func=test_shell.do_image_delete,
2331                                       func_args=args)
2332
2333    @mock.patch('sys.stdout', autospec=True)
2334    @mock.patch.object(utils, 'print_err')
2335    def test_do_image_download_with_forbidden_id(self, mocked_print_err,
2336                                                 mocked_stdout):
2337        args = self._make_args({'id': 'IMG-01', 'file': None,
2338                                'progress': False,
2339                                'allow_md5_fallback': False})
2340        mocked_stdout.isatty = lambda: False
2341        with mock.patch.object(self.gc.images, 'data') as mocked_data:
2342            mocked_data.side_effect = exc.HTTPForbidden
2343            try:
2344                test_shell.do_image_download(self.gc, args)
2345                self.fail('Exit not called')
2346            except SystemExit:
2347                pass
2348
2349            self.assertEqual(1, mocked_data.call_count)
2350            self.assertEqual(1, mocked_print_err.call_count)
2351
2352    @mock.patch('sys.stdout', autospec=True)
2353    @mock.patch.object(utils, 'print_err')
2354    def test_do_image_download_with_500(self, mocked_print_err, mocked_stdout):
2355        args = self._make_args({'id': 'IMG-01', 'file': None,
2356                                'progress': False,
2357                                'allow_md5_fallback': False})
2358        mocked_stdout.isatty = lambda: False
2359        with mock.patch.object(self.gc.images, 'data') as mocked_data:
2360            mocked_data.side_effect = exc.HTTPInternalServerError
2361            try:
2362                test_shell.do_image_download(self.gc, args)
2363                self.fail('Exit not called')
2364            except SystemExit:
2365                pass
2366
2367            self.assertEqual(1, mocked_data.call_count)
2368            self.assertEqual(1, mocked_print_err.call_count)
2369
2370    def test_do_member_list(self):
2371        args = self._make_args({'image_id': 'IMG-01'})
2372        with mock.patch.object(self.gc.image_members, 'list') as mocked_list:
2373            mocked_list.return_value = {}
2374
2375            test_shell.do_member_list(self.gc, args)
2376
2377            mocked_list.assert_called_once_with('IMG-01')
2378            columns = ['Image ID', 'Member ID', 'Status']
2379            utils.print_list.assert_called_once_with({}, columns)
2380
2381    def test_do_member_get(self):
2382        args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'})
2383        with mock.patch.object(self.gc.image_members, 'get') as mock_get:
2384            mock_get.return_value = {}
2385
2386            test_shell.do_member_get(self.gc, args)
2387
2388            mock_get.assert_called_once_with('IMG-01', 'MEM-01')
2389            utils.print_dict.assert_called_once_with({})
2390
2391    def test_do_member_create(self):
2392        args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'})
2393        with mock.patch.object(self.gc.image_members, 'create') as mock_create:
2394            mock_create.return_value = {}
2395
2396            test_shell.do_member_create(self.gc, args)
2397
2398            mock_create.assert_called_once_with('IMG-01', 'MEM-01')
2399            columns = ['Image ID', 'Member ID', 'Status']
2400            utils.print_list.assert_called_once_with([{}], columns)
2401
2402    def test_do_member_create_with_few_arguments(self):
2403        args = self._make_args({'image_id': None, 'member_id': 'MEM-01'})
2404        msg = 'Unable to create member. Specify image_id and member_id'
2405
2406        self.assert_exits_with_msg(func=test_shell.do_member_create,
2407                                   func_args=args,
2408                                   err_msg=msg)
2409
2410    def test_do_member_update(self):
2411        input = {
2412            'image_id': 'IMG-01',
2413            'member_id': 'MEM-01',
2414            'member_status': 'status',
2415        }
2416        args = self._make_args(input)
2417        with mock.patch.object(self.gc.image_members, 'update') as mock_update:
2418            mock_update.return_value = {}
2419
2420            test_shell.do_member_update(self.gc, args)
2421
2422            mock_update.assert_called_once_with('IMG-01', 'MEM-01', 'status')
2423            columns = ['Image ID', 'Member ID', 'Status']
2424            utils.print_list.assert_called_once_with([{}], columns)
2425
2426    def test_do_member_update_with_few_arguments(self):
2427        input = {
2428            'image_id': 'IMG-01',
2429            'member_id': 'MEM-01',
2430            'member_status': None,
2431        }
2432        args = self._make_args(input)
2433        msg = 'Unable to update member. Specify image_id, member_id' \
2434              ' and member_status'
2435
2436        self.assert_exits_with_msg(func=test_shell.do_member_update,
2437                                   func_args=args,
2438                                   err_msg=msg)
2439
2440    def test_do_member_delete(self):
2441        args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'})
2442        with mock.patch.object(self.gc.image_members, 'delete') as mock_delete:
2443            test_shell.do_member_delete(self.gc, args)
2444
2445            mock_delete.assert_called_once_with('IMG-01', 'MEM-01')
2446
2447    def test_do_member_delete_with_few_arguments(self):
2448        args = self._make_args({'image_id': None, 'member_id': 'MEM-01'})
2449        msg = 'Unable to delete member. Specify image_id and member_id'
2450
2451        self.assert_exits_with_msg(func=test_shell.do_member_delete,
2452                                   func_args=args,
2453                                   err_msg=msg)
2454
2455    def test_image_tag_update(self):
2456        args = self._make_args({'image_id': 'IMG-01', 'tag_value': 'tag01'})
2457        with mock.patch.object(self.gc.image_tags, 'update') as mocked_update:
2458            self.gc.images.get = mock.Mock(return_value={})
2459            mocked_update.return_value = None
2460
2461            test_shell.do_image_tag_update(self.gc, args)
2462
2463            mocked_update.assert_called_once_with('IMG-01', 'tag01')
2464
2465    def test_image_tag_update_with_few_arguments(self):
2466        args = self._make_args({'image_id': None, 'tag_value': 'tag01'})
2467        msg = 'Unable to update tag. Specify image_id and tag_value'
2468
2469        self.assert_exits_with_msg(func=test_shell.do_image_tag_update,
2470                                   func_args=args,
2471                                   err_msg=msg)
2472
2473    def test_image_tag_delete(self):
2474        args = self._make_args({'image_id': 'IMG-01', 'tag_value': 'tag01'})
2475        with mock.patch.object(self.gc.image_tags, 'delete') as mocked_delete:
2476            mocked_delete.return_value = None
2477
2478            test_shell.do_image_tag_delete(self.gc, args)
2479
2480            mocked_delete.assert_called_once_with('IMG-01', 'tag01')
2481
2482    def test_image_tag_delete_with_few_arguments(self):
2483        args = self._make_args({'image_id': 'IMG-01', 'tag_value': None})
2484        msg = 'Unable to delete tag. Specify image_id and tag_value'
2485
2486        self.assert_exits_with_msg(func=test_shell.do_image_tag_delete,
2487                                   func_args=args,
2488                                   err_msg=msg)
2489
2490    def test_do_md_namespace_create(self):
2491        args = self._make_args({'namespace': 'MyNamespace',
2492                                'protected': True})
2493        with mock.patch.object(self.gc.metadefs_namespace,
2494                               'create') as mocked_create:
2495            expect_namespace = {
2496                'namespace': 'MyNamespace',
2497                'protected': True
2498            }
2499
2500            mocked_create.return_value = expect_namespace
2501
2502            test_shell.do_md_namespace_create(self.gc, args)
2503
2504            mocked_create.assert_called_once_with(namespace='MyNamespace',
2505                                                  protected=True)
2506            utils.print_dict.assert_called_once_with(expect_namespace)
2507
2508    def test_do_md_namespace_import(self):
2509        args = self._make_args({'file': 'test'})
2510
2511        expect_namespace = {
2512            'namespace': 'MyNamespace',
2513            'protected': True
2514        }
2515
2516        with mock.patch.object(self.gc.metadefs_namespace,
2517                               'create') as mocked_create:
2518            mock_read = mock.Mock(return_value=json.dumps(expect_namespace))
2519            mock_file = mock.Mock(read=mock_read)
2520            utils.get_data_file = mock.Mock(return_value=mock_file)
2521            mocked_create.return_value = expect_namespace
2522
2523            test_shell.do_md_namespace_import(self.gc, args)
2524
2525            mocked_create.assert_called_once_with(**expect_namespace)
2526            utils.print_dict.assert_called_once_with(expect_namespace)
2527
2528    def test_do_md_namespace_import_invalid_json(self):
2529        args = self._make_args({'file': 'test'})
2530        mock_read = mock.Mock(return_value='Invalid')
2531        mock_file = mock.Mock(read=mock_read)
2532        utils.get_data_file = mock.Mock(return_value=mock_file)
2533
2534        self.assertRaises(SystemExit, test_shell.do_md_namespace_import,
2535                          self.gc, args)
2536
2537    def test_do_md_namespace_import_no_input(self):
2538        args = self._make_args({'file': None})
2539        utils.get_data_file = mock.Mock(return_value=None)
2540
2541        self.assertRaises(SystemExit, test_shell.do_md_namespace_import,
2542                          self.gc, args)
2543
2544    def test_do_md_namespace_update(self):
2545        args = self._make_args({'id': 'MyNamespace',
2546                                'protected': True})
2547        with mock.patch.object(self.gc.metadefs_namespace,
2548                               'update') as mocked_update:
2549            expect_namespace = {
2550                'namespace': 'MyNamespace',
2551                'protected': True
2552            }
2553
2554            mocked_update.return_value = expect_namespace
2555
2556            test_shell.do_md_namespace_update(self.gc, args)
2557
2558            mocked_update.assert_called_once_with('MyNamespace',
2559                                                  id='MyNamespace',
2560                                                  protected=True)
2561            utils.print_dict.assert_called_once_with(expect_namespace)
2562
2563    def test_do_md_namespace_show(self):
2564        args = self._make_args({'namespace': 'MyNamespace',
2565                                'max_column_width': 80,
2566                                'resource_type': None})
2567        with mock.patch.object(self.gc.metadefs_namespace,
2568                               'get') as mocked_get:
2569            expect_namespace = {'namespace': 'MyNamespace'}
2570
2571            mocked_get.return_value = expect_namespace
2572
2573            test_shell.do_md_namespace_show(self.gc, args)
2574
2575            mocked_get.assert_called_once_with('MyNamespace')
2576            utils.print_dict.assert_called_once_with(expect_namespace, 80)
2577
2578    def test_do_md_namespace_show_resource_type(self):
2579        args = self._make_args({'namespace': 'MyNamespace',
2580                                'max_column_width': 80,
2581                                'resource_type': 'RESOURCE'})
2582        with mock.patch.object(self.gc.metadefs_namespace,
2583                               'get') as mocked_get:
2584            expect_namespace = {'namespace': 'MyNamespace'}
2585
2586            mocked_get.return_value = expect_namespace
2587
2588            test_shell.do_md_namespace_show(self.gc, args)
2589
2590            mocked_get.assert_called_once_with('MyNamespace',
2591                                               resource_type='RESOURCE')
2592            utils.print_dict.assert_called_once_with(expect_namespace, 80)
2593
2594    def test_do_md_namespace_list(self):
2595        args = self._make_args({'resource_type': None,
2596                                'visibility': None,
2597                                'page_size': None})
2598        with mock.patch.object(self.gc.metadefs_namespace,
2599                               'list') as mocked_list:
2600            expect_namespaces = [{'namespace': 'MyNamespace'}]
2601
2602            mocked_list.return_value = expect_namespaces
2603
2604            test_shell.do_md_namespace_list(self.gc, args)
2605
2606            mocked_list.assert_called_once_with(filters={})
2607            utils.print_list.assert_called_once_with(expect_namespaces,
2608                                                     ['namespace'])
2609
2610    def test_do_md_namespace_list_page_size(self):
2611        args = self._make_args({'resource_type': None,
2612                                'visibility': None,
2613                                'page_size': 2})
2614        with mock.patch.object(self.gc.metadefs_namespace,
2615                               'list') as mocked_list:
2616            expect_namespaces = [{'namespace': 'MyNamespace'}]
2617
2618            mocked_list.return_value = expect_namespaces
2619
2620            test_shell.do_md_namespace_list(self.gc, args)
2621
2622            mocked_list.assert_called_once_with(filters={}, page_size=2)
2623            utils.print_list.assert_called_once_with(expect_namespaces,
2624                                                     ['namespace'])
2625
2626    def test_do_md_namespace_list_one_filter(self):
2627        args = self._make_args({'resource_types': ['OS::Compute::Aggregate'],
2628                                'visibility': None,
2629                                'page_size': None})
2630        with mock.patch.object(self.gc.metadefs_namespace, 'list') as \
2631                mocked_list:
2632            expect_namespaces = [{'namespace': 'MyNamespace'}]
2633
2634            mocked_list.return_value = expect_namespaces
2635
2636            test_shell.do_md_namespace_list(self.gc, args)
2637
2638            mocked_list.assert_called_once_with(filters={
2639                'resource_types': ['OS::Compute::Aggregate']})
2640            utils.print_list.assert_called_once_with(expect_namespaces,
2641                                                     ['namespace'])
2642
2643    def test_do_md_namespace_list_all_filters(self):
2644        args = self._make_args({'resource_types': ['OS::Compute::Aggregate'],
2645                                'visibility': 'public',
2646                                'page_size': None})
2647        with mock.patch.object(self.gc.metadefs_namespace,
2648                               'list') as mocked_list:
2649            expect_namespaces = [{'namespace': 'MyNamespace'}]
2650
2651            mocked_list.return_value = expect_namespaces
2652
2653            test_shell.do_md_namespace_list(self.gc, args)
2654
2655            mocked_list.assert_called_once_with(filters={
2656                'resource_types': ['OS::Compute::Aggregate'],
2657                'visibility': 'public'})
2658            utils.print_list.assert_called_once_with(expect_namespaces,
2659                                                     ['namespace'])
2660
2661    def test_do_md_namespace_list_unknown_filter(self):
2662        args = self._make_args({'resource_type': None,
2663                                'visibility': None,
2664                                'some_arg': 'some_value',
2665                                'page_size': None})
2666        with mock.patch.object(self.gc.metadefs_namespace,
2667                               'list') as mocked_list:
2668            expect_namespaces = [{'namespace': 'MyNamespace'}]
2669
2670            mocked_list.return_value = expect_namespaces
2671
2672            test_shell.do_md_namespace_list(self.gc, args)
2673
2674            mocked_list.assert_called_once_with(filters={})
2675            utils.print_list.assert_called_once_with(expect_namespaces,
2676                                                     ['namespace'])
2677
2678    def test_do_md_namespace_delete(self):
2679        args = self._make_args({'namespace': 'MyNamespace',
2680                                'content': False})
2681        with mock.patch.object(self.gc.metadefs_namespace, 'delete') as \
2682                mocked_delete:
2683            test_shell.do_md_namespace_delete(self.gc, args)
2684
2685            mocked_delete.assert_called_once_with('MyNamespace')
2686
2687    def test_do_md_resource_type_associate(self):
2688        args = self._make_args({'namespace': 'MyNamespace',
2689                                'name': 'MyResourceType',
2690                                'prefix': 'PREFIX:'})
2691        with mock.patch.object(self.gc.metadefs_resource_type,
2692                               'associate') as mocked_associate:
2693            expect_rt = {
2694                'namespace': 'MyNamespace',
2695                'name': 'MyResourceType',
2696                'prefix': 'PREFIX:'
2697            }
2698
2699            mocked_associate.return_value = expect_rt
2700
2701            test_shell.do_md_resource_type_associate(self.gc, args)
2702
2703            mocked_associate.assert_called_once_with('MyNamespace',
2704                                                     **expect_rt)
2705            utils.print_dict.assert_called_once_with(expect_rt)
2706
2707    def test_do_md_resource_type_deassociate(self):
2708        args = self._make_args({'namespace': 'MyNamespace',
2709                                'resource_type': 'MyResourceType'})
2710        with mock.patch.object(self.gc.metadefs_resource_type,
2711                               'deassociate') as mocked_deassociate:
2712            test_shell.do_md_resource_type_deassociate(self.gc, args)
2713
2714            mocked_deassociate.assert_called_once_with('MyNamespace',
2715                                                       'MyResourceType')
2716
2717    def test_do_md_resource_type_list(self):
2718        args = self._make_args({})
2719        with mock.patch.object(self.gc.metadefs_resource_type,
2720                               'list') as mocked_list:
2721            expect_objects = ['MyResourceType1', 'MyResourceType2']
2722
2723            mocked_list.return_value = expect_objects
2724
2725            test_shell.do_md_resource_type_list(self.gc, args)
2726
2727            self.assertEqual(1, mocked_list.call_count)
2728
2729    def test_do_md_namespace_resource_type_list(self):
2730        args = self._make_args({'namespace': 'MyNamespace'})
2731        with mock.patch.object(self.gc.metadefs_resource_type,
2732                               'get') as mocked_get:
2733            expect_objects = [{'namespace': 'MyNamespace',
2734                               'object': 'MyObject'}]
2735
2736            mocked_get.return_value = expect_objects
2737
2738            test_shell.do_md_namespace_resource_type_list(self.gc, args)
2739
2740            mocked_get.assert_called_once_with('MyNamespace')
2741            utils.print_list.assert_called_once_with(expect_objects,
2742                                                     ['name', 'prefix',
2743                                                      'properties_target'])
2744
2745    def test_do_md_property_create(self):
2746        args = self._make_args({'namespace': 'MyNamespace',
2747                                'name': "MyProperty",
2748                                'title': "Title",
2749                                'schema': '{}'})
2750        with mock.patch.object(self.gc.metadefs_property,
2751                               'create') as mocked_create:
2752            expect_property = {
2753                'namespace': 'MyNamespace',
2754                'name': 'MyProperty',
2755                'title': 'Title'
2756            }
2757
2758            mocked_create.return_value = expect_property
2759
2760            test_shell.do_md_property_create(self.gc, args)
2761
2762            mocked_create.assert_called_once_with('MyNamespace',
2763                                                  name='MyProperty',
2764                                                  title='Title')
2765            utils.print_dict.assert_called_once_with(expect_property)
2766
2767    def test_do_md_property_create_invalid_schema(self):
2768        args = self._make_args({'namespace': 'MyNamespace',
2769                                'name': "MyProperty",
2770                                'title': "Title",
2771                                'schema': 'Invalid'})
2772        self.assertRaises(SystemExit, test_shell.do_md_property_create,
2773                          self.gc, args)
2774
2775    def test_do_md_property_update(self):
2776        args = self._make_args({'namespace': 'MyNamespace',
2777                                'property': 'MyProperty',
2778                                'name': 'NewName',
2779                                'title': "Title",
2780                                'schema': '{}'})
2781        with mock.patch.object(self.gc.metadefs_property,
2782                               'update') as mocked_update:
2783            expect_property = {
2784                'namespace': 'MyNamespace',
2785                'name': 'MyProperty',
2786                'title': 'Title'
2787            }
2788
2789            mocked_update.return_value = expect_property
2790
2791            test_shell.do_md_property_update(self.gc, args)
2792
2793            mocked_update.assert_called_once_with('MyNamespace', 'MyProperty',
2794                                                  name='NewName',
2795                                                  title='Title')
2796            utils.print_dict.assert_called_once_with(expect_property)
2797
2798    def test_do_md_property_update_invalid_schema(self):
2799        args = self._make_args({'namespace': 'MyNamespace',
2800                                'property': 'MyProperty',
2801                                'name': "MyObject",
2802                                'title': "Title",
2803                                'schema': 'Invalid'})
2804        self.assertRaises(SystemExit, test_shell.do_md_property_update,
2805                          self.gc, args)
2806
2807    def test_do_md_property_show(self):
2808        args = self._make_args({'namespace': 'MyNamespace',
2809                                'property': 'MyProperty',
2810                                'max_column_width': 80})
2811        with mock.patch.object(self.gc.metadefs_property, 'get') as mocked_get:
2812            expect_property = {
2813                'namespace': 'MyNamespace',
2814                'property': 'MyProperty',
2815                'title': 'Title'
2816            }
2817
2818            mocked_get.return_value = expect_property
2819
2820            test_shell.do_md_property_show(self.gc, args)
2821
2822            mocked_get.assert_called_once_with('MyNamespace', 'MyProperty')
2823            utils.print_dict.assert_called_once_with(expect_property, 80)
2824
2825    def test_do_md_property_delete(self):
2826        args = self._make_args({'namespace': 'MyNamespace',
2827                                'property': 'MyProperty'})
2828        with mock.patch.object(self.gc.metadefs_property,
2829                               'delete') as mocked_delete:
2830            test_shell.do_md_property_delete(self.gc, args)
2831
2832            mocked_delete.assert_called_once_with('MyNamespace', 'MyProperty')
2833
2834    def test_do_md_namespace_property_delete(self):
2835        args = self._make_args({'namespace': 'MyNamespace'})
2836        with mock.patch.object(self.gc.metadefs_property,
2837                               'delete_all') as mocked_delete_all:
2838            test_shell.do_md_namespace_properties_delete(self.gc, args)
2839
2840            mocked_delete_all.assert_called_once_with('MyNamespace')
2841
2842    def test_do_md_property_list(self):
2843        args = self._make_args({'namespace': 'MyNamespace'})
2844        with mock.patch.object(self.gc.metadefs_property,
2845                               'list') as mocked_list:
2846            expect_objects = [{'namespace': 'MyNamespace',
2847                               'property': 'MyProperty',
2848                               'title': 'MyTitle'}]
2849
2850            mocked_list.return_value = expect_objects
2851
2852            test_shell.do_md_property_list(self.gc, args)
2853
2854            mocked_list.assert_called_once_with('MyNamespace')
2855            utils.print_list.assert_called_once_with(expect_objects,
2856                                                     ['name', 'title', 'type'])
2857
2858    def test_do_md_object_create(self):
2859        args = self._make_args({'namespace': 'MyNamespace',
2860                                'name': "MyObject",
2861                                'schema': '{}'})
2862        with mock.patch.object(self.gc.metadefs_object,
2863                               'create') as mocked_create:
2864            expect_object = {
2865                'namespace': 'MyNamespace',
2866                'name': 'MyObject'
2867            }
2868
2869            mocked_create.return_value = expect_object
2870
2871            test_shell.do_md_object_create(self.gc, args)
2872
2873            mocked_create.assert_called_once_with('MyNamespace',
2874                                                  name='MyObject')
2875            utils.print_dict.assert_called_once_with(expect_object)
2876
2877    def test_do_md_object_create_invalid_schema(self):
2878        args = self._make_args({'namespace': 'MyNamespace',
2879                                'name': "MyObject",
2880                                'schema': 'Invalid'})
2881        self.assertRaises(SystemExit, test_shell.do_md_object_create,
2882                          self.gc, args)
2883
2884    def test_do_md_object_update(self):
2885        args = self._make_args({'namespace': 'MyNamespace',
2886                                'object': 'MyObject',
2887                                'name': 'NewName',
2888                                'schema': '{}'})
2889        with mock.patch.object(self.gc.metadefs_object,
2890                               'update') as mocked_update:
2891            expect_object = {
2892                'namespace': 'MyNamespace',
2893                'name': 'MyObject'
2894            }
2895
2896            mocked_update.return_value = expect_object
2897
2898            test_shell.do_md_object_update(self.gc, args)
2899
2900            mocked_update.assert_called_once_with('MyNamespace', 'MyObject',
2901                                                  name='NewName')
2902            utils.print_dict.assert_called_once_with(expect_object)
2903
2904    def test_do_md_object_update_invalid_schema(self):
2905        args = self._make_args({'namespace': 'MyNamespace',
2906                                'object': 'MyObject',
2907                                'name': "MyObject",
2908                                'schema': 'Invalid'})
2909        self.assertRaises(SystemExit, test_shell.do_md_object_update,
2910                          self.gc, args)
2911
2912    def test_do_md_object_show(self):
2913        args = self._make_args({'namespace': 'MyNamespace',
2914                                'object': 'MyObject',
2915                                'max_column_width': 80})
2916        with mock.patch.object(self.gc.metadefs_object, 'get') as mocked_get:
2917            expect_object = {
2918                'namespace': 'MyNamespace',
2919                'object': 'MyObject'
2920            }
2921
2922            mocked_get.return_value = expect_object
2923
2924            test_shell.do_md_object_show(self.gc, args)
2925
2926            mocked_get.assert_called_once_with('MyNamespace', 'MyObject')
2927            utils.print_dict.assert_called_once_with(expect_object, 80)
2928
2929    def test_do_md_object_property_show(self):
2930        args = self._make_args({'namespace': 'MyNamespace',
2931                                'object': 'MyObject',
2932                                'property': 'MyProperty',
2933                                'max_column_width': 80})
2934        with mock.patch.object(self.gc.metadefs_object, 'get') as mocked_get:
2935            expect_object = {'name': 'MyObject',
2936                             'properties': {
2937                                 'MyProperty': {'type': 'string'}
2938                             }}
2939
2940            mocked_get.return_value = expect_object
2941
2942            test_shell.do_md_object_property_show(self.gc, args)
2943
2944            mocked_get.assert_called_once_with('MyNamespace', 'MyObject')
2945            utils.print_dict.assert_called_once_with({'type': 'string',
2946                                                      'name': 'MyProperty'},
2947                                                     80)
2948
2949    def test_do_md_object_property_show_non_existing(self):
2950        args = self._make_args({'namespace': 'MyNamespace',
2951                                'object': 'MyObject',
2952                                'property': 'MyProperty',
2953                                'max_column_width': 80})
2954        with mock.patch.object(self.gc.metadefs_object, 'get') as mocked_get:
2955            expect_object = {'name': 'MyObject', 'properties': {}}
2956            mocked_get.return_value = expect_object
2957
2958            self.assertRaises(SystemExit,
2959                              test_shell.do_md_object_property_show,
2960                              self.gc, args)
2961            mocked_get.assert_called_once_with('MyNamespace', 'MyObject')
2962
2963    def test_do_md_object_delete(self):
2964        args = self._make_args({'namespace': 'MyNamespace',
2965                                'object': 'MyObject'})
2966        with mock.patch.object(self.gc.metadefs_object,
2967                               'delete') as mocked_delete:
2968            test_shell.do_md_object_delete(self.gc, args)
2969
2970            mocked_delete.assert_called_once_with('MyNamespace', 'MyObject')
2971
2972    def test_do_md_namespace_objects_delete(self):
2973        args = self._make_args({'namespace': 'MyNamespace'})
2974        with mock.patch.object(self.gc.metadefs_object,
2975                               'delete_all') as mocked_delete_all:
2976            test_shell.do_md_namespace_objects_delete(self.gc, args)
2977
2978            mocked_delete_all.assert_called_once_with('MyNamespace')
2979
2980    def test_do_md_object_list(self):
2981        args = self._make_args({'namespace': 'MyNamespace'})
2982        with mock.patch.object(self.gc.metadefs_object, 'list') as mocked_list:
2983            expect_objects = [{'namespace': 'MyNamespace',
2984                               'object': 'MyObject'}]
2985
2986            mocked_list.return_value = expect_objects
2987
2988            test_shell.do_md_object_list(self.gc, args)
2989
2990            mocked_list.assert_called_once_with('MyNamespace')
2991            utils.print_list.assert_called_once_with(
2992                expect_objects,
2993                ['name', 'description'],
2994                field_settings={
2995                    'description': {'align': 'l', 'max_width': 50}})
2996
2997    def test_do_md_tag_create(self):
2998        args = self._make_args({'namespace': 'MyNamespace',
2999                                'name': 'MyTag'})
3000        with mock.patch.object(self.gc.metadefs_tag,
3001                               'create') as mocked_create:
3002            expect_tag = {
3003                'namespace': 'MyNamespace',
3004                'name': 'MyTag'
3005            }
3006
3007            mocked_create.return_value = expect_tag
3008
3009            test_shell.do_md_tag_create(self.gc, args)
3010
3011            mocked_create.assert_called_once_with('MyNamespace', 'MyTag')
3012            utils.print_dict.assert_called_once_with(expect_tag)
3013
3014    def test_do_md_tag_update(self):
3015        args = self._make_args({'namespace': 'MyNamespace',
3016                                'tag': 'MyTag',
3017                                'name': 'NewTag'})
3018        with mock.patch.object(self.gc.metadefs_tag,
3019                               'update') as mocked_update:
3020            expect_tag = {
3021                'namespace': 'MyNamespace',
3022                'name': 'NewTag'
3023            }
3024
3025            mocked_update.return_value = expect_tag
3026
3027            test_shell.do_md_tag_update(self.gc, args)
3028
3029            mocked_update.assert_called_once_with('MyNamespace', 'MyTag',
3030                                                  name='NewTag')
3031            utils.print_dict.assert_called_once_with(expect_tag)
3032
3033    def test_do_md_tag_show(self):
3034        args = self._make_args({'namespace': 'MyNamespace',
3035                                'tag': 'MyTag',
3036                                'sort_dir': 'desc'})
3037        with mock.patch.object(self.gc.metadefs_tag, 'get') as mocked_get:
3038            expect_tag = {
3039                'namespace': 'MyNamespace',
3040                'tag': 'MyTag'
3041            }
3042
3043            mocked_get.return_value = expect_tag
3044
3045            test_shell.do_md_tag_show(self.gc, args)
3046
3047            mocked_get.assert_called_once_with('MyNamespace', 'MyTag')
3048            utils.print_dict.assert_called_once_with(expect_tag)
3049
3050    def test_do_md_tag_delete(self):
3051        args = self._make_args({'namespace': 'MyNamespace',
3052                                'tag': 'MyTag'})
3053        with mock.patch.object(self.gc.metadefs_tag,
3054                               'delete') as mocked_delete:
3055            test_shell.do_md_tag_delete(self.gc, args)
3056
3057            mocked_delete.assert_called_once_with('MyNamespace', 'MyTag')
3058
3059    def test_do_md_namespace_tags_delete(self):
3060        args = self._make_args({'namespace': 'MyNamespace'})
3061        with mock.patch.object(self.gc.metadefs_tag,
3062                               'delete_all') as mocked_delete_all:
3063            test_shell.do_md_namespace_tags_delete(self.gc, args)
3064
3065            mocked_delete_all.assert_called_once_with('MyNamespace')
3066
3067    def test_do_md_tag_list(self):
3068        args = self._make_args({'namespace': 'MyNamespace'})
3069        with mock.patch.object(self.gc.metadefs_tag, 'list') as mocked_list:
3070            expect_tags = [{'namespace': 'MyNamespace',
3071                            'tag': 'MyTag'}]
3072
3073            mocked_list.return_value = expect_tags
3074
3075            test_shell.do_md_tag_list(self.gc, args)
3076
3077            mocked_list.assert_called_once_with('MyNamespace')
3078            utils.print_list.assert_called_once_with(
3079                expect_tags,
3080                ['name'],
3081                field_settings={
3082                    'description': {'align': 'l', 'max_width': 50}})
3083
3084    def test_do_md_tag_create_multiple(self):
3085        args = self._make_args({'namespace': 'MyNamespace',
3086                                'delim': ',',
3087                                'names': 'MyTag1, MyTag2'})
3088        with mock.patch.object(
3089                self.gc.metadefs_tag, 'create_multiple') as mocked_create_tags:
3090            expect_tags = [{'tags': [{'name': 'MyTag1'}, {'name': 'MyTag2'}]}]
3091
3092            mocked_create_tags.return_value = expect_tags
3093
3094            test_shell.do_md_tag_create_multiple(self.gc, args)
3095
3096            mocked_create_tags.assert_called_once_with(
3097                'MyNamespace', tags=['MyTag1', 'MyTag2'])
3098            utils.print_list.assert_called_once_with(
3099                expect_tags,
3100                ['name'],
3101                field_settings={
3102                    'description': {'align': 'l', 'max_width': 50}})
3103