Merge ~freyes/ubuntu/+source/python-magnumclient:stable/ussuri into ~ubuntu-openstack-dev/ubuntu/+source/python-magnumclient:stable/ussuri

Proposed by Felipe Reyes
Status: Merged
Merged at revision: aa914fe1e7e667083e2f0e13ac977d7814de789a
Proposed branch: ~freyes/ubuntu/+source/python-magnumclient:stable/ussuri
Merge into: ~ubuntu-openstack-dev/ubuntu/+source/python-magnumclient:stable/ussuri
Diff against target: 4474 lines (+2850/-341)
56 files modified
.zuul.yaml (+3/-6)
AUTHORS (+143/-0)
CONTRIBUTING.rst (+1/-1)
ChangeLog (+624/-0)
PKG-INFO (+61/-0)
README.rst (+6/-6)
debian/changelog (+11/-0)
debian/patches/series (+0/-2)
debian/watch (+2/-2)
dev/null (+0/-79)
doc/requirements.txt (+2/-1)
magnumclient/common/cliutils.py (+2/-2)
magnumclient/common/httpclient.py (+11/-8)
magnumclient/common/utils.py (+69/-35)
magnumclient/exceptions.py (+8/-8)
magnumclient/osc/plugin.py (+6/-4)
magnumclient/osc/v1/cluster_templates.py (+21/-1)
magnumclient/osc/v1/clusters.py (+170/-10)
magnumclient/osc/v1/nodegroups.py (+301/-0)
magnumclient/shell.py (+7/-6)
magnumclient/tests/osc/unit/osc_fakes.py (+2/-5)
magnumclient/tests/osc/unit/v1/fakes.py (+86/-2)
magnumclient/tests/osc/unit/v1/test_cluster_templates.py (+6/-4)
magnumclient/tests/osc/unit/v1/test_clusters.py (+69/-9)
magnumclient/tests/osc/unit/v1/test_nodegroups.py (+333/-0)
magnumclient/tests/test_httpclient.py (+4/-5)
magnumclient/tests/test_utils.py (+20/-20)
magnumclient/tests/utils.py (+3/-3)
magnumclient/tests/v1/shell_test_base.py (+29/-29)
magnumclient/tests/v1/test_bays_shell.py (+0/-9)
magnumclient/tests/v1/test_clusters.py (+43/-0)
magnumclient/tests/v1/test_clusters_shell.py (+0/-11)
magnumclient/tests/v1/test_clustertemplates.py (+6/-1)
magnumclient/tests/v1/test_clustertemplates_shell.py (+3/-1)
magnumclient/tests/v1/test_nodegroups.py (+333/-0)
magnumclient/v1/basemodels.py (+1/-1)
magnumclient/v1/bays_shell.py (+2/-2)
magnumclient/v1/client.py (+2/-0)
magnumclient/v1/cluster_templates_shell.py (+8/-0)
magnumclient/v1/clusters.py (+34/-0)
magnumclient/v1/clusters_shell.py (+2/-3)
magnumclient/v1/nodegroups.py (+84/-0)
python_magnumclient.egg-info/PKG-INFO (+61/-0)
python_magnumclient.egg-info/SOURCES.txt (+128/-0)
python_magnumclient.egg-info/dependency_links.txt (+1/-0)
python_magnumclient.egg-info/entry_points.txt (+36/-0)
python_magnumclient.egg-info/not-zip-safe (+1/-0)
python_magnumclient.egg-info/pbr.json (+1/-0)
python_magnumclient.egg-info/requires.txt (+15/-0)
python_magnumclient.egg-info/top_level.txt (+1/-0)
releasenotes/source/index.rst (+2/-0)
releasenotes/source/stein.rst (+6/-0)
releasenotes/source/train.rst (+6/-0)
setup.cfg (+57/-47)
test-requirements.txt (+2/-2)
tox.ini (+15/-16)
Reviewer Review Type Date Requested Status
Ubuntu OpenStack uploaders Pending
Review via email: mp+442774@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2deleted file mode 100644
3index 17ed160..0000000
4--- a/.gitignore
5+++ /dev/null
6@@ -1,54 +0,0 @@
7-*.py[cod]
8-
9-# C extensions
10-*.so
11-
12-# Packages
13-*.egg*
14-dist
15-build
16-eggs
17-parts
18-bin
19-var
20-sdist
21-develop-eggs
22-.installed.cfg
23-lib
24-lib64
25-
26-# Installer logs
27-pip-log.txt
28-
29-# Unit test / coverage reports
30-.coverage
31-.tox
32-cover
33-cover-master
34-.stestr/
35-.venv
36-
37-# Translations
38-*.mo
39-
40-# Mr Developer
41-.mr.developer.cfg
42-.project
43-.pydevproject
44-.idea
45-
46-# Complexity
47-output/*.html
48-output/*/index.html
49-
50-# Sphinx
51-doc/build
52-
53-# pbr generates these
54-AUTHORS
55-ChangeLog
56-
57-# Editors
58-*~
59-.*.swp
60-*.DS_Store
61diff --git a/.gitreview b/.gitreview
62deleted file mode 100644
63index 2616d47..0000000
64--- a/.gitreview
65+++ /dev/null
66@@ -1,4 +0,0 @@
67-[gerrit]
68-host=review.openstack.org
69-port=29418
70-project=openstack/python-magnumclient.git
71diff --git a/.zuul.yaml b/.zuul.yaml
72index 241ca5d..c946e34 100644
73--- a/.zuul.yaml
74+++ b/.zuul.yaml
75@@ -1,18 +1,15 @@
76 - project:
77 templates:
78- - openstack-python-jobs
79- - openstack-python35-jobs
80- - openstack-python36-jobs
81 - check-requirements
82+ - openstack-cover-jobs
83+ - openstack-lower-constraints-jobs
84+ - openstack-python3-ussuri-jobs
85 - publish-openstack-docs-pti
86 check:
87 jobs:
88- - openstack-tox-lower-constraints
89 - build-openstack-releasenotes
90 - openstack-tox-cover:
91 voting: false
92-
93 gate:
94 jobs:
95- - openstack-tox-lower-constraints
96 - build-openstack-releasenotes
97diff --git a/AUTHORS b/AUTHORS
98new file mode 100644
99index 0000000..add3f74
100--- /dev/null
101+++ b/AUTHORS
102@@ -0,0 +1,143 @@
103+Aaron-DH <dinghh@awcloud.com>
104+Abhishek Chanda <abhishek.becs@gmail.com>
105+Abhishek Chanda <abhishek@cloudscaling.com>
106+Adrian Otto <adrian.otto@rackspace.com>
107+Akhila Kishore <akhila.kishore@intel.com>
108+Amey Bhide <abhide@vmware.com>
109+Andreas Jaeger <aj@suse.com>
110+Andreas Jaeger <aj@suse.de>
111+Andrew Melton <andrew.melton@rackspace.com>
112+Anh Tran <anhtt@vn.fujitsu.com>
113+Bharat Kunwar <b.kunwar@gmail.com>
114+Bharat Kunwar <bharat@stackhpc.com>
115+Bharath Thiruveedula <bharath_ves@hotmail.com>
116+Boris Pilka <boris.pilka@x-works.io>
117+Cedric Brandily <zzelle@gmail.com>
118+Christoph Jansen <jansen.christoph@yahoo.de>
119+Clenimar Filemon <clenimar.filemon@gmail.com>
120+Corey Bryant <corey.bryant@canonical.com>
121+Corey O'Brien <corey.obrien@rackspace.com>
122+Costin GamenČ› <costin@cern.ch>
123+Dai Dang Van <daidv@vn.fujitsu.com>
124+Dane LeBlanc <leblancd@cisco.com>
125+Daneyon Hansen <danehans@cisco.com>
126+Daniel Abad <d.abad@cern.ch>
127+Davanum Srinivas <davanum@gmail.com>
128+Davanum Srinivas <dims@linux.vnet.ibm.com>
129+David Liu <david.liu@cn.ibm.com>
130+David Rabel <rabel@b1-systems.de>
131+Doug Hellmann <doug@doughellmann.com>
132+Eli Qiao <liyong.qiao@intel.com>
133+Eric Brown <browne@vmware.com>
134+Erik Olof Gunnar Andersson <eandersson@blizzard.com>
135+Fang Fenghua <449171342@qq.com>
136+Fang fenghua <449171342@qq.com>
137+Feilong Wang <flwang@catalyst.net.nz>
138+Feng Shengqin <feng.shengqin@zte.com.cn>
139+Flavio Percoco <flaper87@gmail.com>
140+Ghanshyam Mann <gmann@ghanshyammann.com>
141+Haiwei Xu <xu-haiwei@mxw.nes.nec.co.jp>
142+Hangdong Zhang <hdzhang@fiberhome.com>
143+Hieu LE <hieulq@vn.fujitsu.com>
144+Hongbin Lu <hongbin.lu@huawei.com>
145+Hongbin Lu <hongbin034@gmail.com>
146+Hongbn Lu <hongbin.lu@huawei.com>
147+Hua Wang <wanghua.humble@gmail.com>
148+Ian Cordasco <graffatcolmingov@gmail.com>
149+Jake Yip <jake.yip@unimelb.edu.au>
150+James Page <james.page@ubuntu.com>
151+Janek Lehr <jjlehr@us.ibm.com>
152+Janonymous <janonymous.codevulture@gmail.com>
153+Jason Dunsmore <jasondunsmore@gmail.com>
154+Jay Lau (Guangya Liu) <liugya@cn.ibm.com>
155+Jay Lau <liugya@cn.ibm.com>
156+Jennifer Carlucci <joffter@us.ibm.com>
157+Jeremy Stanley <fungi@yuggoth.org>
158+Johannes Grassler <johannes.grassler@suse.com>
159+Kennan <wkq5325@gmail.com>
160+Kennan <wkqwu@cn.ibm.com>
161+Kiran_totad <kiran.totad@nectechnologies.in>
162+Lan Qi song <lqslan@cn.ibm.com>
163+Lin Yang <lin.a.yang@intel.com>
164+Lucky samadhiya <lucky.samadhiya@nectechnologies.in>
165+M V P Nitesh <m.nitesh@nectechnologies.in>
166+Madhuri <madhuri.kumari@intel.com>
167+Madhuri Kumari <madhuri.kumari@intel.com>
168+Madhuri Kumari <madhuri.kumari@nectechnologies.in>
169+Madhuri Kumari <madhuri.rai07@gmail.com>
170+Manjeet Singh Bhatia <manjeet.s.bhatia@intel.com>
171+Mark Goddard <mark@stackhpc.com>
172+Michael Lekkas <lekkasmi@uk.ibm.com>
173+Michal Arbet <michal.arbet@ultimum.io>
174+Mike Fedosin <mfedosin@gmail.com>
175+Monty Taylor <mordred@inaugust.com>
176+Motohiro OTSUKA <ootsuka@mxs.nes.nec.co.jp>
177+Murali Allada <murali.allada@rackspace.com>
178+Namrata <sitlani.namrata@yahoo.in>
179+Nguyen Hai <nguyentrihai93@gmail.com>
180+Niall Bunting <niall.bunting@hpe.com>
181+OTSUKA, Yuanying <ootsuka@mxs.nes.nec.co.jp>
182+OTSUKA, Yuanying <yuanying@fraction.jp>
183+OpenStack Release Bot <infra-root@openstack.org>
184+PanFengyun <fengyun.pan@easystack.cn>
185+Pavlo Shchelokovskyy <shchelokovskyy@gmail.com>
186+Rajiv Kumar <rajiv.kumar@nectechnologies.in>
187+Ricardo Rocha <rocha.porto@gmail.com>
188+Sergey Vilgelm <sergey@vilgelm.info>
189+Spyros Trigazis <spyridon.trigazis@cern.ch>
190+Spyros Trigazis <strigazi@gmail.com>
191+Stephen Watson <stephen.watson@intel.com>
192+Steven Dake <sdake@redhat.com>
193+Steven Dake <stdake@cisco.com>
194+Surojit Pathak <suro@yahoo-inc.com>
195+Swapnil Kulkarni (coolsvap) <me@coolsvap.net>
196+Theodoros Tsioutsias <theodoros.tsioutsias@cern.ch>
197+Tom Cammann <tom.cammann@hp.com>
198+Tom Cammann <tom.cammann@hpe.com>
199+Tovin Seven <vinhnt@vn.fujitsu.com>
200+Vieri <15050873171@163.com>
201+Vijendar Komalla <vijendar.komalla@RACKSPACE.COM>
202+Vikas Choudhary <choudharyvikas16@gmail.com>
203+Vilobh Meshram <vilobhmm@yahoo-inc.com>
204+Vipul Nayyar <nayyar_vipul@yahoo.com>
205+Vivek Jain <vivek.jain.openstack@gmail.com>
206+Vu Cong Tuan <tuanvc@vn.fujitsu.com>
207+Wanlong Gao <wanlong.gao@easystack.cn>
208+Wenzhi Yu <wenzhi_yu@163.com>
209+XiaojueGuan <guanalbertjone@gmail.com>
210+Yang Hongyang <hongyang.yang@easystack.cn>
211+Yolanda Robla <yolanda.robla-mota@hp.com>
212+Zhenguo Niu <niuzhenguo@huawei.com>
213+chenaidong1 <chen.aidong@zte.com.cn>
214+chenlx <chenlx@fiberhome.com>
215+coldmoment <yan.zhiwei1@zte.com.cn>
216+coldmoment <ztehypervisor@zte.com.cn>
217+digambar <digambar_patil1@persistent.co.in>
218+digambar <digambarpatil15@yahoo.co.in>
219+gecong1973 <ge.cong@zte.com.cn>
220+guo yunxian <yunxian.guo@easystack.cn>
221+houming-wang <houming.wang@easystack.cn>
222+jacky06 <zhang.min@99cloud.net>
223+kavithahr <kavitha.r@nectechnologies.in>
224+maliki <imran.malik@emc.com>
225+melissaml <ma.lei@99cloud.net>
226+npraveen35 <npraveen35@gmail.com>
227+pawnesh.kumar <pawnesh.kumar@nectechnologies.in>
228+phelanm <phelanm@gmail.com>
229+qingszhao <zhao.daqing@99cloud.net>
230+rajat29 <rajat.sharma@nectechnologies.in>
231+ricolin <rico.l@inwinstack.com>
232+ricolin <rico.lin@easystack.cn>
233+shu-mutou <shu-mutou@rf.jp.nec.com>
234+space <fengzhr@awcloud.com>
235+sunjia <sunjia@inspur.com>
236+ting.wang <ting.wang@easystack.cn>
237+venkatamahesh <venkatamaheshkotha@gmail.com>
238+wangbo <wangbo_bupt@163.com>
239+wangqun <bjwqun@cn.ibm.com>
240+xiexs <xiexs@cn.fujitsu.com>
241+yanghuichan <yanghc@fiberhome.com>
242+yatin <yatin.karel@nectechnologies.in>
243+yatin <ykarel@redhat.com>
244+yatin karel <yatin.karel@nectechnologies.in>
245+yatinkarel <yatin.karel@nectechnologies.in>
246diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
247index c82011a..771dc9f 100644
248--- a/CONTRIBUTING.rst
249+++ b/CONTRIBUTING.rst
250@@ -13,4 +13,4 @@ Pull requests submitted through GitHub will be ignored.
251
252 Bugs should be filed on Launchpad, not GitHub:
253
254- https://bugs.launchpad.net/python-magnumclient
255\ No newline at end of file
256+ https://bugs.launchpad.net/python-magnumclient
257diff --git a/ChangeLog b/ChangeLog
258new file mode 100644
259index 0000000..6646705
260--- /dev/null
261+++ b/ChangeLog
262@@ -0,0 +1,624 @@
263+CHANGES
264+=======
265+
266+3.0.1
267+-----
268+
269+* Labels override
270+* Rename variables to address pep8 error
271+* Update TOX/UPPER\_CONSTRAINTS\_FILE for stable/ussuri
272+* Update .gitreview for stable/ussuri
273+
274+3.0.0
275+-----
276+
277+* Update master for stable/train
278+* Update hacking for Python3
279+* Drop py27 tests
280+* Bugfix: Use fields option for cluster template list
281+
282+2.17.0
283+------
284+
285+* Allow cluster config for any cluster state
286+* Add nodegroup CRUD commands
287+* Replace git.openstack.org URLs with opendev.org URLs
288+
289+2.15.0
290+------
291+
292+* Support network, subnet and FIP when creating cluster
293+
294+2.14.0
295+------
296+
297+* Add Python 3 Train unit tests
298+* Conditional hidden arg for backward compatibility
299+* Fix coverage test
300+* Blacklist bandit 1.6.0 and cap Sphinx on Python2
301+* Display project\_id for cluster show
302+* OpenDev Migration Patch
303+* Dropping the py35 testing
304+
305+2.13.0
306+------
307+
308+* Add nodegroup list/show commands
309+* Support upgrade API
310+* Support resize api
311+* Update master for stable/stein
312+* Support health\_status on client side
313+* python3 fixes
314+
315+2.12.0
316+------
317+
318+* Keystone auth support
319+* add python 3.7 unit test job
320+* Add hidden property to cluster template
321+* Fix py37 compatibility
322+* Use oslo\_serialization instead of the json module directly
323+* Use template for lower-constraints
324+* Change openstack-dev to openstack-discuss
325+* Add Python 3.6 classifier to setup.cfg
326+* add python 3.6 unit test job
327+* Trivial: Update pypi url to new url
328+
329+2.11.0
330+------
331+
332+* Fix crash on Service catalog empty 403 response
333+* add python 3.6 unit test job
334+* switch documentation job to new PTI
335+* import zuul job settings from project-config
336+* Add release notes for magnum client
337+* [k8s] Add embed certs to config
338+
339+2.10.0
340+------
341+
342+* Switch to stestr
343+* osc: Don't pass parameters with null value
344+* Update links in README
345+* fix tox python3 overrides
346+* Fix entrypoints for quotas
347+* Follow the new PTI for document build
348+* add lower-constraints job
349+* OSC command for magnum quota's
350+* Add deprecation warnings to magnum client commands
351+* Replace six.iteritems() with dict.items() in python-magnumclient
352+* OSC command for ca-show, ca-sign, ca-roatate and stats-list
353+* Now cluster-template-update works for "labels"
354+* Make cluster-config rbac compatible for kubebernetes
355+
356+2.8.0
357+-----
358+
359+* Add disable floating ip parameter
360+* Updated from global requirements
361+* Cleanup test-requirements
362+* Updated from global requirements
363+* Updated from global requirements
364+* Set --labels default to None on cluster create
365+* OSC: Add --flavor to coe cluster create
366+* Add missing master\_flavor\_id in cluster attributes
367+* Avoid tox\_install.sh for constraints support
368+* Updated from global requirements
369+* Replace six.iteritems() with .items()
370+* OSC: Add --master-flavor to coe cluster create
371+* Make cluster config --force a boolean
372+* Updated from global requirements
373+* Add --labels for cluster-create
374+* OSC: Add --labels to coe cluster create
375+* Add oslo.log as requirement
376+* OSC: Add magnum service-list command
377+* Now \`name\` is a positional argument in cluster creation
378+* OSC: Remove unused files
379+* inline comment typo fix
380+
381+2.7.0
382+-----
383+
384+* Remove log translations
385+* Updated from global requirements
386+* OSC: Add cluster config command
387+* OSC 4/4 Add remaining cluster commands
388+* OSC 3/4 Add remaining CT commands
389+* OSC 2/4 Add Cluster Create and List
390+* Updated from global requirements
391+* OSC 1/4 Add CT create and UT framework
392+* Remove docker-volume-size from cluster-list
393+* Updated from global requirements
394+* Make cluster name positional in ca-show
395+* remove slash from kubernetes cluster context name
396+* Update the documentation link for doc migration
397+* Updated from global requirements
398+* Updated from global requirements
399+* switch to openstackdocstheme
400+* Don't set a default for docker\_volume\_size
401+* Updated from global requirements
402+* Make --profile load from environment variables
403+* Add --docker-volume-size for cluster-create
404+* Updated from global requirements
405+* Change assertTrue(isinstance()) by optimal assert
406+
407+2.6.0
408+-----
409+
410+* Add the support for 'detail' flag
411+* Updated from global requirements
412+* Replace assertRaisesRegexp with assertRaisesRegex
413+* Updated from global requirements
414+* Updated from global requirements
415+* Magnum client suport insecure\_registry
416+* Use assertIsNone(...) instead of assertEqual(None, ...)
417+* Updated from global requirements
418+* Updated from global requirements
419+* Make --cluster option required for ca-rotate
420+* Move cover.sh to the tools directory
421+* Remove support for keyapir UUID
422+* Fix wrong path reference
423+* Update .gitignore to ignore .eggs
424+* Increase Test coverage
425+* The python 3.4 support is removed
426+* Correct mistake from OSprofiler help
427+* Fix UT for duplicate name args test case
428+* Updated from global requirements
429+* Add magnum client support for resource quotas
430+* Update test requirement
431+* Allow name as positional argument
432+* Print exception details on update failure
433+* Simplify magnumclient.shell.OpenStackMagnumShell.main()
434+* Compare test coverage with the master branch
435+* Updated from global requirements
436+
437+2.5.0
438+-----
439+
440+* Add usage docs for magnum client
441+* Updated from global requirements
442+* Increase UT Coverage
443+* Add the OSC 'cluster template list' command
444+* Add osc-lib to requirements.txt
445+* Add ca-rotate command to magnumclient
446+
447+2.4.0
448+-----
449+
450+* Adding stats-list command to magnum client
451+* Added link for modindex
452+* Increase UT Coverage
453+* Fix passing TLS\_VERIFIED in clusters/bays\_shell
454+* Increase UT Coverage
455+* Integrate OSprofiler in Magnum Client
456+* Remove H903 error in sources
457+* Updated from global requirements
458+* Fix: swarm cluster-config, bay-config with tls\_disabled
459+* Updated from global requirements
460+* Pass 'api\_version' to create HTTPClient
461+* Mark help messages for Translation
462+* Allow cluster-config on cluster status 'ROLLBACK\_COMPLETE'
463+* Add OSC command for cluster\_templates list
464+* Rollback cluster/bay on update failure
465+* Updated from global requirements
466+* Magnum cluster-config/bay-config compatible with py3
467+* Updated from global requirements
468+* Show team and repo badges on README
469+* Remove Not used Classes/Methods from apiclient.base
470+* Use assert\_called\_\*/assert\_not\_called to verify mock calls
471+* Fix: some typos in unit test
472+* Use assert\_called\_\*/assert\_not\_called to verify mock calls
473+* Renames \*-id parameters
474+* Implement Parameter Deprecation
475+* Use assert\_called\_\*/assert\_not\_called to verify mock calls
476+* Add a deprecation message to the bay\* commands' help text
477+* Updated from global requirements
478+* Increase UT coverage
479+* Remove invalid check for 'manifest' path
480+* Updated from global requirements
481+* Add .venv directory to .gitignore
482+* Enable DeprecationWarning in test environments
483+* Updated from global requirements
484+* Add \_\_ne\_\_ built-in function
485+* Add missing options for HTTPClient if auth\_token given
486+* Updated from global requirements
487+* Remove white space between print and ()
488+* Add --keypair-id for cluster-create
489+* Replace assertTrue(a in b) with assertIn(a, b)
490+* Updated from global requirements
491+* Fix the generated k8s config file
492+* cluster-config return absolute path
493+* delete python bytecode including pyo before every test run
494+* Fix a keyward arguement error on bay-config
495+* Add magnum.bash\_completion This can support the function of autocomplete for magnum's commands
496+* Cluster creation command returns complete cluster uuid
497+* Sync tools/tox\_install.sh
498+
499+2.3.0
500+-----
501+
502+* Updated from global requirements
503+* Magnum client to support sync and async bay opts
504+* Add microversioning support for httpclient
505+* Add floating\_ip\_enabled attributes to baymodel
506+* Cleanup coverage configuration
507+* Fixes bay\_uuid parameter issue in functional tests
508+* Fix to use HttpClient if token is given
509+* Updates certificate CLI to use cluster
510+* Updated from global requirements
511+* Adds 'cluster' and 'cluster template'
512+* Append value using comma if key exists in label
513+* bay-config return absolute path
514+* Updated from global requirements
515+* Use testr coverage feature
516+* Use upper constraints for all jobs in tox.ini
517+* Updated from global requirements
518+* Enabled magnum client to display detailed information
519+* Updated from global requirements
520+* Add Python 3.5 classifier and venv
521+* Base OSC plugin support
522+* Use os-client-config in shell
523+* Remove discover from test-requirements
524+* Increase unit test coverage for module baymodels
525+* Update the magnum client to send the latest version
526+* Updated from global requirements
527+* Add shell command bay-config
528+* Display baymodel info with bay-show command
529+
530+2.2.0
531+-----
532+
533+* Updated from global requirements
534+* Bay\_create\_timeout should be set to 60 default
535+* Prints '-' instead of 'None' when data is None
536+* Updated from global requirements
537+* Pass a flag to disable LB in baymodels
538+* Log appropriate error while exception
539+* Add fixed\_subnet attributes to baymodel
540+* Updated from global requirements
541+* Support OS\_PROJECT\_\* env variables
542+* Completely remove openstack common modules
543+* Updated from global requirements
544+* Add docker-storage-driver argument to baymodel
545+* Updated from global requirements
546+* Updated from global requirements
547+* Updated from global requirements
548+* Tox test should respect upper-constraints
549+* Switch to a new service type "container-infra"
550+* Add mesos\_slave\_executor\_environment\_variables validate in CLI
551+* Add some fields back to bay\_list
552+* Updated from global requirements
553+* Add '--fields' to show more columns for bay-list
554+* Update the home-page with developer documentation
555+* Correcting help messages of baymodel
556+* Add python3 to python classifiers
557+* Add '--fields' to show more columns for baymodel-list
558+* Corrected spacing mistake in baymodels\_shell.py
559+* Updated from global requirements
560+* Fix the bug when some value in labels has comma, it will fail
561+* Remove k8s pods, rcs, svc and container API calls
562+
563+2.1.0
564+-----
565+
566+* Updated from global requirements
567+* Add endpoint\_override parameter to python-magnumclient
568+* Updated from global requirements
569+* Revert "Parameter format change for ca-show and ca-sign"
570+* Updated from global requirements
571+* Parameter format change for ca-show and ca-sign
572+* Updated from global requirements
573+* Fix the container-list with --limit 'a negative number'
574+* Remove the update function of container
575+* Add missing user message
576+* Add marker/limit/sort-key/sort-dir features for bay-list
577+* Add marker/limit/sort-key/sort-dir features for container-list
578+* Allow semicolons in list of labels
579+* Add unit tests for MServiceManager.list() method
580+* Add unit tests for ContainerManager.list() method
581+* Add unit tests for BayModelManager.list() method
582+* Add unit tests for BayManager.list() method
583+* Adapt http response error message parsing
584+
585+2.0.0
586+-----
587+
588+* Fix incorrect initialization of OrderedDict
589+* Remove bandit.yaml in favor of defaults
590+* Revert "Completely remove openstack common modules"
591+* Use six.u instead of u''
592+* Completely remove openstack common modules
593+* Add param for magnum baymodel-list
594+* Move bandit into pep8
595+* Client : Create BayModel with server type(VM/BM)
596+* Updated from global requirements
597+* Sync with oslo-incubator
598+* Updated from global requirements
599+* Remove node object from magnumclient
600+* Exception not catched when bay create failed
601+* Fix a spell typo
602+* The rc-list should contain bay\_uuid
603+* Remove unused attribute "ssh\_authorized\_key"
604+* Update translation setup
605+* Fix incorrect help message
606+* Add Keystone v3 compatibility
607+* Updated from global requirements
608+* Adds Container Volume Model volume\_driver support
609+* Python 3.4 compatibility
610+* Add --insecure option
611+* Fix endpoint\_types
612+* Updated from global requirements
613+* Replace six.iteritems(dict) with dict.items()
614+* Change the endpoint env varible
615+* Prevent list rcs when bay is not ready
616+* raise exception when create rc with invalid bay status
617+* raise exception when create container with invalid bay status
618+* raise exception when create pod with invalid bay status
619+* Revert "Pass environment variables of proxy to tox"
620+* Add debug testenv in tox
621+* Remove check for bay state on ca-sign and ca-show
622+* Fix bypass\_url and errors with no headers
623+* Remove unnecessary check when create bay
624+* Fix test\_keys\_and\_vals\_to\_strs dict assert
625+* Add unit test for pod-create with invalid bay status
626+* Prevent list pods while bay is not ready
627+* Updated from global requirements
628+* Python 3 deprecated the logger.warn method in favor of warning
629+* Remove references to \_i18n and apiclient.exceptions
630+* Handle list object when print a dict
631+* Put py34 first in the env order of tox
632+* Updated from global requirements
633+* Packages missing from requirements.txt
634+* Add type validation and default for some parameter
635+* Add py3 compatibility for unicode builtin
636+* Add optional parameter --bay when doing container list
637+* Updated from global requirements
638+* Drop py33 support
639+* use wild card for passing env variables
640+* Add registry\_enabled option to baymodel-create
641+* Improve client master\_count validation
642+* Improve client node\_count validation
643+* Fix default bay create timeout
644+* Remove py26 support
645+* Fix POD CLI to work with Object from Bay
646+* Fix RC CLI to work with Object from Bay
647+* Add .idea directory to .gitignore
648+* Fix Service CLI to work with Object from Bay
649+* Handle faultstring when using SessionClient
650+* fix wrong function description
651+* fix exceptions.from\_response() parameter
652+* Fix the ouput of 'container-create' when it fails
653+* Make bandit included in test-requirements.txt
654+* Correct help message of '--name' for baymodel-create
655+* Remove test\_shell\_args.py
656+
657+1.1.0
658+-----
659+
660+* Revise help message of '--docker-volume-size' for baymodel-create
661+* Updated from global requirements
662+* Improve readme contents
663+* Split v1 shell sub-command into specific files
664+* Updated from global requirements
665+* Fix RC CLI to work with Object from Bay changes
666+* Update baymodel-update help doc
667+* Delete python bytecode before every test run
668+* Add .DS\_Store to .gitignore
669+* Updated from global requirements
670+* Use keystoneauth to create a Session
671+* Make image as required for do\_container\_create
672+* Add test for container\_create
673+* Add unversioned client constructor
674+* Fix rc cli to work with bay identifier
675+* Updated from global requirements
676+* Updated from global requirements
677+* Add the introduce of the command "magnum help ca-show"
678+* Add the introduce of the command "magnum help ca-sign"
679+
680+1.0.0.0b1
681+---------
682+
683+* Updated from global requirements
684+* Pass bay\_ident to k8s objects methods
685+* Add an option to specify container memory size
686+* Add support for 'baymodel-update' in python-client
687+* Support for public baymodels
688+* Rename 'insecure' baymodel attribute to 'tls\_disabled'
689+* Client: Pass bay\_uuid to Service Read/Write API
690+* Client: Pass bay\_uuid to Pod Read/Write API
691+* Client: Pass bay\_uuid to RC Read/Write API
692+* Add insecure flag to baymodel
693+* Magnum show cmds display dictionaries with unicode u chars
694+* Adding 'magnum service-list'
695+* Add certificates operations
696+* Change ignore-errors to ignore\_errors
697+* Add support for python >= 3.4
698+* Updated from global requirements
699+* Adds Labels Support
700+* Adds Container Network Model network\_driver support
701+* Update help message for coe service related command
702+* Add missed space between two words
703+* Updated from global requirements
704+* Rename existing service-\* to coe-service-\*
705+* Updated from global requirements
706+* Remove name from test token
707+* This adds proxy feature in magnum client
708+* Updated from global requirements
709+* Updated from global requirements
710+* Updated from global requirements
711+* Add support for multiple master nodes
712+* Updated from global requirements
713+* Remove uuidutils from openstack.common
714+* Updated from global requirements
715+* Updated from global requirements
716+* Remove H803 rule
717+* Updated from global requirements
718+* Updated from global requirements
719+* Updated from global requirements
720+* Rename image\_id to image when create a container
721+* Updated from global requirements
722+* Add missing dependency oslo.serialization
723+* Updated from global requirements
724+* Add additional arguments to CLI for container-create
725+* Pass environment variables of proxy to tox
726+* Change container-execute to container-exec
727+* Updated from global requirements
728+* Sync from latest oslo-incubator
729+* Updated from global requirements
730+* Fix translation setup
731+
732+0.2.1
733+-----
734+
735+* Make metavar's consistent
736+* Setup for translation
737+* Updated from global requirements
738+* Bump up to newer hacking
739+* Add support of container resource management with "name"
740+* Add support for container status
741+* Drop use of 'oslo' namespace package
742+
743+0.2.0
744+-----
745+
746+* Remove links attribute from pod show
747+* Log the correct url in debug mode
748+* Add heat timeout to bay-create for magnum client
749+* Add coe attribute to BayModel creation
750+* Add bay\_uuid to container create
751+* Update README to work with release tools
752+* Add support for pod name in pod-update command
753+
754+0.1.0
755+-----
756+
757+* Add bay status check when rc create
758+* Add bay status check when service create
759+* fix a typo in log
760+* Add bay status check when pod create
761+* Rename swarm-token to discovery-url
762+* Support pass command field when create a container
763+* Delegate magnum url search to url\_for method
764+* Remove side effect in get\_keystoneclient
765+* Add unittests for magnumclient.v1.client module
766+* Correct mock use in TestCommandLineArgument.setUp
767+* Uncap library requirements for liberty
768+* Support update a replication controller
769+* Support update a service
770+* Add swarm\_token to bay-create call
771+* Support update a pod
772+* Authenticate once
773+* Add error info detail for magnum cli
774+* Update bandit for latest usage
775+* Add tox bandit support for python-magnumclient
776+* Remove unused get\_projects\_list method
777+* Add support for bay name in bay-update command
778+* Allow bay name when replication controller is created
779+* Update .gitreview for project rename
780+* Allow baymodel name when bay is created
781+* Allow bay name when pod is created
782+* Allow bay name when service is created
783+* Support keystone region
784+* Rename k8s specific bay attributes
785+* Add support of baymodel resource management with "name"
786+* Add support of rc resource management with "name"
787+* Add support of service resource management with "name"
788+* Allow specification of ssh\_authorized\_key
789+
790+0.0.1
791+-----
792+
793+* Add support of pod resource management with "name"
794+* Remove some default values for baymodel create
795+* Add support of bay resource management with "name"
796+* Fix container exec output
797+* Fix the container logs output
798+* Make baymodel-create fail when no argument passed
799+* Display bay status column
800+* tidy up language on one shell help command
801+* Allow adding master flavor id to baymodel
802+* Allow specification of fixed\_network
803+* Allow specification of docker\_volume\_size
804+* Support multiple args in some magnum commands
805+* Remove '--id' option from magnum commands
806+* Make bay-create fail when no argument passed
807+* Adding test for magnumclient argument parsing
808+* Add support for updating a bay
809+* Adding tests for v1/shell.py
810+* Adding test for shell.py
811+* Container logs should use HTTP GET
812+* Add help info for container operations
813+* Update help message for baymodel operations
814+* Add help for magnum object show
815+* Remove 'desc' from container output
816+* Rename \`resource\`\_data/url attributes to manifest/manifest\_url
817+* Default version should be v1 in magnum
818+* Fix an error on not finding log handler
819+* Add id as required for bay-show/delete
820+* Revert "Fix an error on not finding log handler"
821+* Change rc\_data to replicationcontroller\_data
822+* Fix an error on not finding log handler
823+* Make replication controller client works
824+* Remove 'desc' from docker creation attribute
825+* Update test\_update to test\_pod\_update for pod client test
826+* Add client test for k8s replication controller
827+* Add client test for k8s service
828+* Add client test for magnum containers
829+* Add client test for magnum baymodel
830+* Add client test for magnum bay
831+* Add client test for magnum node
832+* Support file path as a service manifest data
833+* Adjusted CLI argument names to use dashes rather than underscores
834+* Add client test for test\_pods
835+* Support file path as a pod data
836+* Add unit test for magnumclient/common/httpclient.py
837+* Add unit test for magnumclient/common/utils.py
838+* Update K8S Pod and Service input parameter
839+* Make id as required when delete or show object info
840+* Construct URI properly for container-execute
841+* Add k8s replication support for magnum client
842+* Ensure a id string is passed when --id is used
843+* Change folder of api to v1 for magumclient
844+* Get container-list CLI working
845+* Do not advertise py33 compatibility
846+* Set HTTP PUT Content-Length for container actions
847+* Implement container actions
848+* Add image\_id attribute for container
849+* Service create only need service filename
850+* pod create only need pod filename
851+* Implement client for service operations
852+* Update parmater name for \_show\_pod
853+* Add baymodel\_id to bay\_create
854+* Add flavor\_id property to baymodel object
855+* Implement pod client CLI
856+* Make bay-create operate again as a result of baymodel
857+* Make bay create work with new bay model structure
858+* Type and image\_id are removed from server
859+* Don't show links when showing bay
860+* Make baymodel-list only show name and UUID
861+* Make baymodel not show links
862+* Implement baymodel in client
863+* Add node object to the python client
864+* Add image\_id and node\_count to bay-create
865+* Workflow documentation is now in infra-manual
866+* Make separate Bay objects for each bay object
867+* Complete implementation of bay operations
868+* Docstring cleanups
869+* Delete bay rather then container when requested
870+* Misc cleanups
871+* Make container in sync with bays and pods
872+* Make pod objects work in ReST Client API
873+* Make bay objects work in ReST Client API
874+* Modify container list attributes
875+* Implements basic container operations
876+* Add client resources and managers
877+* Update apiclient from oslo-incubator
878+* Add apiclient library from oslo-incubator
879+* Added default service type
880+* Skeleton for the cli client
881+* Include the auth module from oslo
882+* Sync common code from oslo-incubator
883+* Added project required files
884+* Add service API methods to what should be implemented
885+* Boilerplate client for communicating with ReST API
886+* Initial commit
887diff --git a/PKG-INFO b/PKG-INFO
888new file mode 100644
889index 0000000..8045ab2
890--- /dev/null
891+++ b/PKG-INFO
892@@ -0,0 +1,61 @@
893+Metadata-Version: 1.1
894+Name: python-magnumclient
895+Version: 3.0.1
896+Summary: Client library for Magnum API
897+Home-page: https://docs.openstack.org/python-magnumclient/latest/
898+Author: OpenStack
899+Author-email: openstack-discuss@lists.openstack.org
900+License: UNKNOWN
901+Description: ========================
902+ Team and repository tags
903+ ========================
904+
905+ .. image:: https://governance.openstack.org/tc/badges/python-magnumclient.svg
906+ :target: https://governance.openstack.org/tc/reference/tags/index.html
907+
908+ .. Change things from this point on
909+
910+ Python bindings to the Magnum API
911+ =================================
912+
913+ .. image:: https://img.shields.io/pypi/v/python-magnumclient.svg
914+ :target: https://pypi.org/project/python-magnumclient/
915+ :alt: Latest Version
916+
917+ .. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg
918+ :target: https://pypi.org/project/python-magnumclient/
919+ :alt: Downloads
920+
921+ This is a client library for Magnum built on the Magnum API. It
922+ provides a Python API (the ``magnumclient`` module) and a command-line
923+ tool (``magnum``).
924+
925+ Development takes place via the usual OpenStack processes as outlined
926+ in the `developer guide
927+ <https://docs.openstack.org/infra/manual/developers.html>`_.
928+
929+ * License: Apache License, Version 2.0
930+ * `PyPi`_ - package installation
931+ * `Online Documentation`_
932+ * `Launchpad project`_ - release management
933+ * `Bugs`_ - issue tracking
934+ * `Source`_
935+
936+ .. _PyPi: https://pypi.org/project/python-magnumclient
937+ .. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/
938+ .. _Launchpad project: https://launchpad.net/python-magnumclient
939+ .. _Bugs: https://bugs.launchpad.net/python-magnumclient
940+ .. _Source: https://opendev.org/openstack/python-magnumclient
941+
942+
943+Platform: UNKNOWN
944+Classifier: Environment :: OpenStack
945+Classifier: Intended Audience :: Information Technology
946+Classifier: Intended Audience :: System Administrators
947+Classifier: License :: OSI Approved :: Apache Software License
948+Classifier: Operating System :: POSIX :: Linux
949+Classifier: Programming Language :: Python
950+Classifier: Programming Language :: Python :: 2
951+Classifier: Programming Language :: Python :: 3
952+Classifier: Programming Language :: Python :: 3.6
953+Classifier: Programming Language :: Python :: 3.7
954diff --git a/README.rst b/README.rst
955index 2b7cf2b..c39cfb8 100644
956--- a/README.rst
957+++ b/README.rst
958@@ -2,8 +2,8 @@
959 Team and repository tags
960 ========================
961
962-.. image:: http://governance.openstack.org/badges/python-magnumclient.svg
963- :target: http://governance.openstack.org/reference/tags/index.html
964+.. image:: https://governance.openstack.org/tc/badges/python-magnumclient.svg
965+ :target: https://governance.openstack.org/tc/reference/tags/index.html
966
967 .. Change things from this point on
968
969@@ -11,11 +11,11 @@ Python bindings to the Magnum API
970 =================================
971
972 .. image:: https://img.shields.io/pypi/v/python-magnumclient.svg
973- :target: https://pypi.python.org/pypi/python-magnumclient/
974+ :target: https://pypi.org/project/python-magnumclient/
975 :alt: Latest Version
976
977 .. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg
978- :target: https://pypi.python.org/pypi/python-magnumclient/
979+ :target: https://pypi.org/project/python-magnumclient/
980 :alt: Downloads
981
982 This is a client library for Magnum built on the Magnum API. It
983@@ -33,8 +33,8 @@ in the `developer guide
984 * `Bugs`_ - issue tracking
985 * `Source`_
986
987-.. _PyPi: https://pypi.python.org/pypi/python-magnumclient
988+.. _PyPi: https://pypi.org/project/python-magnumclient
989 .. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/
990 .. _Launchpad project: https://launchpad.net/python-magnumclient
991 .. _Bugs: https://bugs.launchpad.net/python-magnumclient
992-.. _Source: https://git.openstack.org/cgit/openstack/python-magnumclient
993+.. _Source: https://opendev.org/openstack/python-magnumclient
994diff --git a/debian/changelog b/debian/changelog
995index 67e2c7c..7860985 100644
996--- a/debian/changelog
997+++ b/debian/changelog
998@@ -1,3 +1,14 @@
999+python-magnumclient (3.0.1-0ubuntu1) UNRELEASED; urgency=medium
1000+
1001+ * d/watch: Scope to 3.x series.
1002+ * New stable point release for OpenStack Ussuri (LP: #1996229).
1003+ * d/p/fix-py37-compatibility.patch: Dropped. Fixed in new stable point
1004+ release.
1005+ * d/p/Fix-failing-to-parse-json-error-msg.patch: Dropped. Fixed in new
1006+ stable point release.
1007+
1008+ -- Felipe Reyes <felipe.reyes@canonical.com> Fri, 12 May 2023 12:18:49 -0400
1009+
1010 python-magnumclient (2.11.0-0ubuntu6) focal; urgency=medium
1011
1012 * d/p/Fix-failing-to-parse-json-error-msg.patch: Fix failing to parse json
1013diff --git a/debian/patches/Fix-failing-to-parse-json-error-msg.patch b/debian/patches/Fix-failing-to-parse-json-error-msg.patch
1014deleted file mode 100644
1015index 3bb25d8..0000000
1016--- a/debian/patches/Fix-failing-to-parse-json-error-msg.patch
1017+++ /dev/null
1018@@ -1,38 +0,0 @@
1019-From f7551a6bac103070ff456098fe2631409620d492 Mon Sep 17 00:00:00 2001
1020-From: Tobias Urdin <tobias.urdin@binero.se>
1021-Date: Wed, 18 Nov 2020 11:46:44 +0100
1022-Subject: [PATCH] Fix failing to parse json error msg
1023-
1024-It assumes its a requests response but could
1025-be a HTTPResponse from urllib.
1026-
1027-Story: 2008789
1028-Task: 42183
1029-
1030-Change-Id: I7306d167a17284c7f478ec1c1599a8d4b32040c2
1031----
1032- magnumclient/common/httpclient.py | 3 +++
1033- 1 file changed, 3 insertions(+)
1034-
1035-Index: python-magnumclient-2.11.0/magnumclient/common/httpclient.py
1036-===================================================================
1037---- python-magnumclient-2.11.0.orig/magnumclient/common/httpclient.py
1038-+++ python-magnumclient-2.11.0/magnumclient/common/httpclient.py
1039-@@ -23,6 +23,7 @@ import socket
1040- import ssl
1041-
1042- from keystoneauth1 import adapter
1043-+from oslo_serialization import jsonutils
1044- from oslo_utils import importutils
1045- import six
1046- import six.moves.urllib.parse as urlparse
1047-@@ -65,6 +66,9 @@ def _extract_error_json(body, resp):
1048- try:
1049- body_json = resp.json()
1050- return _extract_error_json_text(body_json)
1051-+ except AttributeError:
1052-+ body_json = jsonutils.loads(body)
1053-+ return _extract_error_json_text(body_json)
1054- except ValueError:
1055- return {}
1056- else:
1057diff --git a/debian/patches/fix-py37-compatibility.patch b/debian/patches/fix-py37-compatibility.patch
1058deleted file mode 100644
1059index 29e3459..0000000
1060--- a/debian/patches/fix-py37-compatibility.patch
1061+++ /dev/null
1062@@ -1,47 +0,0 @@
1063-From 5deb538930d98bf83e11bfb1dacb509982226540 Mon Sep 17 00:00:00 2001
1064-From: Michal Arbet <michal.arbet@ultimum.io>
1065-Date: Wed, 6 Feb 2019 14:11:07 +0100
1066-Subject: [PATCH] Fix py37 compatibility
1067-
1068-Unit tests are failing under python3.7.
1069-Generators which explicitly raise StopIteration can generally be
1070-changed to simply return instead. This will be compatible with
1071-all existing Python versions.
1072-
1073-PEP Documentation for this change:
1074-https://www.python.org/dev/peps/pep-0479/
1075-
1076-Change-Id: I4ae2049d8a2469d0a37077bdc722481e68d7cc49
1077-Closes-Bug: #1814890
1078----
1079- magnumclient/common/httpclient.py | 7 +++++--
1080- 1 file changed, 5 insertions(+), 2 deletions(-)
1081-
1082-diff --git a/magnumclient/common/httpclient.py b/magnumclient/common/httpclient.py
1083-index ce9e4c9..038fb21 100644
1084---- a/magnumclient/common/httpclient.py
1085-+++ b/magnumclient/common/httpclient.py
1086-@@ -406,7 +406,10 @@ class ResponseBodyIterator(object):
1087-
1088- def __iter__(self):
1089- while True:
1090-- yield self.next()
1091-+ try:
1092-+ yield self.next()
1093-+ except StopIteration:
1094-+ return
1095-
1096- def __bool__(self):
1097- return hasattr(self, 'items')
1098-@@ -418,7 +421,7 @@ class ResponseBodyIterator(object):
1099- if chunk:
1100- return chunk
1101- else:
1102-- raise StopIteration()
1103-+ raise StopIteration
1104-
1105-
1106- def _construct_http_client(*args, **kwargs):
1107---
1108-2.31.1
1109-
1110diff --git a/debian/patches/series b/debian/patches/series
1111index 5b19bc2..e69de29 100644
1112--- a/debian/patches/series
1113+++ b/debian/patches/series
1114@@ -1,2 +0,0 @@
1115-fix-py37-compatibility.patch
1116-Fix-failing-to-parse-json-error-msg.patch
1117diff --git a/debian/watch b/debian/watch
1118index e38e2ce..b2923cb 100644
1119--- a/debian/watch
1120+++ b/debian/watch
1121@@ -1,3 +1,3 @@
1122 version=3
1123-opts="uversionmangle=s/\.(b|rc)/~$1/" \
1124-https://github.com/openstack/python-magnumclient/tags .*/(\d[\d\.]+)\.tar\.gz
1125+opts="uversionmangle=s/\.([a-zA-Z])/~$1/;s/%7E/~/;s/\.0b/~b/;s/\.0rc/~rc/" \
1126+https://tarballs.opendev.org/openstack/python-magnumclient/ python-magnumclient-(3\.\d.*)\.tar\.gz
1127diff --git a/doc/requirements.txt b/doc/requirements.txt
1128index 0dff061..711fb97 100644
1129--- a/doc/requirements.txt
1130+++ b/doc/requirements.txt
1131@@ -1,3 +1,4 @@
1132-sphinx!=1.6.6,>=1.6.2 # BSD
1133+sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD
1134+sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD
1135 openstackdocstheme>=1.18.1 # Apache-2.0
1136 reno>=2.5.0 # Apache-2.0
1137diff --git a/magnumclient/common/cliutils.py b/magnumclient/common/cliutils.py
1138index 34ab129..7061f3b 100644
1139--- a/magnumclient/common/cliutils.py
1140+++ b/magnumclient/common/cliutils.py
1141@@ -404,8 +404,8 @@ def get_service_type(f):
1142 return getattr(f, 'service_type', None)
1143
1144
1145-def pretty_choice_list(l):
1146- return ', '.join("'%s'" % i for i in l)
1147+def pretty_choice_list(lst):
1148+ return ', '.join("'%s'" % i for i in lst)
1149
1150
1151 def exit(msg=''):
1152diff --git a/magnumclient/common/httpclient.py b/magnumclient/common/httpclient.py
1153index 8e5d90f..038fb21 100644
1154--- a/magnumclient/common/httpclient.py
1155+++ b/magnumclient/common/httpclient.py
1156@@ -16,13 +16,13 @@
1157 # under the License.
1158
1159 import copy
1160-import json
1161 import logging
1162 import os
1163 import socket
1164 import ssl
1165
1166 from keystoneauth1 import adapter
1167+from oslo_serialization import jsonutils
1168 from oslo_utils import importutils
1169 import six
1170 import six.moves.urllib.parse as urlparse
1171@@ -43,7 +43,7 @@ def _extract_error_json_text(body_json):
1172 error_json = {}
1173 if 'error_message' in body_json:
1174 raw_msg = body_json['error_message']
1175- error_json = json.loads(raw_msg)
1176+ error_json = jsonutils.loads(raw_msg)
1177 elif 'error' in body_json:
1178 error_body = body_json['error']
1179 error_json = {'faultstring': error_body['title'],
1180@@ -69,7 +69,7 @@ def _extract_error_json(body, resp):
1181 return {}
1182 else:
1183 try:
1184- body_json = json.loads(body)
1185+ body_json = jsonutils.loads(body)
1186 return _extract_error_json_text(body_json)
1187 except ValueError:
1188 return {}
1189@@ -228,7 +228,7 @@ class HTTPClient(object):
1190 kwargs['headers'].setdefault('Accept', 'application/json')
1191
1192 if 'body' in kwargs:
1193- kwargs['body'] = json.dumps(kwargs['body'])
1194+ kwargs['body'] = jsonutils.dumps(kwargs['body'])
1195
1196 resp, body_iter = self._http_request(url, method, **kwargs)
1197 content_type = resp.getheader('content-type', None)
1198@@ -239,7 +239,7 @@ class HTTPClient(object):
1199 if 'application/json' in content_type:
1200 body = ''.join([chunk for chunk in body_iter])
1201 try:
1202- body = json.loads(body)
1203+ body = jsonutils.loads(body)
1204 except ValueError:
1205 LOG.error('Could not decode response body as JSON')
1206 else:
1207@@ -373,7 +373,7 @@ class SessionClient(adapter.LegacyJsonAdapter):
1208 kwargs['headers'].setdefault('Content-Type', 'application/json')
1209 kwargs['headers'].setdefault('Accept', 'application/json')
1210 if 'body' in kwargs:
1211- kwargs['data'] = json.dumps(kwargs.pop('body'))
1212+ kwargs['data'] = jsonutils.dumps(kwargs.pop('body'))
1213
1214 resp = self._http_request(url, method, **kwargs)
1215 body = resp.content
1216@@ -406,7 +406,10 @@ class ResponseBodyIterator(object):
1217
1218 def __iter__(self):
1219 while True:
1220- yield self.next()
1221+ try:
1222+ yield self.next()
1223+ except StopIteration:
1224+ return
1225
1226 def __bool__(self):
1227 return hasattr(self, 'items')
1228@@ -418,7 +421,7 @@ class ResponseBodyIterator(object):
1229 if chunk:
1230 return chunk
1231 else:
1232- raise StopIteration()
1233+ raise StopIteration
1234
1235
1236 def _construct_http_client(*args, **kwargs):
1237diff --git a/magnumclient/common/utils.py b/magnumclient/common/utils.py
1238index 99676da..4a8cfb1 100644
1239--- a/magnumclient/common/utils.py
1240+++ b/magnumclient/common/utils.py
1241@@ -15,8 +15,6 @@
1242 # License for the specific language governing permissions and limitations
1243 # under the License.
1244
1245-import base64
1246-import json
1247 import os
1248
1249 from cryptography.hazmat.backends import default_backend
1250@@ -25,6 +23,8 @@ from cryptography.hazmat.primitives import hashes
1251 from cryptography.hazmat.primitives import serialization
1252 from cryptography import x509
1253 from cryptography.x509.oid import NameOID
1254+from oslo_serialization import base64
1255+from oslo_serialization import jsonutils
1256
1257 from magnumclient import exceptions as exc
1258 from magnumclient.i18n import _
1259@@ -64,7 +64,7 @@ def split_and_deserialize(string):
1260 raise exc.CommandError(_('Attributes must be a list of '
1261 'PATH=VALUE not "%s"') % string)
1262 try:
1263- value = json.loads(value)
1264+ value = jsonutils.loads(value)
1265 except ValueError:
1266 pass
1267
1268@@ -100,7 +100,7 @@ def handle_labels(labels):
1269 if 'mesos_slave_executor_env_file' in labels:
1270 environment_variables_data = handle_json_from_file(
1271 labels['mesos_slave_executor_env_file'])
1272- labels['mesos_slave_executor_env_variables'] = json.dumps(
1273+ labels['mesos_slave_executor_env_variables'] = jsonutils.dumps(
1274 environment_variables_data)
1275 return labels
1276
1277@@ -118,12 +118,12 @@ def format_labels(lbls, parse_comma=True):
1278 lbls = lbls[0].replace(';', ',').split(',')
1279
1280 labels = {}
1281- for l in lbls:
1282+ for lbl in lbls:
1283 try:
1284- (k, v) = l.split(('='), 1)
1285+ (k, v) = lbl.split(('='), 1)
1286 except ValueError:
1287 raise exc.CommandError(_('labels must be a list of KEY=VALUE '
1288- 'not %s') % l)
1289+ 'not %s') % lbl)
1290 if k not in labels:
1291 labels[k] = v
1292 else:
1293@@ -146,7 +146,7 @@ def handle_json_from_file(json_arg):
1294 try:
1295 with open(json_arg, 'r') as f:
1296 json_arg = f.read().strip()
1297- json_arg = json.loads(json_arg)
1298+ json_arg = jsonutils.loads(json_arg)
1299 except IOError as e:
1300 err = _("Cannot get JSON from file '%(file)s'. "
1301 "Error: %(err)s") % {'err': e, 'file': json_arg}
1302@@ -160,11 +160,11 @@ def handle_json_from_file(json_arg):
1303
1304
1305 def config_cluster(cluster, cluster_template, cfg_dir, force=False,
1306- certs=None):
1307+ certs=None, use_keystone=False):
1308 """Return and write configuration for the given cluster."""
1309 if cluster_template.coe == 'kubernetes':
1310 return _config_cluster_kubernetes(cluster, cluster_template, cfg_dir,
1311- force, certs)
1312+ force, certs, use_keystone)
1313 elif (cluster_template.coe == 'swarm'
1314 or cluster_template.coe == 'swarm-mode'):
1315 return _config_cluster_swarm(cluster, cluster_template, cfg_dir,
1316@@ -172,7 +172,7 @@ def config_cluster(cluster, cluster_template, cfg_dir, force=False,
1317
1318
1319 def _config_cluster_kubernetes(cluster, cluster_template, cfg_dir,
1320- force=False, certs=None):
1321+ force=False, certs=None, use_keystone=False):
1322 """Return and write configuration for the given kubernetes cluster."""
1323 cfg_file = "%s/config" % cfg_dir
1324 if cluster_template.tls_disabled or certs is None:
1325@@ -193,30 +193,64 @@ def _config_cluster_kubernetes(cluster, cluster_template, cfg_dir,
1326 "- name: %(name)s'\n"
1327 % {'name': cluster.name, 'api_address': cluster.api_address})
1328 else:
1329- cfg = ("apiVersion: v1\n"
1330- "clusters:\n"
1331- "- cluster:\n"
1332- " certificate-authority-data: %(ca)s\n"
1333- " server: %(api_address)s\n"
1334- " name: %(name)s\n"
1335- "contexts:\n"
1336- "- context:\n"
1337- " cluster: %(name)s\n"
1338- " user: admin\n"
1339- " name: default\n"
1340- "current-context: default\n"
1341- "kind: Config\n"
1342- "preferences: {}\n"
1343- "users:\n"
1344- "- name: admin\n"
1345- " user:\n"
1346- " client-certificate-data: %(cert)s\n"
1347- " client-key-data: %(key)s\n"
1348- % {'name': cluster.name,
1349- 'api_address': cluster.api_address,
1350- 'key': base64.b64encode(certs['key']),
1351- 'cert': base64.b64encode(certs['cert']),
1352- 'ca': base64.b64encode(certs['ca'])})
1353+ if not use_keystone:
1354+ cfg = ("apiVersion: v1\n"
1355+ "clusters:\n"
1356+ "- cluster:\n"
1357+ " certificate-authority-data: %(ca)s\n"
1358+ " server: %(api_address)s\n"
1359+ " name: %(name)s\n"
1360+ "contexts:\n"
1361+ "- context:\n"
1362+ " cluster: %(name)s\n"
1363+ " user: admin\n"
1364+ " name: default\n"
1365+ "current-context: default\n"
1366+ "kind: Config\n"
1367+ "preferences: {}\n"
1368+ "users:\n"
1369+ "- name: admin\n"
1370+ " user:\n"
1371+ " client-certificate-data: %(cert)s\n"
1372+ " client-key-data: %(key)s\n"
1373+ % {'name': cluster.name,
1374+ 'api_address': cluster.api_address,
1375+ 'key': base64.encode_as_text(certs['key']),
1376+ 'cert': base64.encode_as_text(certs['cert']),
1377+ 'ca': base64.encode_as_text(certs['ca'])})
1378+ else:
1379+ cfg = ("apiVersion: v1\n"
1380+ "clusters:\n"
1381+ "- cluster:\n"
1382+ " certificate-authority-data: %(ca)s\n"
1383+ " server: %(api_address)s\n"
1384+ " name: %(name)s\n"
1385+ "contexts:\n"
1386+ "- context:\n"
1387+ " cluster: %(name)s\n"
1388+ " user: openstackuser\n"
1389+ " name: openstackuser@kubernetes\n"
1390+ "current-context: openstackuser@kubernetes\n"
1391+ "kind: Config\n"
1392+ "preferences: {}\n"
1393+ "users:\n"
1394+ "- name: openstackuser\n"
1395+ " user:\n"
1396+ " exec:\n"
1397+ " command: /bin/bash\n"
1398+ " apiVersion: client.authentication.k8s.io/v1alpha1\n"
1399+ " args:\n"
1400+ " - -c\n"
1401+ " - >\n"
1402+ " if [ -z ${OS_TOKEN} ]; then\n"
1403+ " echo 'Error: Missing OpenStack credential from environment variable $OS_TOKEN' > /dev/stderr\n" # noqa
1404+ " exit 1\n"
1405+ " else\n"
1406+ " echo '{ \"apiVersion\": \"client.authentication.k8s.io/v1alpha1\", \"kind\": \"ExecCredential\", \"status\": { \"token\": \"'\"${OS_TOKEN}\"'\"}}'\n" # noqa
1407+ " fi\n"
1408+ % {'name': cluster.name,
1409+ 'api_address': cluster.api_address,
1410+ 'ca': base64.encode_as_text(certs['ca'])})
1411
1412 if os.path.exists(cfg_file) and not force:
1413 raise exc.CommandError("File %s exists, aborting." % cfg_file)
1414diff --git a/magnumclient/exceptions.py b/magnumclient/exceptions.py
1415index d069f85..8afd80a 100644
1416--- a/magnumclient/exceptions.py
1417+++ b/magnumclient/exceptions.py
1418@@ -18,15 +18,15 @@ from magnumclient.common.apiclient.exceptions import * # noqa
1419
1420 # NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
1421 # compatibility.
1422-InvalidEndpoint = EndpointException
1423-CommunicationError = ConnectionRefused
1424-HTTPBadRequest = BadRequest
1425-HTTPInternalServerError = InternalServerError
1426-HTTPNotFound = NotFound
1427-HTTPServiceUnavailable = ServiceUnavailable
1428+InvalidEndpoint = exceptions.EndpointException
1429+CommunicationError = exceptions.ConnectionRefused
1430+HTTPBadRequest = exceptions.BadRequest
1431+HTTPInternalServerError = exceptions.InternalServerError
1432+HTTPNotFound = exceptions.NotFound
1433+HTTPServiceUnavailable = exceptions.ServiceUnavailable
1434
1435
1436-class AmbiguousAuthSystem(ClientException):
1437+class AmbiguousAuthSystem(exceptions.ClientException):
1438 """Could not obtain token and endpoint using provided credentials."""
1439 pass
1440
1441@@ -35,7 +35,7 @@ class AmbiguousAuthSystem(ClientException):
1442 AmbigiousAuthSystem = AmbiguousAuthSystem
1443
1444
1445-class InvalidAttribute(ClientException):
1446+class InvalidAttribute(exceptions.ClientException):
1447 pass
1448
1449
1450diff --git a/magnumclient/osc/plugin.py b/magnumclient/osc/plugin.py
1451index dde56fd..49309c2 100644
1452--- a/magnumclient/osc/plugin.py
1453+++ b/magnumclient/osc/plugin.py
1454@@ -17,7 +17,8 @@ from osc_lib import utils
1455
1456 LOG = logging.getLogger(__name__)
1457
1458-DEFAULT_API_VERSION = '1'
1459+DEFAULT_MAJOR_API_VERSION = '1'
1460+DEFAULT_MAGNUM_API_VERSION = 'latest'
1461 API_VERSION_OPTION = 'os_container_infra_api_version'
1462 API_NAME = 'container_infra'
1463 API_VERSIONS = {
1464@@ -37,7 +38,8 @@ def make_client(instance):
1465 region_name=instance._region_name,
1466 interface=instance._interface,
1467 insecure=instance._insecure,
1468- ca_cert=instance._cacert)
1469+ ca_cert=instance._cacert,
1470+ api_version=DEFAULT_MAGNUM_API_VERSION)
1471 return client
1472
1473
1474@@ -49,8 +51,8 @@ def build_option_parser(parser):
1475 metavar='<container-infra-api-version>',
1476 default=utils.env(
1477 'OS_CONTAINER_INFRA_API_VERSION',
1478- default=DEFAULT_API_VERSION),
1479+ default=DEFAULT_MAJOR_API_VERSION),
1480 help='Container-Infra API version, default=' +
1481- DEFAULT_API_VERSION +
1482+ DEFAULT_MAJOR_API_VERSION +
1483 ' (Env: OS_CONTAINER_INFRA_API_VERSION)')
1484 return parser
1485diff --git a/magnumclient/osc/v1/cluster_templates.py b/magnumclient/osc/v1/cluster_templates.py
1486index d898af9..852ee33 100644
1487--- a/magnumclient/osc/v1/cluster_templates.py
1488+++ b/magnumclient/osc/v1/cluster_templates.py
1489@@ -51,7 +51,8 @@ CLUSTER_TEMPLATE_ATTRIBUTES = [
1490 'coe',
1491 'flavor_id',
1492 'master_lb_enabled',
1493- 'dns_nameserver'
1494+ 'dns_nameserver',
1495+ 'hidden'
1496 ]
1497
1498
1499@@ -221,6 +222,17 @@ class CreateClusterTemplate(command.ShowOne):
1500 action='append_const',
1501 const=False,
1502 help=_('Disables floating ip creation on the new Cluster'))
1503+ parser.add_argument(
1504+ '--hidden',
1505+ dest='hidden',
1506+ action='store_true',
1507+ default=False,
1508+ help=_('Indicates the cluster template should be hidden.'))
1509+ parser.add_argument(
1510+ '--visible',
1511+ dest='hidden',
1512+ action='store_false',
1513+ help=_('Indicates the cluster template should be visible.'))
1514
1515 return parser
1516
1517@@ -252,6 +264,12 @@ class CreateClusterTemplate(command.ShowOne):
1518 'server_type': parsed_args.server_type,
1519 'master_lb_enabled': parsed_args.master_lb_enabled,
1520 }
1521+
1522+ # NOTE (brtknr): Only supply hidden arg if it is True
1523+ # for backward compatibility
1524+ if parsed_args.hidden:
1525+ args['hidden'] = parsed_args.hidden
1526+
1527 if len(parsed_args.floating_ip_enabled) > 1:
1528 raise InvalidAttribute('--floating-ip-enabled and '
1529 '--floating-ip-disabled are '
1530@@ -336,6 +354,8 @@ class ListTemplateCluster(command.Lister):
1531
1532 mag_client = self.app.client_manager.container_infra
1533 columns = ['uuid', 'name']
1534+ if parsed_args.fields:
1535+ columns += parsed_args.fields.split(',')
1536 cts = mag_client.cluster_templates.list(limit=parsed_args.limit,
1537 sort_key=parsed_args.sort_key,
1538 sort_dir=parsed_args.sort_dir)
1539diff --git a/magnumclient/osc/v1/clusters.py b/magnumclient/osc/v1/clusters.py
1540index ac1419a..3ab06e9 100644
1541--- a/magnumclient/osc/v1/clusters.py
1542+++ b/magnumclient/osc/v1/clusters.py
1543@@ -24,6 +24,7 @@ from osc_lib import utils
1544
1545 CLUSTER_ATTRIBUTES = [
1546 'status',
1547+ 'health_status',
1548 'cluster_template_id',
1549 'node_addresses',
1550 'uuid',
1551@@ -33,6 +34,9 @@ CLUSTER_ATTRIBUTES = [
1552 'updated_at',
1553 'coe_version',
1554 'labels',
1555+ 'labels_overridden',
1556+ 'labels_skipped',
1557+ 'labels_added',
1558 'faults',
1559 'keypair',
1560 'api_address',
1561@@ -45,6 +49,8 @@ CLUSTER_ATTRIBUTES = [
1562 'name',
1563 'master_flavor_id',
1564 'flavor_id',
1565+ 'health_status_reason',
1566+ 'project_id',
1567 ]
1568
1569
1570@@ -113,6 +119,38 @@ class CreateCluster(command.Command):
1571 metavar='<flavor>',
1572 help=_('The nova flavor name or UUID to use when launching the '
1573 'Cluster.'))
1574+ parser.add_argument(
1575+ '--fixed-network',
1576+ dest='fixed_network',
1577+ metavar='<fixed-network>',
1578+ help=_('The private Neutron network name to connect to this '
1579+ 'Cluster template.'))
1580+ parser.add_argument(
1581+ '--fixed-subnet',
1582+ dest='fixed_subnet',
1583+ metavar='<fixed-subnet>',
1584+ help=_('The private Neutron subnet name to connect to Cluster.'))
1585+ parser.add_argument(
1586+ '--floating-ip-enabled',
1587+ dest='floating_ip_enabled',
1588+ default=[],
1589+ action='append_const',
1590+ const=True,
1591+ help=_('Indicates whether created Clusters should have a '
1592+ 'floating ip.'))
1593+ parser.add_argument(
1594+ '--floating-ip-disabled',
1595+ dest='floating_ip_enabled',
1596+ action='append_const',
1597+ const=False,
1598+ help=_('Disables floating ip creation on the new Cluster'))
1599+ parser.add_argument(
1600+ '--merge-labels',
1601+ dest='merge_labels',
1602+ action='store_true',
1603+ default=False,
1604+ help=_('The labels provided will be merged with the labels '
1605+ 'configured in the specified cluster template.'))
1606
1607 return parser
1608
1609@@ -130,6 +168,15 @@ class CreateCluster(command.Command):
1610 'node_count': parsed_args.node_count,
1611 }
1612
1613+ if len(parsed_args.floating_ip_enabled) > 1:
1614+ raise exceptions.InvalidAttribute(
1615+ '--floating-ip-enabled and '
1616+ '--floating-ip-disabled are '
1617+ 'mutually exclusive and '
1618+ 'should be specified only once.')
1619+ elif len(parsed_args.floating_ip_enabled) == 1:
1620+ args['floating_ip_enabled'] = parsed_args.floating_ip_enabled[0]
1621+
1622 if parsed_args.labels is not None:
1623 args['labels'] = magnum_utils.handle_labels(parsed_args.labels)
1624
1625@@ -142,6 +189,17 @@ class CreateCluster(command.Command):
1626 if parsed_args.flavor is not None:
1627 args['flavor_id'] = parsed_args.flavor
1628
1629+ if parsed_args.fixed_network is not None:
1630+ args["fixed_network"] = parsed_args.fixed_network
1631+
1632+ if parsed_args.fixed_subnet is not None:
1633+ args["fixed_subnet"] = parsed_args.fixed_subnet
1634+
1635+ if parsed_args.merge_labels:
1636+ # We are only sending this if it's True. This
1637+ # way we avoid breaking older APIs.
1638+ args["merge_labels"] = parsed_args.merge_labels
1639+
1640 cluster = mag_client.clusters.create(**args)
1641 print("Request to create cluster %s accepted"
1642 % cluster.uuid)
1643@@ -197,7 +255,8 @@ class ListCluster(command.Lister):
1644
1645 mag_client = self.app.client_manager.container_infra
1646 columns = [
1647- 'uuid', 'name', 'keypair', 'node_count', 'master_count', 'status']
1648+ 'uuid', 'name', 'keypair', 'node_count', 'master_count', 'status',
1649+ 'health_status']
1650 clusters = mag_client.clusters.list(limit=parsed_args.limit,
1651 sort_key=parsed_args.sort_key,
1652 sort_dir=parsed_args.sort_dir)
1653@@ -305,6 +364,18 @@ class ConfigCluster(command.Command):
1654 dest='output_certs',
1655 default=False,
1656 help=_('Output certificates in separate files.'))
1657+ parser.add_argument(
1658+ '--use-certificate',
1659+ action='store_true',
1660+ dest='use_certificate',
1661+ default=True,
1662+ help=_('Use certificate in config files.'))
1663+ parser.add_argument(
1664+ '--use-keystone',
1665+ action='store_true',
1666+ dest='use_keystone',
1667+ default=False,
1668+ help=_('Use Keystone token in config files.'))
1669
1670 return parser
1671
1672@@ -315,16 +386,21 @@ class ConfigCluster(command.Command):
1673 the corresponding COE configured to access the cluster.
1674
1675 """
1676+ if parsed_args.use_keystone:
1677+ parsed_args.use_certificate = False
1678+ if not parsed_args.use_certificate:
1679+ parsed_args.use_keystone = True
1680+
1681 self.log.debug("take_action(%s)", parsed_args)
1682
1683 mag_client = self.app.client_manager.container_infra
1684
1685 parsed_args.dir = os.path.abspath(parsed_args.dir)
1686 cluster = mag_client.clusters.get(parsed_args.cluster)
1687- if cluster.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE',
1688- 'ROLLBACK_COMPLETE'):
1689- raise exceptions.CommandError("cluster in status %s" %
1690- cluster.status)
1691+ if cluster.api_address is None:
1692+ self.log.warning("WARNING: The cluster's api_address is"
1693+ " not known yet.")
1694+
1695 cluster_template = mag_client.cluster_templates.get(
1696 cluster.cluster_template_id)
1697 opts = {
1698@@ -346,8 +422,92 @@ class ConfigCluster(command.Command):
1699 with open(fname, "w") as f:
1700 f.write(tls[k])
1701
1702- print(magnum_utils.config_cluster(cluster,
1703- cluster_template,
1704- parsed_args.dir,
1705- force=parsed_args.force,
1706- certs=tls))
1707+ print(magnum_utils.config_cluster(
1708+ cluster, cluster_template, parsed_args.dir,
1709+ force=parsed_args.force, certs=tls,
1710+ use_keystone=parsed_args.use_keystone))
1711+
1712+
1713+class ResizeCluster(command.Command):
1714+ _description = _("Resize a Cluster")
1715+
1716+ def get_parser(self, prog_name):
1717+ parser = super(ResizeCluster, self).get_parser(prog_name)
1718+ parser.add_argument(
1719+ 'cluster',
1720+ metavar='<cluster>',
1721+ help=_('The name or UUID of cluster to update'))
1722+
1723+ parser.add_argument(
1724+ 'node_count',
1725+ type=int,
1726+ help=_("Desired node count of the cluser."))
1727+
1728+ parser.add_argument(
1729+ '--nodes-to-remove',
1730+ metavar='<Server UUID>',
1731+ action='append',
1732+ help=_("Server ID of the nodes to be removed. Repeat to add"
1733+ "more server ID"))
1734+
1735+ parser.add_argument(
1736+ '--nodegroup',
1737+ metavar='<nodegroup>',
1738+ help=_('The name or UUID of the nodegroup of current cluster.'))
1739+
1740+ return parser
1741+
1742+ def take_action(self, parsed_args):
1743+ self.log.debug("take_action(%s)", parsed_args)
1744+
1745+ mag_client = self.app.client_manager.container_infra
1746+ cluster = mag_client.clusters.get(parsed_args.cluster)
1747+
1748+ mag_client.clusters.resize(cluster.uuid,
1749+ parsed_args.node_count,
1750+ parsed_args.nodes_to_remove,
1751+ parsed_args.nodegroup)
1752+ print("Request to resize cluster %s has been accepted." %
1753+ parsed_args.cluster)
1754+
1755+
1756+class UpgradeCluster(command.Command):
1757+ _description = _("Upgrade a Cluster")
1758+
1759+ def get_parser(self, prog_name):
1760+ parser = super(UpgradeCluster, self).get_parser(prog_name)
1761+ parser.add_argument(
1762+ 'cluster',
1763+ metavar='<cluster>',
1764+ help=_('The name or UUID of cluster to update'))
1765+
1766+ parser.add_argument(
1767+ 'cluster_template',
1768+ help=_("The new cluster template ID will be upgraded to."))
1769+
1770+ parser.add_argument(
1771+ '--max-batch-size',
1772+ metavar='<max_batch_size>',
1773+ type=int,
1774+ default=1,
1775+ help=_("The max batch size for upgrading each time."))
1776+
1777+ parser.add_argument(
1778+ '--nodegroup',
1779+ metavar='<nodegroup>',
1780+ help=_('The name or UUID of the nodegroup of current cluster.'))
1781+
1782+ return parser
1783+
1784+ def take_action(self, parsed_args):
1785+ self.log.debug("take_action(%s)", parsed_args)
1786+
1787+ mag_client = self.app.client_manager.container_infra
1788+ cluster = mag_client.clusters.get(parsed_args.cluster)
1789+
1790+ mag_client.clusters.upgrade(cluster.uuid,
1791+ parsed_args.cluster_template,
1792+ parsed_args.max_batch_size,
1793+ parsed_args.nodegroup)
1794+ print("Request to upgrade cluster %s has been accepted." %
1795+ parsed_args.cluster)
1796diff --git a/magnumclient/osc/v1/nodegroups.py b/magnumclient/osc/v1/nodegroups.py
1797new file mode 100644
1798index 0000000..afb5870
1799--- /dev/null
1800+++ b/magnumclient/osc/v1/nodegroups.py
1801@@ -0,0 +1,301 @@
1802+# Copyright (c) 2018 European Organization for Nuclear Research.
1803+# All Rights Reserved.
1804+#
1805+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1806+# not use this file except in compliance with the License. You may obtain
1807+# a copy of the License at
1808+#
1809+# http://www.apache.org/licenses/LICENSE-2.0
1810+#
1811+# Unless required by applicable law or agreed to in writing, software
1812+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1813+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1814+# License for the specific language governing permissions and limitations
1815+# under the License.
1816+
1817+from magnumclient.common import utils as magnum_utils
1818+from magnumclient.i18n import _
1819+
1820+from osc_lib.command import command
1821+from osc_lib import utils
1822+
1823+
1824+NODEGROUP_ATTRIBUTES = [
1825+ 'uuid',
1826+ 'name',
1827+ 'cluster_id',
1828+ 'project_id',
1829+ 'docker_volume_size',
1830+ 'labels',
1831+ 'labels_overridden',
1832+ 'labels_skipped',
1833+ 'labels_added',
1834+ 'flavor_id',
1835+ 'image_id',
1836+ 'node_addresses',
1837+ 'node_count',
1838+ 'role',
1839+ 'max_node_count',
1840+ 'min_node_count',
1841+ 'is_default',
1842+ 'stack_id',
1843+ 'status',
1844+ 'status_reason',
1845+]
1846+
1847+
1848+class CreateNodeGroup(command.Command):
1849+ _description = _("Create a nodegroup")
1850+
1851+ def get_parser(self, prog_name):
1852+ parser = super(CreateNodeGroup, self).get_parser(prog_name)
1853+ # NOTE: All arguments are positional and, if not provided
1854+ # with a default, required.
1855+ parser.add_argument('--docker-volume-size',
1856+ dest='docker_volume_size',
1857+ type=int,
1858+ metavar='<docker-volume-size>',
1859+ help=('The size in GB for the docker volume to '
1860+ 'use.'))
1861+ parser.add_argument('--labels',
1862+ metavar='<KEY1=VALUE1,KEY2=VALUE2;KEY3=VALUE3...>',
1863+ action='append',
1864+ help=_('Arbitrary labels in the form of key=value'
1865+ 'pairs to associate with a nodegroup. '
1866+ 'May be used multiple times.'))
1867+ parser.add_argument('cluster',
1868+ metavar='<cluster>',
1869+ help='Name of the nodegroup to create.')
1870+ parser.add_argument('name',
1871+ metavar='<name>',
1872+ help='Name of the nodegroup to create.')
1873+ parser.add_argument('--node-count',
1874+ dest='node_count',
1875+ type=int,
1876+ default=1,
1877+ metavar='<node-count>',
1878+ help='The nodegroup node count.')
1879+ parser.add_argument('--min-nodes',
1880+ dest='min_node_count',
1881+ type=int,
1882+ default=1,
1883+ metavar='<min-nodes>',
1884+ help='The nodegroup minimum node count.')
1885+ parser.add_argument('--max-nodes',
1886+ dest='max_node_count',
1887+ type=int,
1888+ default=None,
1889+ metavar='<max-nodes>',
1890+ help='The nodegroup maximum node count.')
1891+ parser.add_argument('--role',
1892+ dest='role',
1893+ type=str,
1894+ default='worker',
1895+ metavar='<role>',
1896+ help=('The role of the nodegroup'))
1897+ parser.add_argument(
1898+ '--image',
1899+ metavar='<image>',
1900+ help=_('The name or UUID of the base image to customize for the '
1901+ 'NodeGroup.'))
1902+ parser.add_argument(
1903+ '--flavor',
1904+ metavar='<flavor>',
1905+ help=_('The nova flavor name or UUID to use when launching the '
1906+ 'nodes in this NodeGroup.'))
1907+ parser.add_argument(
1908+ '--merge-labels',
1909+ dest='merge_labels',
1910+ action='store_true',
1911+ default=False,
1912+ help=_('The labels provided will be merged with the labels '
1913+ 'configured in the specified cluster.'))
1914+
1915+ return parser
1916+
1917+ def take_action(self, parsed_args):
1918+ self.log.debug("take_action(%s)", parsed_args)
1919+
1920+ mag_client = self.app.client_manager.container_infra
1921+ args = {
1922+ 'name': parsed_args.name,
1923+ 'node_count': parsed_args.node_count,
1924+ 'max_node_count': parsed_args.max_node_count,
1925+ 'min_node_count': parsed_args.min_node_count,
1926+ 'role': parsed_args.role,
1927+ }
1928+
1929+ if parsed_args.labels is not None:
1930+ args['labels'] = magnum_utils.handle_labels(parsed_args.labels)
1931+
1932+ if parsed_args.docker_volume_size is not None:
1933+ args['docker_volume_size'] = parsed_args.docker_volume_size
1934+
1935+ if parsed_args.flavor is not None:
1936+ args['flavor_id'] = parsed_args.flavor
1937+
1938+ if parsed_args.image is not None:
1939+ args['image_id'] = parsed_args.image
1940+
1941+ if parsed_args.merge_labels:
1942+ # We are only sending this if it's True. This
1943+ # way we avoid breaking older APIs.
1944+ args["merge_labels"] = parsed_args.merge_labels
1945+
1946+ cluster_id = parsed_args.cluster
1947+ nodegroup = mag_client.nodegroups.create(cluster_id, **args)
1948+ print("Request to create nodegroup %s accepted"
1949+ % nodegroup.uuid)
1950+
1951+
1952+class DeleteNodeGroup(command.Command):
1953+ _description = _("Delete a nodegroup")
1954+
1955+ def get_parser(self, prog_name):
1956+ parser = super(DeleteNodeGroup, self).get_parser(prog_name)
1957+ parser.add_argument(
1958+ 'cluster',
1959+ metavar='<cluster>',
1960+ help=_('ID or name of the cluster where the nodegroup(s) '
1961+ 'belong(s).'))
1962+ parser.add_argument(
1963+ 'nodegroup',
1964+ nargs='+',
1965+ metavar='<nodegroup>',
1966+ help='ID or name of the nodegroup(s) to delete.')
1967+
1968+ return parser
1969+
1970+ def take_action(self, parsed_args):
1971+ self.log.debug("take_action(%s)", parsed_args)
1972+
1973+ mag_client = self.app.client_manager.container_infra
1974+ cluster_id = parsed_args.cluster
1975+ for ng in parsed_args.nodegroup:
1976+ mag_client.nodegroups.delete(cluster_id, ng)
1977+ print("Request to delete nodegroup %s has been accepted." % ng)
1978+
1979+
1980+class ListNodeGroup(command.Lister):
1981+ _description = _("List nodegroups")
1982+
1983+ def get_parser(self, prog_name):
1984+ parser = super(ListNodeGroup, self).get_parser(prog_name)
1985+
1986+ parser.add_argument(
1987+ 'cluster',
1988+ metavar='<cluster>',
1989+ help=_('ID or name of the cluster where the nodegroup belongs.'))
1990+ parser.add_argument(
1991+ '--limit',
1992+ metavar='<limit>',
1993+ type=int,
1994+ help=_('Maximum number of nodegroups to return'))
1995+ parser.add_argument(
1996+ '--sort-key',
1997+ metavar='<sort-key>',
1998+ help=_('Column to sort results by'))
1999+ parser.add_argument(
2000+ '--sort-dir',
2001+ metavar='<sort-dir>',
2002+ choices=['desc', 'asc'],
2003+ help=_('Direction to sort. "asc" or "desc".'))
2004+ parser.add_argument(
2005+ '--role',
2006+ metavar='<role>',
2007+ help=_('List the nodegroups in the cluster with this role'))
2008+
2009+ return parser
2010+
2011+ def take_action(self, parsed_args):
2012+ self.log.debug("take_action(%s)", parsed_args)
2013+
2014+ mag_client = self.app.client_manager.container_infra
2015+ columns = ['uuid', 'name', 'flavor_id', 'image_id', 'node_count',
2016+ 'status', 'role']
2017+ cluster_id = parsed_args.cluster
2018+ nodegroups = mag_client.nodegroups.list(cluster_id,
2019+ limit=parsed_args.limit,
2020+ sort_key=parsed_args.sort_key,
2021+ sort_dir=parsed_args.sort_dir,
2022+ role=parsed_args.role)
2023+ return (
2024+ columns,
2025+ (utils.get_item_properties(n, columns) for n in nodegroups)
2026+ )
2027+
2028+
2029+class ShowNodeGroup(command.ShowOne):
2030+ _description = _("Show a nodegroup")
2031+
2032+ def get_parser(self, prog_name):
2033+ parser = super(ShowNodeGroup, self).get_parser(prog_name)
2034+ parser.add_argument(
2035+ 'cluster',
2036+ metavar='<cluster>',
2037+ help=_('ID or name of the cluster where the nodegroup belongs.'))
2038+ parser.add_argument(
2039+ 'nodegroup',
2040+ metavar='<nodegroup>',
2041+ help=_('ID or name of the nodegroup to show.')
2042+ )
2043+ return parser
2044+
2045+ def take_action(self, parsed_args):
2046+ self.log.debug("take_action(%s)", parsed_args)
2047+
2048+ columns = NODEGROUP_ATTRIBUTES
2049+
2050+ mag_client = self.app.client_manager.container_infra
2051+ cluster_id = parsed_args.cluster
2052+ nodegroup = mag_client.nodegroups.get(cluster_id,
2053+ parsed_args.nodegroup)
2054+
2055+ return (columns, utils.get_item_properties(nodegroup, columns))
2056+
2057+
2058+class UpdateNodeGroup(command.Command):
2059+ _description = _("Update a Nodegroup")
2060+
2061+ def get_parser(self, prog_name):
2062+ parser = super(UpdateNodeGroup, self).get_parser(prog_name)
2063+ parser.add_argument(
2064+ 'cluster',
2065+ metavar='<cluster>',
2066+ help=_('ID or name of the cluster where the nodegroup belongs.'))
2067+ parser.add_argument(
2068+ 'nodegroup',
2069+ metavar='<nodegroup>',
2070+ help=_('The name or UUID of cluster to update'))
2071+
2072+ parser.add_argument(
2073+ 'op',
2074+ metavar='<op>',
2075+ choices=['add', 'replace', 'remove'],
2076+ help=_("Operations: one of 'add', 'replace' or 'remove'"))
2077+
2078+ parser.add_argument(
2079+ 'attributes',
2080+ metavar='<path=value>',
2081+ nargs='+',
2082+ action='append',
2083+ default=[],
2084+ help=_(
2085+ "Attributes to add/replace or remove (only PATH is necessary "
2086+ "on remove)"))
2087+
2088+ return parser
2089+
2090+ def take_action(self, parsed_args):
2091+ self.log.debug("take_action(%s)", parsed_args)
2092+
2093+ mag_client = self.app.client_manager.container_infra
2094+
2095+ patch = magnum_utils.args_array_to_patch(parsed_args.op,
2096+ parsed_args.attributes[0])
2097+
2098+ cluster_id = parsed_args.cluster
2099+ mag_client.nodegroups.update(cluster_id, parsed_args.nodegroup,
2100+ patch)
2101+ print("Request to update nodegroup %s has been accepted." %
2102+ parsed_args.nodegroup)
2103diff --git a/magnumclient/shell.py b/magnumclient/shell.py
2104index 259f1ad..9da9255 100644
2105--- a/magnumclient/shell.py
2106+++ b/magnumclient/shell.py
2107@@ -33,6 +33,13 @@ from oslo_utils import importutils
2108 from oslo_utils import strutils
2109 import six
2110
2111+from magnumclient.common import cliutils
2112+from magnumclient import exceptions as exc
2113+from magnumclient.i18n import _
2114+from magnumclient.v1 import client as client_v1
2115+from magnumclient.v1 import shell as shell_v1
2116+from magnumclient import version
2117+
2118 profiler = importutils.try_import("osprofiler.profiler")
2119
2120 HAS_KEYRING = False
2121@@ -51,12 +58,6 @@ try:
2122 except ImportError:
2123 pass
2124
2125-from magnumclient.common import cliutils
2126-from magnumclient import exceptions as exc
2127-from magnumclient.i18n import _
2128-from magnumclient.v1 import client as client_v1
2129-from magnumclient.v1 import shell as shell_v1
2130-from magnumclient import version
2131
2132 LATEST_API_VERSION = ('1', 'latest')
2133 DEFAULT_INTERFACE = 'public'
2134diff --git a/magnumclient/tests/osc/unit/osc_fakes.py b/magnumclient/tests/osc/unit/osc_fakes.py
2135index 2df0686..b9e8086 100644
2136--- a/magnumclient/tests/osc/unit/osc_fakes.py
2137+++ b/magnumclient/tests/osc/unit/osc_fakes.py
2138@@ -13,13 +13,12 @@
2139 # under the License.
2140 #
2141
2142-import json
2143 import mock
2144+from oslo_serialization import jsonutils
2145 import sys
2146
2147 from keystoneauth1 import fixture
2148 import requests
2149-import six
2150
2151 AUTH_TOKEN = "foobar"
2152 AUTH_URL = "http://0.0.0.0"
2153@@ -239,9 +238,7 @@ class FakeResponse(requests.Response):
2154 self.status_code = status_code
2155
2156 self.headers.update(headers)
2157- self._content = json.dumps(data)
2158- if not isinstance(self._content, six.binary_type):
2159- self._content = self._content.encode()
2160+ self._content = jsonutils.dump_as_bytes(data)
2161
2162
2163 class FakeModel(dict):
2164diff --git a/magnumclient/tests/osc/unit/v1/fakes.py b/magnumclient/tests/osc/unit/v1/fakes.py
2165index df11d41..4181010 100644
2166--- a/magnumclient/tests/osc/unit/v1/fakes.py
2167+++ b/magnumclient/tests/osc/unit/v1/fakes.py
2168@@ -62,14 +62,38 @@ class FakeQuotasModelManager(object):
2169 pass
2170
2171
2172+class FakeNodeGroupManager(object):
2173+ def list(self, cluster_id, limit=None, marker=None, sort_key=None,
2174+ sort_dir=None, detail=False):
2175+ pass
2176+
2177+ def get(self, cluster_id, id):
2178+ pass
2179+
2180+ def create(self, cluster_id, **kwargs):
2181+ pass
2182+
2183+ def delete(self, cluster_id, id):
2184+ pass
2185+
2186+ def update(self, cluster_id, id, patch):
2187+ pass
2188+
2189+
2190+class FakeCertificatesModelManager(FakeBaseModelManager):
2191+ def get(self, cluster_uuid):
2192+ pass
2193+
2194+
2195 class MagnumFakeContainerInfra(object):
2196 def __init__(self):
2197 self.cluster_templates = FakeBaseModelManager()
2198 self.clusters = FakeBaseModelManager()
2199 self.mservices = FakeBaseModelManager()
2200- self.certificates = FakeBaseModelManager()
2201+ self.certificates = FakeCertificatesModelManager()
2202 self.stats = FakeStatsModelManager()
2203 self.quotas = FakeQuotasModelManager()
2204+ self.nodegroups = FakeNodeGroupManager()
2205
2206
2207 class MagnumFakeClientManager(osc_fakes.FakeClientManager):
2208@@ -160,7 +184,8 @@ class FakeClusterTemplate(object):
2209 'coe': 'kubernetes',
2210 'flavor_id': 'm1.medium',
2211 'master_lb_enabled': False,
2212- 'dns_nameserver': '8.8.8.8'
2213+ 'dns_nameserver': '8.8.8.8',
2214+ 'hidden': False
2215 }
2216
2217 # Overwrite default attributes.
2218@@ -205,12 +230,16 @@ class FakeCluster(object):
2219 # set default attributes.
2220 cluster_info = {
2221 'status': 'CREATE_IN_PROGRESS',
2222+ 'health_status': 'HEALTHY',
2223 'cluster_template_id': 'fake-ct',
2224 'node_addresses': [],
2225 'uuid': '3a369884-b6ba-484f-a206-919b4b718aff',
2226 'stack_id': 'c4554582-77bd-4734-8f1a-72c3c40e5fb4',
2227 'status_reason': None,
2228 'labels': {},
2229+ 'labels_overridden': {},
2230+ 'labels_added': {},
2231+ 'labels_skipped': {},
2232 'created_at': '2017-03-16T18:40:39+00:00',
2233 'updated_at': '2017-03-16T18:40:45+00:00',
2234 'coe_version': None,
2235@@ -227,6 +256,7 @@ class FakeCluster(object):
2236 'master_flavor_id': None,
2237 'flavor_id': 'm1.medium',
2238 'project_id': None,
2239+ 'health_status_reason': {'api': 'ok'}
2240 }
2241
2242 # Overwrite default attributes.
2243@@ -237,6 +267,11 @@ class FakeCluster(object):
2244 return cluster
2245
2246
2247+class FakeCert(object):
2248+ def __init__(self, pem):
2249+ self.pem = pem
2250+
2251+
2252 class FakeQuota(object):
2253 """Fake one or more Quota"""
2254
2255@@ -266,3 +301,52 @@ class FakeQuota(object):
2256 quota = osc_fakes.FakeResource(info=copy.deepcopy(quota_info),
2257 loaded=True)
2258 return quota
2259+
2260+
2261+class FakeNodeGroup(object):
2262+ """Fake one or more NodeGroup."""
2263+
2264+ @staticmethod
2265+ def create_one_nodegroup(attrs=None):
2266+ """Create a fake NodeGroup.
2267+
2268+ :param Dictionary attrs:
2269+ A dictionary with all attributes
2270+ :return:
2271+ A FakeResource object, with flavor_id, image_id, and so on
2272+ """
2273+
2274+ attrs = attrs or {}
2275+
2276+ # set default attributes.
2277+ nodegroup_info = {
2278+ 'created_at': '2017-03-16T18:40:39+00:00',
2279+ 'updated_at': '2017-03-16T18:40:45+00:00',
2280+ 'uuid': '3a369884-b6ba-484f-a206-919b4b718aff',
2281+ 'cluster_id': 'fake-cluster',
2282+ 'docker_volume_size': None,
2283+ 'node_addresses': [],
2284+ 'labels': {},
2285+ 'labels_overridden': {},
2286+ 'labels_added': {},
2287+ 'labels_skipped': {},
2288+ 'node_count': 1,
2289+ 'name': 'fake-nodegroup',
2290+ 'flavor_id': 'm1.medium',
2291+ 'image_id': 'fedora-latest',
2292+ 'project_id': None,
2293+ 'role': 'worker',
2294+ 'max_node_count': 10,
2295+ 'min_node_count': 1,
2296+ 'is_default': False,
2297+ 'stack_id': '3a369884-b6ba-484f-fake-stackb718aff',
2298+ 'status': 'CREATE_COMPLETE',
2299+ 'status_reason': 'None'
2300+ }
2301+
2302+ # Overwrite default attributes.
2303+ nodegroup_info.update(attrs)
2304+
2305+ nodegroup = osc_fakes.FakeResource(info=copy.deepcopy(nodegroup_info),
2306+ loaded=True)
2307+ return nodegroup
2308diff --git a/magnumclient/tests/osc/unit/v1/test_cluster_templates.py b/magnumclient/tests/osc/unit/v1/test_cluster_templates.py
2309index 5321687..b5c1c35 100644
2310--- a/magnumclient/tests/osc/unit/v1/test_cluster_templates.py
2311+++ b/magnumclient/tests/osc/unit/v1/test_cluster_templates.py
2312@@ -48,7 +48,7 @@ class TestClusterTemplate(magnum_fakes.TestMagnumClientOSCV1):
2313 'registry_enabled': False,
2314 'server_type': 'vm',
2315 'tls_disabled': False,
2316- 'volume_driver': None
2317+ 'volume_driver': None,
2318 }
2319
2320 def setUp(self):
2321@@ -271,22 +271,24 @@ class TestClusterTemplateList(TestClusterTemplate):
2322 '--limit', '1',
2323 '--sort-key', 'key',
2324 '--sort-dir', 'asc',
2325- '--fields', 'fields'
2326+ '--fields', 'field1,field2'
2327 ]
2328 verifylist = [
2329 ('limit', 1),
2330 ('sort_key', 'key'),
2331 ('sort_dir', 'asc'),
2332- ('fields', 'fields'),
2333+ ('fields', 'field1,field2'),
2334 ]
2335+ verifycolumns = self.columns + ['field1', 'field2']
2336 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2337
2338- self.cmd.take_action(parsed_args)
2339+ columns, data = self.cmd.take_action(parsed_args)
2340 self.cluster_templates_mock.list.assert_called_with(
2341 limit=1,
2342 sort_dir='asc',
2343 sort_key='key',
2344 )
2345+ self.assertEqual(verifycolumns, columns)
2346
2347 def test_cluster_template_list_bad_sort_dir_fail(self):
2348 arglist = [
2349diff --git a/magnumclient/tests/osc/unit/v1/test_clusters.py b/magnumclient/tests/osc/unit/v1/test_clusters.py
2350index 4783c13..2530796 100644
2351--- a/magnumclient/tests/osc/unit/v1/test_clusters.py
2352+++ b/magnumclient/tests/osc/unit/v1/test_clusters.py
2353@@ -37,6 +37,8 @@ class TestCluster(magnum_fakes.TestMagnumClientOSCV1):
2354 super(TestCluster, self).setUp()
2355
2356 self.clusters_mock = self.app.client_manager.container_infra.clusters
2357+ self.certificates_mock = \
2358+ self.app.client_manager.container_infra.certificates
2359
2360
2361 class TestClusterCreate(TestCluster):
2362@@ -181,7 +183,8 @@ class TestClusterList(TestCluster):
2363 'keypair',
2364 'node_count',
2365 'master_count',
2366- 'status'
2367+ 'status',
2368+ 'health_status'
2369 ]
2370
2371 datalist = (
2372@@ -191,7 +194,8 @@ class TestClusterList(TestCluster):
2373 _cluster.keypair,
2374 _cluster.node_count,
2375 _cluster.master_count,
2376- _cluster.status
2377+ _cluster.status,
2378+ _cluster.health_status,
2379 ),
2380 )
2381
2382@@ -360,10 +364,15 @@ class TestClusterConfig(TestCluster):
2383 self.clusters_mock.get = mock.Mock()
2384 self.clusters_mock.get.return_value = self._cluster
2385
2386+ cert = magnum_fakes.FakeCert(pem='foo bar')
2387+ self.certificates_mock.create = mock.Mock()
2388+ self.certificates_mock.create.return_value = cert
2389+ self.certificates_mock.get = mock.Mock()
2390+ self.certificates_mock.get.return_value = cert
2391+
2392 # Fake the cluster_template
2393 attr = dict()
2394 attr['name'] = 'fake-ct'
2395- attr['tls_disabled'] = True
2396 self._cluster_template = \
2397 magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr)
2398
2399@@ -459,14 +468,65 @@ export KUBECONFIG={}/config
2400
2401 self.clusters_mock.get.assert_called_with('fake-cluster')
2402
2403- def test_cluster_config_with_in_progress_status(self):
2404- self._cluster.status = 'CREATE_IN_PROGRESS'
2405
2406- arglist = ['fake-cluster-1']
2407+class TestClusterResize(TestCluster):
2408+
2409+ def setUp(self):
2410+ super(TestClusterResize, self).setUp()
2411+ self.cluster = mock.Mock()
2412+ self.cluster.uuid = "UUID1"
2413+ self.clusters_mock.resize = mock.Mock()
2414+ self.clusters_mock.resize.return_value = None
2415+
2416+ self.clusters_mock.get = mock.Mock()
2417+ self.clusters_mock.get.return_value = self.cluster
2418+
2419+ # Get the command object to test
2420+ self.cmd = osc_clusters.ResizeCluster(self.app, None)
2421+
2422+ def test_cluster_resize_pass(self):
2423+ arglist = ['foo', '2']
2424 verifylist = [
2425- ('cluster', 'fake-cluster-1')
2426+ ('cluster', 'foo'),
2427+ ('node_count', 2),
2428+ ('nodes_to_remove', None),
2429+ ('nodegroup', None)
2430 ]
2431+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2432+
2433+ self.cmd.take_action(parsed_args)
2434+ self.clusters_mock.resize.assert_called_with(
2435+ "UUID1", 2, None, None
2436+ )
2437+
2438+
2439+class TestClusterUpgrade(TestCluster):
2440
2441+ def setUp(self):
2442+ super(TestClusterUpgrade, self).setUp()
2443+ self.cluster = mock.Mock()
2444+ self.cluster.uuid = "UUID1"
2445+ self.clusters_mock.upgrade = mock.Mock()
2446+ self.clusters_mock.upgrade.return_value = None
2447+
2448+ self.clusters_mock.get = mock.Mock()
2449+ self.clusters_mock.get.return_value = self.cluster
2450+
2451+ # Get the command object to test
2452+ self.cmd = osc_clusters.UpgradeCluster(self.app, None)
2453+
2454+ def test_cluster_upgrade_pass(self):
2455+ cluster_template_id = 'TEMPLATE_ID'
2456+ arglist = ['foo', cluster_template_id]
2457+ verifylist = [
2458+ ('cluster', 'foo'),
2459+ ('cluster_template', cluster_template_id),
2460+ ('max_batch_size', 1),
2461+ ('nodegroup', None)
2462+ ]
2463 parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2464- self.assertRaises(exceptions.CommandError,
2465- self.cmd.take_action, parsed_args)
2466+
2467+ self.cmd.take_action(parsed_args)
2468+ self.clusters_mock.upgrade.assert_called_with(
2469+ "UUID1", cluster_template_id, 1, None
2470+ )
2471diff --git a/magnumclient/tests/osc/unit/v1/test_nodegroups.py b/magnumclient/tests/osc/unit/v1/test_nodegroups.py
2472new file mode 100644
2473index 0000000..06f1b55
2474--- /dev/null
2475+++ b/magnumclient/tests/osc/unit/v1/test_nodegroups.py
2476@@ -0,0 +1,333 @@
2477+# Copyright (c) 2018 European Organization for Nuclear Research.
2478+# All Rights Reserved.
2479+#
2480+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2481+# not use this file except in compliance with the License. You may obtain
2482+# a copy of the License at
2483+#
2484+# http://www.apache.org/licenses/LICENSE-2.0
2485+#
2486+# Unless required by applicable law or agreed to in writing, software
2487+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2488+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2489+# License for the specific language governing permissions and limitations
2490+# under the License.
2491+
2492+import copy
2493+import mock
2494+from mock import call
2495+
2496+from magnumclient.osc.v1 import nodegroups as osc_nodegroups
2497+from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes
2498+
2499+
2500+class TestNodeGroup(magnum_fakes.TestMagnumClientOSCV1):
2501+
2502+ def setUp(self):
2503+ super(TestNodeGroup, self).setUp()
2504+ self.ng_mock = self.app.client_manager.container_infra.nodegroups
2505+
2506+
2507+class TestNodeGroupCreate(TestNodeGroup):
2508+
2509+ def setUp(self):
2510+ super(TestNodeGroupCreate, self).setUp()
2511+ self.nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup()
2512+
2513+ self.ng_mock.create = mock.Mock()
2514+ self.ng_mock.create.return_value = self.nodegroup
2515+
2516+ self.ng_mock.get = mock.Mock()
2517+ self.ng_mock.get.return_value = copy.deepcopy(self.nodegroup)
2518+
2519+ self.ng_mock.update = mock.Mock()
2520+ self.ng_mock.update.return_value = self.nodegroup
2521+
2522+ self._default_args = {
2523+ 'name': 'fake-nodegroup',
2524+ 'node_count': 1,
2525+ 'role': 'worker',
2526+ 'min_node_count': 1,
2527+ 'max_node_count': None,
2528+ }
2529+
2530+ # Get the command object to test
2531+ self.cmd = osc_nodegroups.CreateNodeGroup(self.app, None)
2532+
2533+ self.data = tuple(map(lambda x: getattr(self.nodegroup, x),
2534+ osc_nodegroups.NODEGROUP_ATTRIBUTES))
2535+
2536+ def test_nodegroup_create_required_args_pass(self):
2537+ """Verifies required arguments."""
2538+
2539+ arglist = [
2540+ self.nodegroup.cluster_id,
2541+ self.nodegroup.name
2542+ ]
2543+ verifylist = [
2544+ ('cluster', self.nodegroup.cluster_id),
2545+ ('name', self.nodegroup.name)
2546+ ]
2547+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2548+ self.cmd.take_action(parsed_args)
2549+ self.ng_mock.create.assert_called_with(self.nodegroup.cluster_id,
2550+ **self._default_args)
2551+
2552+ def test_nodegroup_create_missing_required_arg(self):
2553+ """Verifies missing required arguments."""
2554+
2555+ arglist = [
2556+ self.nodegroup.name
2557+ ]
2558+ verifylist = [
2559+ ('name', self.nodegroup.name)
2560+ ]
2561+ self.assertRaises(magnum_fakes.MagnumParseException,
2562+ self.check_parser, self.cmd, arglist, verifylist)
2563+
2564+ def test_nodegroup_create_with_labels(self):
2565+ """Verifies labels are properly parsed when given as argument."""
2566+
2567+ expected_args = self._default_args
2568+ expected_args['labels'] = {
2569+ 'arg1': 'value1', 'arg2': 'value2'
2570+ }
2571+
2572+ arglist = [
2573+ '--labels', 'arg1=value1',
2574+ '--labels', 'arg2=value2',
2575+ self.nodegroup.cluster_id,
2576+ self.nodegroup.name
2577+ ]
2578+ verifylist = [
2579+ ('labels', ['arg1=value1', 'arg2=value2']),
2580+ ('name', self.nodegroup.name),
2581+ ('cluster', self.nodegroup.cluster_id)
2582+ ]
2583+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2584+ self.cmd.take_action(parsed_args)
2585+ self.ng_mock.create.assert_called_with(self.nodegroup.cluster_id,
2586+ **expected_args)
2587+
2588+
2589+class TestNodeGroupDelete(TestNodeGroup):
2590+
2591+ def setUp(self):
2592+ super(TestNodeGroupDelete, self).setUp()
2593+
2594+ self.ng_mock.delete = mock.Mock()
2595+ self.ng_mock.delete.return_value = None
2596+
2597+ # Get the command object to test
2598+ self.cmd = osc_nodegroups.DeleteNodeGroup(self.app, None)
2599+
2600+ def test_nodegroup_delete_one(self):
2601+ arglist = ['foo', 'fake-nodegroup']
2602+ verifylist = [
2603+ ('cluster', 'foo'),
2604+ ('nodegroup', ['fake-nodegroup'])
2605+ ]
2606+
2607+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2608+
2609+ self.cmd.take_action(parsed_args)
2610+ self.ng_mock.delete.assert_called_with('foo', 'fake-nodegroup')
2611+
2612+ def test_nodegroup_delete_multiple(self):
2613+ arglist = ['foo', 'fake-nodegroup1', 'fake-nodegroup2']
2614+ verifylist = [
2615+ ('cluster', 'foo'),
2616+ ('nodegroup', ['fake-nodegroup1', 'fake-nodegroup2'])
2617+ ]
2618+
2619+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2620+
2621+ self.cmd.take_action(parsed_args)
2622+ self.ng_mock.delete.assert_has_calls(
2623+ [call('foo', 'fake-nodegroup1'), call('foo', 'fake-nodegroup2')]
2624+ )
2625+
2626+ def test_nodegroup_delete_no_args(self):
2627+ arglist = []
2628+ verifylist = [
2629+ ('cluster', ''),
2630+ ('nodegroup', [])
2631+ ]
2632+
2633+ self.assertRaises(magnum_fakes.MagnumParseException,
2634+ self.check_parser, self.cmd, arglist, verifylist)
2635+
2636+
2637+class TestNodeGroupUpdate(TestNodeGroup):
2638+
2639+ def setUp(self):
2640+ super(TestNodeGroupUpdate, self).setUp()
2641+
2642+ self.ng_mock.update = mock.Mock()
2643+ self.ng_mock.update.return_value = None
2644+
2645+ # Get the command object to test
2646+ self.cmd = osc_nodegroups.UpdateNodeGroup(self.app, None)
2647+
2648+ def test_nodegroup_update_pass(self):
2649+ arglist = ['foo', 'ng1', 'remove', 'bar']
2650+ verifylist = [
2651+ ('cluster', 'foo'),
2652+ ('nodegroup', 'ng1'),
2653+ ('op', 'remove'),
2654+ ('attributes', [['bar']])
2655+ ]
2656+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2657+
2658+ self.cmd.take_action(parsed_args)
2659+ self.ng_mock.update.assert_called_with(
2660+ 'foo', 'ng1',
2661+ [{'op': 'remove', 'path': '/bar'}]
2662+ )
2663+
2664+ def test_nodegroup_update_bad_op(self):
2665+ arglist = ['cluster', 'ng1', 'foo', 'bar']
2666+ verifylist = [
2667+ ('cluster', 'cluster'),
2668+ ('nodegroup', 'ng1'),
2669+ ('op', 'foo'),
2670+ ('attributes', ['bar'])
2671+ ]
2672+
2673+ self.assertRaises(magnum_fakes.MagnumParseException,
2674+ self.check_parser, self.cmd, arglist, verifylist)
2675+
2676+
2677+class TestNodeGroupShow(TestNodeGroup):
2678+
2679+ def setUp(self):
2680+ super(TestNodeGroupShow, self).setUp()
2681+
2682+ self.nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup()
2683+ self.ng_mock.get = mock.Mock()
2684+ self.ng_mock.get.return_value = self.nodegroup
2685+
2686+ self.data = tuple(map(lambda x: getattr(self.nodegroup, x),
2687+ osc_nodegroups.NODEGROUP_ATTRIBUTES))
2688+
2689+ # Get the command object to test
2690+ self.cmd = osc_nodegroups.ShowNodeGroup(self.app, None)
2691+
2692+ def test_nodegroup_show_pass(self):
2693+ arglist = ['fake-cluster', 'fake-nodegroup']
2694+ verifylist = [
2695+ ('cluster', 'fake-cluster'),
2696+ ('nodegroup', 'fake-nodegroup')
2697+ ]
2698+
2699+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2700+
2701+ columns, data = self.cmd.take_action(parsed_args)
2702+ self.ng_mock.get.assert_called_with(
2703+ 'fake-cluster', 'fake-nodegroup')
2704+ self.assertEqual(osc_nodegroups.NODEGROUP_ATTRIBUTES, columns)
2705+ self.assertEqual(self.data, data)
2706+
2707+ def test_nodegroup_show_no_nodegroup_fail(self):
2708+ arglist = ['fake-cluster']
2709+ verifylist = [
2710+ ('cluster', 'fake-cluster'),
2711+ ('nodegroup', '')
2712+ ]
2713+
2714+ self.assertRaises(magnum_fakes.MagnumParseException,
2715+ self.check_parser, self.cmd, arglist, verifylist)
2716+
2717+ def test_nodegroup_show_no_args(self):
2718+ arglist = []
2719+ verifylist = [
2720+ ('cluster', ''),
2721+ ('nodegroup', '')
2722+ ]
2723+
2724+ self.assertRaises(magnum_fakes.MagnumParseException,
2725+ self.check_parser, self.cmd, arglist, verifylist)
2726+
2727+
2728+class TestNodeGroupList(TestNodeGroup):
2729+
2730+ nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup()
2731+
2732+ columns = ['uuid', 'name', 'flavor_id', 'image_id', 'node_count',
2733+ 'status', 'role']
2734+
2735+ datalist = (
2736+ (
2737+ nodegroup.uuid,
2738+ nodegroup.name,
2739+ nodegroup.flavor_id,
2740+ nodegroup.image_id,
2741+ nodegroup.node_count,
2742+ nodegroup.status,
2743+ nodegroup.role,
2744+ ),
2745+ )
2746+
2747+ def setUp(self):
2748+ super(TestNodeGroupList, self).setUp()
2749+ self.ng_mock.list = mock.Mock()
2750+ self.ng_mock.list.return_value = [self.nodegroup]
2751+
2752+ # Get the command object to test
2753+ self.cmd = osc_nodegroups.ListNodeGroup(self.app, None)
2754+
2755+ def test_nodegroup_list_no_options(self):
2756+ arglist = []
2757+ verifylist = [
2758+ ('cluster', ''),
2759+ ('limit', None),
2760+ ('sort_key', None),
2761+ ('sort_dir', None),
2762+ ]
2763+ self.assertRaises(magnum_fakes.MagnumParseException,
2764+ self.check_parser, self.cmd, arglist, verifylist)
2765+
2766+ def test_nodegroup_list_ok(self):
2767+ arglist = ['fake-cluster']
2768+ verifylist = [
2769+ ('cluster', 'fake-cluster'),
2770+ ('limit', None),
2771+ ('sort_key', None),
2772+ ('sort_dir', None),
2773+ ]
2774+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2775+
2776+ columns, data = self.cmd.take_action(parsed_args)
2777+ self.ng_mock.list.assert_called_with(
2778+ 'fake-cluster',
2779+ limit=None,
2780+ sort_dir=None,
2781+ sort_key=None,
2782+ role=None,
2783+ )
2784+ self.assertEqual(self.columns, columns)
2785+ self.assertEqual(self.datalist, tuple(data))
2786+
2787+ def test_nodegroup_list_options(self):
2788+ arglist = [
2789+ 'fake-cluster',
2790+ '--limit', '1',
2791+ '--sort-key', 'key',
2792+ '--sort-dir', 'asc'
2793+ ]
2794+ verifylist = [
2795+ ('cluster', 'fake-cluster'),
2796+ ('limit', 1),
2797+ ('sort_key', 'key'),
2798+ ('sort_dir', 'asc')
2799+ ]
2800+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2801+
2802+ self.cmd.take_action(parsed_args)
2803+ self.ng_mock.list.assert_called_with(
2804+ 'fake-cluster',
2805+ limit=1,
2806+ sort_dir='asc',
2807+ sort_key='key',
2808+ role=None
2809+ )
2810diff --git a/magnumclient/tests/test_httpclient.py b/magnumclient/tests/test_httpclient.py
2811index 205ff14..f045c9c 100644
2812--- a/magnumclient/tests/test_httpclient.py
2813+++ b/magnumclient/tests/test_httpclient.py
2814@@ -13,9 +13,8 @@
2815 # License for the specific language governing permissions and limitations
2816 # under the License.
2817
2818-import json
2819-
2820 import mock
2821+from oslo_serialization import jsonutils
2822 import six
2823 import socket
2824
2825@@ -37,7 +36,7 @@ def _get_error_body(faultstring=None, debuginfo=None, err_type=NORMAL_ERROR):
2826 'faultstring': faultstring,
2827 'debuginfo': debuginfo
2828 }
2829- raw_error_body = json.dumps(error_body)
2830+ raw_error_body = jsonutils.dumps(error_body)
2831 body = {'error_message': raw_error_body}
2832 elif err_type == ERROR_DICT:
2833 body = {'error': {'title': faultstring, 'message': debuginfo}}
2834@@ -47,7 +46,7 @@ def _get_error_body(faultstring=None, debuginfo=None, err_type=NORMAL_ERROR):
2835 elif err_type == ERROR_LIST_WITH_DESC:
2836 main_body = {'title': faultstring, 'description': debuginfo}
2837 body = {'errors': [main_body]}
2838- raw_body = json.dumps(body)
2839+ raw_body = jsonutils.dumps(body)
2840 return raw_body
2841
2842
2843@@ -347,7 +346,7 @@ class HttpClientTest(utils.BaseTestCase):
2844 resp, body = client.json_request('GET', '/v1/resources')
2845
2846 self.assertEqual(resp, fake_resp)
2847- self.assertEqual(json.dumps(body), err)
2848+ self.assertEqual(jsonutils.dumps(body), err)
2849
2850 def test_raw_request(self):
2851 fake_resp = utils.FakeResponse(
2852diff --git a/magnumclient/tests/test_utils.py b/magnumclient/tests/test_utils.py
2853index 93a96fe..831fea4 100644
2854--- a/magnumclient/tests/test_utils.py
2855+++ b/magnumclient/tests/test_utils.py
2856@@ -17,7 +17,7 @@
2857
2858 import collections
2859 import mock
2860-from oslo_serialization import jsonutils as json
2861+from oslo_serialization import jsonutils
2862 import six
2863 import six.moves.builtins as __builtin__
2864 import tempfile
2865@@ -123,7 +123,7 @@ class FormatLabelsTest(test_utils.BaseTestCase):
2866 self.assertEqual({}, utils.format_labels(None))
2867
2868 def test_format_labels(self):
2869- l = utils.format_labels([
2870+ la = utils.format_labels([
2871 'K1=V1,K2=V2,'
2872 'K3=V3,K4=V4,'
2873 'K5=V5'])
2874@@ -132,10 +132,10 @@ class FormatLabelsTest(test_utils.BaseTestCase):
2875 'K3': 'V3',
2876 'K4': 'V4',
2877 'K5': 'V5'
2878- }, l)
2879+ }, la)
2880
2881 def test_format_labels_semicolon(self):
2882- l = utils.format_labels([
2883+ la = utils.format_labels([
2884 'K1=V1;K2=V2;'
2885 'K3=V3;K4=V4;'
2886 'K5=V5'])
2887@@ -144,10 +144,10 @@ class FormatLabelsTest(test_utils.BaseTestCase):
2888 'K3': 'V3',
2889 'K4': 'V4',
2890 'K5': 'V5'
2891- }, l)
2892+ }, la)
2893
2894 def test_format_labels_mix_commas_semicolon(self):
2895- l = utils.format_labels([
2896+ la = utils.format_labels([
2897 'K1=V1,K2=V2,'
2898 'K3=V3;K4=V4,'
2899 'K5=V5'])
2900@@ -156,10 +156,10 @@ class FormatLabelsTest(test_utils.BaseTestCase):
2901 'K3': 'V3',
2902 'K4': 'V4',
2903 'K5': 'V5'
2904- }, l)
2905+ }, la)
2906
2907 def test_format_labels_split(self):
2908- l = utils.format_labels([
2909+ la = utils.format_labels([
2910 'K1=V1,'
2911 'K2=V22222222222222222222222222222'
2912 '222222222222222222222222222,'
2913@@ -167,10 +167,10 @@ class FormatLabelsTest(test_utils.BaseTestCase):
2914 self.assertEqual({'K1': 'V1',
2915 'K2': 'V22222222222222222222222222222'
2916 '222222222222222222222222222',
2917- 'K3': '3.3.3.3'}, l)
2918+ 'K3': '3.3.3.3'}, la)
2919
2920 def test_format_labels_multiple(self):
2921- l = utils.format_labels([
2922+ la = utils.format_labels([
2923 'K1=V1',
2924 'K2=V22222222222222222222222222222'
2925 '222222222222222222222222222',
2926@@ -178,35 +178,35 @@ class FormatLabelsTest(test_utils.BaseTestCase):
2927 self.assertEqual({'K1': 'V1',
2928 'K2': 'V22222222222222222222222222222'
2929 '222222222222222222222222222',
2930- 'K3': '3.3.3.3'}, l)
2931+ 'K3': '3.3.3.3'}, la)
2932
2933 def test_format_labels_multiple_colon_values(self):
2934- l = utils.format_labels([
2935+ la = utils.format_labels([
2936 'K1=V1',
2937 'K2=V2,V22,V222,V2222',
2938 'K3=3.3.3.3'])
2939 self.assertEqual({'K1': 'V1',
2940 'K2': 'V2,V22,V222,V2222',
2941- 'K3': '3.3.3.3'}, l)
2942+ 'K3': '3.3.3.3'}, la)
2943
2944 def test_format_labels_parse_comma_false(self):
2945- l = utils.format_labels(
2946+ la = utils.format_labels(
2947 ['K1=V1,K2=2.2.2.2,K=V'],
2948 parse_comma=False)
2949- self.assertEqual({'K1': 'V1,K2=2.2.2.2,K=V'}, l)
2950+ self.assertEqual({'K1': 'V1,K2=2.2.2.2,K=V'}, la)
2951
2952 def test_format_labels_multiple_values_per_labels(self):
2953- l = utils.format_labels([
2954+ la = utils.format_labels([
2955 'K1=V1',
2956 'K1=V2'])
2957- self.assertEqual({'K1': 'V1,V2'}, l)
2958+ self.assertEqual({'K1': 'V1,V2'}, la)
2959
2960 def test_format_label_special_label(self):
2961 labels = ['K1=V1,K22.2.2.2']
2962- l = utils.format_labels(
2963+ la = utils.format_labels(
2964 labels,
2965 parse_comma=True)
2966- self.assertEqual({'K1': 'V1,K22.2.2.2'}, l)
2967+ self.assertEqual({'K1': 'V1,K22.2.2.2'}, la)
2968
2969 def test_format_multiple_bad_label(self):
2970 labels = ['K1=V1', 'K22.2.2.2']
2971@@ -261,7 +261,7 @@ class HandleJsonFromFileTest(test_utils.BaseTestCase):
2972 f.flush()
2973 steps = utils.handle_json_from_file(f.name)
2974
2975- self.assertEqual(json.loads(contents), steps)
2976+ self.assertEqual(jsonutils.loads(contents), steps)
2977
2978 @mock.patch.object(__builtin__, 'open', autospec=True)
2979 def test_handle_json_from_file_open_fail(self, mock_open):
2980diff --git a/magnumclient/tests/utils.py b/magnumclient/tests/utils.py
2981index c155ae0..1562671 100644
2982--- a/magnumclient/tests/utils.py
2983+++ b/magnumclient/tests/utils.py
2984@@ -15,8 +15,8 @@
2985
2986 import copy
2987 import datetime
2988-import json as jsonlib
2989 import os
2990+from oslo_serialization import jsonutils
2991 import sys
2992
2993 import fixtures
2994@@ -95,7 +95,7 @@ class FakeResponse(object):
2995 self.reason = reason
2996
2997 def __getitem__(self, key):
2998- if key is 'location':
2999+ if key == 'location':
3000 return 'fake_url'
3001 else:
3002 return None
3003@@ -182,7 +182,7 @@ class FakeSessionResponse(object):
3004
3005 def json(self):
3006 if self.content is not None:
3007- return jsonlib.loads(self.content)
3008+ return jsonutils.loads(self.content)
3009 else:
3010 return {}
3011
3012diff --git a/magnumclient/tests/v1/shell_test_base.py b/magnumclient/tests/v1/shell_test_base.py
3013index 89f6195..b165298 100644
3014--- a/magnumclient/tests/v1/shell_test_base.py
3015+++ b/magnumclient/tests/v1/shell_test_base.py
3016@@ -28,56 +28,56 @@ FAKE_ENV = {'OS_USERNAME': 'username',
3017
3018 class TestCommandLineArgument(utils.TestCase):
3019 _unrecognized_arg_error = [
3020- '.*?^usage: ',
3021- '.*?^error: unrecognized arguments:',
3022- ".*?^Try 'magnum help ' for more information.",
3023+ r'.*?^usage: ',
3024+ r'.*?^error: unrecognized arguments:',
3025+ r".*?^Try 'magnum help ' for more information.",
3026 ]
3027
3028 _mandatory_group_arg_error = [
3029- '.*?^usage: ',
3030- '.*?^error: one of the arguments',
3031- ".*?^Try 'magnum help ",
3032+ r'.*?^usage: ',
3033+ r'.*?^error: one of the arguments',
3034+ r".*?^Try 'magnum help ",
3035 ]
3036
3037 _too_many_group_arg_error = [
3038- '.*?^usage: ',
3039- '.*?^error: (argument \-\-[a-z\-]*: not allowed with argument )',
3040- ".*?^Try 'magnum help ",
3041+ r'.*?^usage: ',
3042+ r'.*?^error: (argument \-\-[a-z\-]*: not allowed with argument )',
3043+ r".*?^Try 'magnum help ",
3044 ]
3045
3046 _mandatory_arg_error = [
3047- '.*?^usage: ',
3048- '.*?^error: (the following arguments|argument)',
3049- ".*?^Try 'magnum help ",
3050- ]
3051+ r'.*?^usage: ',
3052+ r'.*?^error: (the following arguments|argument)',
3053+ r".*?^Try 'magnum help ",
3054+ ]
3055
3056 _duplicate_arg_error = [
3057- '.*?^usage: ',
3058- '.*?^error: (Duplicate "<.*>" arguments:)',
3059- ".*?^Try 'magnum help ",
3060- ]
3061+ r'.*?^usage: ',
3062+ r'.*?^error: (Duplicate "<.*>" arguments:)',
3063+ r".*?^Try 'magnum help ",
3064+ ]
3065
3066 _deprecated_warning = [
3067- '.*(WARNING: The \-\-[a-z\-]* parameter is deprecated)+',
3068- ('.*(Use the [\<\-a-z\-\>]* (positional )*parameter to avoid seeing '
3069+ r'.*(WARNING: The \-\-[a-z\-]* parameter is deprecated)+',
3070+ (r'.*(Use the [\<\-a-z\-\>]* (positional )*parameter to avoid seeing '
3071 'this message)+')
3072 ]
3073
3074 _few_argument_error = [
3075- '.*?^usage: magnum ',
3076- '.*?^error: (the following arguments|too few arguments)',
3077- ".*?^Try 'magnum help ",
3078- ]
3079+ r'.*?^usage: magnum ',
3080+ r'.*?^error: (the following arguments|too few arguments)',
3081+ r".*?^Try 'magnum help ",
3082+ ]
3083
3084 _invalid_value_error = [
3085- '.*?^usage: ',
3086- '.*?^error: argument .*: invalid .* value:',
3087- ".*?^Try 'magnum help ",
3088- ]
3089+ r'.*?^usage: ',
3090+ r'.*?^error: argument .*: invalid .* value:',
3091+ r".*?^Try 'magnum help ",
3092+ ]
3093
3094 _bay_status_error = [
3095- '.*?^Bay status for',
3096- ]
3097+ r'.*?^Bay status for',
3098+ ]
3099
3100 def setUp(self):
3101 super(TestCommandLineArgument, self).setUp()
3102diff --git a/magnumclient/tests/v1/test_bays_shell.py b/magnumclient/tests/v1/test_bays_shell.py
3103index e9a3a47..60f512f 100644
3104--- a/magnumclient/tests/v1/test_bays_shell.py
3105+++ b/magnumclient/tests/v1/test_bays_shell.py
3106@@ -329,15 +329,6 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
3107 self._test_arg_success('bay-config --dir /tmp --force xxx')
3108 mock_bay.assert_called_with('xxx')
3109
3110- @mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
3111- @mock.patch('magnumclient.v1.bays.BayManager.get')
3112- def test_bay_config_failure_wrong_status(self, mock_bay, mock_baymodel):
3113- mock_bay.return_value = FakeBay(status='CREATE_IN_PROGRESS')
3114- self.assertRaises(exceptions.CommandError,
3115- self._test_arg_failure,
3116- 'bay-config xxx',
3117- ['.*?^Bay in status: '])
3118-
3119 @mock.patch('magnumclient.v1.bays.BayManager.get')
3120 def test_bay_config_failure_no_arg(self, mock_bay):
3121 self._test_arg_failure('bay-config', self._few_argument_error)
3122diff --git a/magnumclient/tests/v1/test_clusters.py b/magnumclient/tests/v1/test_clusters.py
3123index dc1a100..ee47fc0 100644
3124--- a/magnumclient/tests/v1/test_clusters.py
3125+++ b/magnumclient/tests/v1/test_clusters.py
3126@@ -54,6 +54,14 @@ UPDATED_CLUSTER = copy.deepcopy(CLUSTER1)
3127 NEW_NAME = 'newcluster'
3128 UPDATED_CLUSTER['name'] = NEW_NAME
3129
3130+RESIZED_CLUSTER = copy.deepcopy(CLUSTER1)
3131+RESIZED_NODE_COUNT = 5
3132+UPDATED_CLUSTER['node_count'] = RESIZED_NODE_COUNT
3133+
3134+UPGRADED_CLUSTER = copy.deepcopy(CLUSTER1)
3135+UPGRADED_TO_TEMPLATE = "eabbc463-0d3f-49dc-8519-cb6b59507bd6"
3136+UPGRADED_CLUSTER['cluster_template_id'] = UPGRADED_TO_TEMPLATE
3137+
3138 fake_responses = {
3139 '/v1/clusters':
3140 {
3141@@ -145,6 +153,20 @@ fake_responses = {
3142 {'clusters': [CLUSTER2, CLUSTER1]},
3143 ),
3144 },
3145+ '/v1/clusters/%s/actions/resize' % CLUSTER1['uuid']:
3146+ {
3147+ 'POST': (
3148+ {},
3149+ UPDATED_CLUSTER
3150+ ),
3151+ },
3152+ '/v1/clusters/%s/actions/upgrade' % CLUSTER1['uuid']:
3153+ {
3154+ 'POST': (
3155+ {},
3156+ UPGRADED_CLUSTER
3157+ ),
3158+ }
3159 }
3160
3161
3162@@ -355,3 +377,24 @@ class ClusterManagerTest(testtools.TestCase):
3163 ]
3164 self.assertEqual(expect, self.api.calls)
3165 self.assertEqual(NEW_NAME, cluster.name)
3166+
3167+ def test_cluster_resize(self):
3168+ body = {'node_count': RESIZED_NODE_COUNT}
3169+ cluster = self.mgr.resize(CLUSTER1["uuid"], **body)
3170+ expect = [
3171+ ('POST', '/v1/clusters/%s/actions/resize' % CLUSTER1['uuid'],
3172+ {}, body),
3173+ ]
3174+ self.assertEqual(expect, self.api.calls)
3175+ self.assertEqual(RESIZED_NODE_COUNT, cluster.node_count)
3176+
3177+ def test_cluster_upgrade(self):
3178+ body = {'cluster_template': UPGRADED_TO_TEMPLATE,
3179+ 'max_batch_size': 1}
3180+ cluster = self.mgr.upgrade(CLUSTER1["uuid"], **body)
3181+ expect = [
3182+ ('POST', '/v1/clusters/%s/actions/upgrade' % CLUSTER1['uuid'],
3183+ {}, body),
3184+ ]
3185+ self.assertEqual(expect, self.api.calls)
3186+ self.assertEqual(UPGRADED_TO_TEMPLATE, cluster.cluster_template_id)
3187diff --git a/magnumclient/tests/v1/test_clusters_shell.py b/magnumclient/tests/v1/test_clusters_shell.py
3188index 244132c..43d81d1 100644
3189--- a/magnumclient/tests/v1/test_clusters_shell.py
3190+++ b/magnumclient/tests/v1/test_clusters_shell.py
3191@@ -447,17 +447,6 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
3192 self._test_arg_success('cluster-config --dir /tmp --force xxx')
3193 mock_cluster.assert_called_with('xxx')
3194
3195- @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
3196- @mock.patch('magnumclient.v1.clusters.ClusterManager.get')
3197- def test_cluster_config_failure_wrong_status(self,
3198- mock_cluster,
3199- mock_clustertemplate):
3200- mock_cluster.return_value = FakeCluster(status='CREATE_IN_PROGRESS')
3201- self.assertRaises(exceptions.CommandError,
3202- self._test_arg_failure,
3203- 'cluster-config xxx',
3204- ['.*?^Cluster in status: '])
3205-
3206 @mock.patch('magnumclient.v1.clusters.ClusterManager.get')
3207 def test_cluster_config_failure_no_arg(self, mock_cluster):
3208 self._test_arg_failure('cluster-config', self._few_argument_error)
3209diff --git a/magnumclient/tests/v1/test_clustertemplates.py b/magnumclient/tests/v1/test_clustertemplates.py
3210index 0471b1a..2beb78e 100644
3211--- a/magnumclient/tests/v1/test_clustertemplates.py
3212+++ b/magnumclient/tests/v1/test_clustertemplates.py
3213@@ -46,7 +46,8 @@ CLUSTERTEMPLATE1 = {
3214 'public': False,
3215 'registry_enabled': False,
3216 'master_lb_enabled': True,
3217- 'floating_ip_enabled': True
3218+ 'floating_ip_enabled': True,
3219+ 'hidden': False
3220 }
3221
3222 CLUSTERTEMPLATE2 = {
3223@@ -309,6 +310,8 @@ class ClusterTemplateManagerTest(testtools.TestCase):
3224 cluster_template.master_lb_enabled)
3225 self.assertEqual(CLUSTERTEMPLATE1['floating_ip_enabled'],
3226 cluster_template.floating_ip_enabled)
3227+ self.assertEqual(CLUSTERTEMPLATE1['hidden'],
3228+ cluster_template.hidden)
3229
3230 def test_clustertemplate_show_by_name(self):
3231 cluster_template = self.mgr.get(CLUSTERTEMPLATE1['name'])
3232@@ -355,6 +358,8 @@ class ClusterTemplateManagerTest(testtools.TestCase):
3233 cluster_template.master_lb_enabled)
3234 self.assertEqual(CLUSTERTEMPLATE1['floating_ip_enabled'],
3235 cluster_template.floating_ip_enabled)
3236+ self.assertEqual(CLUSTERTEMPLATE1['hidden'],
3237+ cluster_template.hidden)
3238
3239 def test_clustertemplate_create(self):
3240 cluster_template = self.mgr.create(**CREATE_CLUSTERTEMPLATE)
3241diff --git a/magnumclient/tests/v1/test_clustertemplates_shell.py b/magnumclient/tests/v1/test_clustertemplates_shell.py
3242index 67bce87..ca17a4a 100644
3243--- a/magnumclient/tests/v1/test_clustertemplates_shell.py
3244+++ b/magnumclient/tests/v1/test_clustertemplates_shell.py
3245@@ -32,6 +32,7 @@ class FakeClusterTemplate(ClusterTemplate):
3246 self.coe = kwargs.get('coe', 'x')
3247 self.public = kwargs.get('public', False)
3248 self.name = kwargs.get('name', 'x')
3249+ self.hidden = kwargs.get('hidden', False)
3250
3251
3252 class ShellTest(shell_test_base.TestCommandLineArgument):
3253@@ -58,7 +59,7 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
3254 tls_disabled=False, public=False,
3255 master_lb_enabled=False, server_type='vm',
3256 registry_enabled=False,
3257- insecure_registry=None):
3258+ insecure_registry=None, hidden=False):
3259
3260 expected_args = {}
3261 expected_args['image_id'] = image_id
3262@@ -85,6 +86,7 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
3263 expected_args['server_type'] = server_type
3264 expected_args['registry_enabled'] = registry_enabled
3265 expected_args['insecure_registry'] = insecure_registry
3266+ expected_args['hidden'] = hidden
3267
3268 return expected_args
3269
3270diff --git a/magnumclient/tests/v1/test_nodegroups.py b/magnumclient/tests/v1/test_nodegroups.py
3271new file mode 100644
3272index 0000000..40cbcf6
3273--- /dev/null
3274+++ b/magnumclient/tests/v1/test_nodegroups.py
3275@@ -0,0 +1,333 @@
3276+# Copyright (c) 2018 European Organization for Nuclear Research.
3277+# All Rights Reserved.
3278+#
3279+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3280+# not use this file except in compliance with the License. You may obtain
3281+# a copy of the License at
3282+#
3283+# http://www.apache.org/licenses/LICENSE-2.0
3284+#
3285+# Unless required by applicable law or agreed to in writing, software
3286+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3287+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3288+# License for the specific language governing permissions and limitations
3289+# under the License.
3290+
3291+import copy
3292+
3293+import testtools
3294+from testtools import matchers
3295+
3296+from magnumclient import exceptions
3297+from magnumclient.tests import utils
3298+from magnumclient.v1 import nodegroups
3299+
3300+
3301+NODEGROUP1 = {
3302+ 'id': 123,
3303+ 'uuid': '66666666-7777-8888-9999-000000000001',
3304+ 'cluster_id': '66666666-7777-8888-9999-000000000000',
3305+ 'name': 'test-worker',
3306+ 'node_addresses': ['172.17.2.3'],
3307+ 'node_count': 2,
3308+ 'project_id': 'fake_project',
3309+ 'labels': {},
3310+ 'flavor_id': 'fake_flavor_1',
3311+ 'image_id': 'fake_image',
3312+ 'is_default': True,
3313+ 'role': 'worker',
3314+ 'max_node_count': 10,
3315+ 'min_node_count': 1
3316+}
3317+NODEGROUP2 = {
3318+ 'id': 124,
3319+ 'uuid': '66666666-7777-8888-9999-000000000002',
3320+ 'cluster_id': '66666666-7777-8888-9999-000000000000',
3321+ 'name': 'test-master',
3322+ 'node_addresses': ['172.17.2.4'],
3323+ 'node_count': 2,
3324+ 'project_id': 'fake_project',
3325+ 'labels': {},
3326+ 'flavor_id': 'fake_flavor_1',
3327+ 'image_id': 'fake_image',
3328+ 'is_default': True,
3329+ 'role': 'master',
3330+ 'max_node_count': 10,
3331+ 'min_node_count': 1
3332+}
3333+
3334+CREATE_NODEGROUP = copy.deepcopy(NODEGROUP1)
3335+del CREATE_NODEGROUP['id']
3336+del CREATE_NODEGROUP['uuid']
3337+del CREATE_NODEGROUP['node_addresses']
3338+del CREATE_NODEGROUP['is_default']
3339+del CREATE_NODEGROUP['cluster_id']
3340+
3341+UPDATED_NODEGROUP = copy.deepcopy(NODEGROUP1)
3342+NEW_NODE_COUNT = 9
3343+UPDATED_NODEGROUP['node_count'] = NEW_NODE_COUNT
3344+
3345+
3346+fake_responses = {
3347+ '/v1/clusters/test/nodegroups/':
3348+ {
3349+ 'GET': (
3350+ {},
3351+ {'nodegroups': [NODEGROUP1, NODEGROUP2]},
3352+ ),
3353+ 'POST': (
3354+ {},
3355+ CREATE_NODEGROUP,
3356+ ),
3357+ },
3358+ '/v1/clusters/test/nodegroups/%s' % NODEGROUP1['id']:
3359+ {
3360+ 'GET': (
3361+ {},
3362+ NODEGROUP1
3363+ ),
3364+ 'DELETE': (
3365+ {},
3366+ None,
3367+ ),
3368+ 'PATCH': (
3369+ {},
3370+ UPDATED_NODEGROUP,
3371+ ),
3372+ },
3373+ '/v1/clusters/test/nodegroups/%s/?rollback=True' % NODEGROUP1['id']:
3374+ {
3375+ 'PATCH': (
3376+ {},
3377+ UPDATED_NODEGROUP,
3378+ ),
3379+ },
3380+ '/v1/clusters/test/nodegroups/%s' % NODEGROUP1['name']:
3381+ {
3382+ 'GET': (
3383+ {},
3384+ NODEGROUP1
3385+ ),
3386+ 'DELETE': (
3387+ {},
3388+ None,
3389+ ),
3390+ 'PATCH': (
3391+ {},
3392+ UPDATED_NODEGROUP,
3393+ ),
3394+ },
3395+ '/v1/clusters/test/nodegroups/?limit=2':
3396+ {
3397+ 'GET': (
3398+ {},
3399+ {'nodegroups': [NODEGROUP1, NODEGROUP2]},
3400+ ),
3401+ },
3402+ '/v1/clusters/test/nodegroups/?marker=%s' % NODEGROUP2['uuid']:
3403+ {
3404+ 'GET': (
3405+ {},
3406+ {'nodegroups': [NODEGROUP1, NODEGROUP2]},
3407+ ),
3408+ },
3409+ '/v1/clusters/test/nodegroups/?limit=2&marker=%s' % NODEGROUP2['uuid']:
3410+ {
3411+ 'GET': (
3412+ {},
3413+ {'nodegroups': [NODEGROUP1, NODEGROUP2]},
3414+ ),
3415+ },
3416+ '/v1/clusters/test/nodegroups/?sort_dir=asc':
3417+ {
3418+ 'GET': (
3419+ {},
3420+ {'nodegroups': [NODEGROUP1, NODEGROUP2]},
3421+ ),
3422+ },
3423+ '/v1/clusters/test/nodegroups/?sort_key=uuid':
3424+ {
3425+ 'GET': (
3426+ {},
3427+ {'nodegroups': [NODEGROUP1, NODEGROUP2]},
3428+ ),
3429+ },
3430+ '/v1/clusters/test/nodegroups/?sort_key=uuid&sort_dir=desc':
3431+ {
3432+ 'GET': (
3433+ {},
3434+ {'nodegroups': [NODEGROUP2, NODEGROUP1]},
3435+ ),
3436+ },
3437+}
3438+
3439+
3440+class NodeGroupManagerTest(testtools.TestCase):
3441+
3442+ def setUp(self):
3443+ super(NodeGroupManagerTest, self).setUp()
3444+ self.api = utils.FakeAPI(fake_responses)
3445+ self.mgr = nodegroups.NodeGroupManager(self.api)
3446+ self.cluster_id = 'test'
3447+ self.base_path = '/v1/clusters/test/nodegroups/'
3448+
3449+ def test_nodegroup_list(self):
3450+ clusters = self.mgr.list(self.cluster_id)
3451+ expect = [
3452+ ('GET', self.base_path, {}, None),
3453+ ]
3454+ self.assertEqual(expect, self.api.calls)
3455+ self.assertThat(clusters, matchers.HasLength(2))
3456+
3457+ def _test_nodegroup_list_with_filters(self, cluster_id, limit=None,
3458+ marker=None, sort_key=None,
3459+ sort_dir=None, detail=False,
3460+ expect=[]):
3461+ nodegroup_filter = self.mgr.list(cluster_id,
3462+ limit=limit,
3463+ marker=marker,
3464+ sort_key=sort_key,
3465+ sort_dir=sort_dir,
3466+ detail=detail)
3467+ self.assertEqual(expect, self.api.calls)
3468+ self.assertThat(nodegroup_filter, matchers.HasLength(2))
3469+
3470+ def test_nodegroup_list_with_limit(self):
3471+ expect = [
3472+ ('GET', self.base_path + '?limit=2', {}, None),
3473+ ]
3474+ self._test_nodegroup_list_with_filters(
3475+ self.cluster_id,
3476+ limit=2,
3477+ expect=expect)
3478+
3479+ def test_nodegroup_list_with_marker(self):
3480+ filter_ = '?marker=%s' % NODEGROUP2['uuid']
3481+ expect = [
3482+ ('GET', self.base_path + filter_, {}, None),
3483+ ]
3484+ self._test_nodegroup_list_with_filters(
3485+ self.cluster_id,
3486+ marker=NODEGROUP2['uuid'],
3487+ expect=expect)
3488+
3489+ def test_nodegroup_list_with_marker_limit(self):
3490+ filter_ = '?limit=2&marker=%s' % NODEGROUP2['uuid']
3491+ expect = [
3492+ ('GET', self.base_path + filter_, {}, None),
3493+ ]
3494+ self._test_nodegroup_list_with_filters(
3495+ self.cluster_id,
3496+ limit=2, marker=NODEGROUP2['uuid'],
3497+ expect=expect)
3498+
3499+ def test_nodegroup_list_with_sort_dir(self):
3500+ expect = [
3501+ ('GET', '/v1/clusters/test/nodegroups/?sort_dir=asc', {}, None),
3502+ ]
3503+ self._test_nodegroup_list_with_filters(
3504+ self.cluster_id,
3505+ sort_dir='asc',
3506+ expect=expect)
3507+
3508+ def test_nodegroup_list_with_sort_key(self):
3509+ expect = [
3510+ ('GET', '/v1/clusters/test/nodegroups/?sort_key=uuid', {}, None),
3511+ ]
3512+ self._test_nodegroup_list_with_filters(
3513+ self.cluster_id,
3514+ sort_key='uuid',
3515+ expect=expect)
3516+
3517+ def test_nodegroup_list_with_sort_key_dir(self):
3518+ expect = [
3519+ ('GET', self.base_path + '?sort_key=uuid&sort_dir=desc', {}, None),
3520+ ]
3521+ self._test_nodegroup_list_with_filters(
3522+ self.cluster_id,
3523+ sort_key='uuid', sort_dir='desc',
3524+ expect=expect)
3525+
3526+ def test_nodegroup_show_by_name(self):
3527+ nodegroup = self.mgr.get(self.cluster_id, NODEGROUP1['name'])
3528+ expect = [
3529+ ('GET', self.base_path + '%s' % NODEGROUP1['name'], {}, None)
3530+ ]
3531+ self.assertEqual(expect, self.api.calls)
3532+ self.assertEqual(NODEGROUP1['name'], nodegroup.name)
3533+
3534+ def test_nodegroup_show_by_id(self):
3535+ nodegroup = self.mgr.get(self.cluster_id, NODEGROUP1['id'])
3536+ expect = [
3537+ ('GET', self.base_path + '%s' % NODEGROUP1['id'], {}, None)
3538+ ]
3539+ self.assertEqual(expect, self.api.calls)
3540+ self.assertEqual(NODEGROUP1['name'], nodegroup.name)
3541+
3542+ def test_nodegroup_delete_by_id(self):
3543+ nodegroup = self.mgr.delete(self.cluster_id, NODEGROUP1['id'])
3544+ expect = [
3545+ ('DELETE', self.base_path + '%s' % NODEGROUP1['id'], {}, None),
3546+ ]
3547+ self.assertEqual(expect, self.api.calls)
3548+ self.assertIsNone(nodegroup)
3549+
3550+ def test_nodegroup_delete_by_name(self):
3551+ nodegroup = self.mgr.delete(self.cluster_id, NODEGROUP1['name'])
3552+ expect = [
3553+ ('DELETE', self.base_path + '%s' % NODEGROUP1['name'], {}, None),
3554+ ]
3555+ self.assertEqual(expect, self.api.calls)
3556+ self.assertIsNone(nodegroup)
3557+
3558+ def test_nodegroup_update(self):
3559+ patch = {'op': 'replace',
3560+ 'value': NEW_NODE_COUNT,
3561+ 'path': '/node_count'}
3562+ nodegroup = self.mgr.update(self.cluster_id, id=NODEGROUP1['id'],
3563+ patch=patch)
3564+ expect = [
3565+ ('PATCH', self.base_path + '%s' % NODEGROUP1['id'], {}, patch),
3566+ ]
3567+ self.assertEqual(expect, self.api.calls)
3568+ self.assertEqual(NEW_NODE_COUNT, nodegroup.node_count)
3569+
3570+ def test_nodegroup_create(self):
3571+ nodegroup = self.mgr.create(self.cluster_id, **CREATE_NODEGROUP)
3572+ expect = [
3573+ ('POST', self.base_path, {}, CREATE_NODEGROUP),
3574+ ]
3575+ self.assertEqual(expect, self.api.calls)
3576+ self.assertTrue(nodegroup)
3577+
3578+ def test_nodegroup_create_with_docker_volume_size(self):
3579+ ng_with_volume_size = dict()
3580+ ng_with_volume_size.update(CREATE_NODEGROUP)
3581+ ng_with_volume_size['docker_volume_size'] = 20
3582+ nodegroup = self.mgr.create(self.cluster_id, **ng_with_volume_size)
3583+ expect = [
3584+ ('POST', self.base_path, {}, ng_with_volume_size),
3585+ ]
3586+ self.assertEqual(expect, self.api.calls)
3587+ self.assertTrue(nodegroup)
3588+
3589+ def test_nodegroup_create_with_labels(self):
3590+ ng_with_labels = dict()
3591+ ng_with_labels.update(CREATE_NODEGROUP)
3592+ ng_with_labels['labels'] = "key=val"
3593+ nodegroup = self.mgr.create(self.cluster_id, **ng_with_labels)
3594+ expect = [
3595+ ('POST', self.base_path, {}, ng_with_labels),
3596+ ]
3597+ self.assertEqual(expect, self.api.calls)
3598+ self.assertTrue(nodegroup)
3599+
3600+ def test_nodegroup_create_fail(self):
3601+ CREATE_NODEGROUP_FAIL = copy.deepcopy(CREATE_NODEGROUP)
3602+ CREATE_NODEGROUP_FAIL["wrong_key"] = "wrong"
3603+ self.assertRaisesRegex(exceptions.InvalidAttribute,
3604+ ("Key must be in %s" %
3605+ ','.join(nodegroups.CREATION_ATTRIBUTES)),
3606+ self.mgr.create, self.cluster_id,
3607+ **CREATE_NODEGROUP_FAIL)
3608+ self.assertEqual([], self.api.calls)
3609diff --git a/magnumclient/v1/basemodels.py b/magnumclient/v1/basemodels.py
3610index 0334b89..6ba0be9 100644
3611--- a/magnumclient/v1/basemodels.py
3612+++ b/magnumclient/v1/basemodels.py
3613@@ -22,7 +22,7 @@ CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id',
3614 'no_proxy', 'network_driver', 'tls_disabled', 'public',
3615 'registry_enabled', 'volume_driver', 'server_type',
3616 'docker_storage_driver', 'master_lb_enabled',
3617- 'floating_ip_enabled']
3618+ 'floating_ip_enabled', 'hidden']
3619
3620 OUTPUT_ATTRIBUTES = CREATION_ATTRIBUTES + ['apiserver_port', 'created_at',
3621 'insecure_registry', 'links',
3622diff --git a/magnumclient/v1/bays_shell.py b/magnumclient/v1/bays_shell.py
3623index 689e84f..d2c865f 100644
3624--- a/magnumclient/v1/bays_shell.py
3625+++ b/magnumclient/v1/bays_shell.py
3626@@ -228,8 +228,8 @@ def do_bay_config(cs, args):
3627 """
3628 args.dir = os.path.abspath(args.dir)
3629 bay = cs.bays.get(args.bay)
3630- if bay.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE'):
3631- raise exceptions.CommandError("Bay in status %s" % bay.status)
3632+ if (hasattr(bay, 'api_address') and bay.api_address is None):
3633+ print("WARNING: The bay's api_address is not known yet.")
3634 baymodel = cs.baymodels.get(bay.baymodel_id)
3635 opts = {
3636 'cluster_uuid': bay.uuid,
3637diff --git a/magnumclient/v1/client.py b/magnumclient/v1/client.py
3638index 8a8c72e..cb98cf9 100644
3639--- a/magnumclient/v1/client.py
3640+++ b/magnumclient/v1/client.py
3641@@ -25,6 +25,7 @@ from magnumclient.v1 import certificates
3642 from magnumclient.v1 import cluster_templates
3643 from magnumclient.v1 import clusters
3644 from magnumclient.v1 import mservices
3645+from magnumclient.v1 import nodegroups
3646 from magnumclient.v1 import quotas
3647 from magnumclient.v1 import stats
3648
3649@@ -215,3 +216,4 @@ class Client(object):
3650 profiler.init(profile)
3651 self.stats = stats.StatsManager(self.http_client)
3652 self.quotas = quotas.QuotasManager(self.http_client)
3653+ self.nodegroups = nodegroups.NodeGroupManager(self.http_client)
3654diff --git a/magnumclient/v1/cluster_templates_shell.py b/magnumclient/v1/cluster_templates_shell.py
3655index 32a1aa5..11f8d12 100644
3656--- a/magnumclient/v1/cluster_templates_shell.py
3657+++ b/magnumclient/v1/cluster_templates_shell.py
3658@@ -187,6 +187,13 @@ def _show_cluster_template(cluster_template):
3659 @utils.arg('--insecure-registry',
3660 metavar='<insecure-registry>',
3661 help='url of docker registry')
3662+@utils.arg('--hidden',
3663+ action='store_true', default=False,
3664+ help=_('Make cluster template hidden.'))
3665+@utils.arg('--visible',
3666+ dest='hidden',
3667+ action='store_false',
3668+ help=_('Make cluster template visible.'))
3669 @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING)
3670 def do_cluster_template_create(cs, args):
3671 """Create a cluster template."""
3672@@ -219,6 +226,7 @@ def do_cluster_template_create(cs, args):
3673 opts['server_type'] = args.server_type
3674 opts['master_lb_enabled'] = args.master_lb_enabled
3675 opts['insecure_registry'] = args.insecure_registry
3676+ opts['hidden'] = args.hidden
3677
3678 if len(args.floating_ip_enabled) > 1:
3679 raise InvalidAttribute('--floating-ip-enabled and '
3680diff --git a/magnumclient/v1/clusters.py b/magnumclient/v1/clusters.py
3681index cdb599a..6aabc4b 100644
3682--- a/magnumclient/v1/clusters.py
3683+++ b/magnumclient/v1/clusters.py
3684@@ -23,6 +23,10 @@ CREATION_ATTRIBUTES.append('docker_volume_size')
3685 CREATION_ATTRIBUTES.append('labels')
3686 CREATION_ATTRIBUTES.append('master_flavor_id')
3687 CREATION_ATTRIBUTES.append('flavor_id')
3688+CREATION_ATTRIBUTES.append('fixed_network')
3689+CREATION_ATTRIBUTES.append('fixed_subnet')
3690+CREATION_ATTRIBUTES.append('floating_ip_enabled')
3691+CREATION_ATTRIBUTES.append('merge_labels')
3692
3693
3694 class Cluster(baseunit.BaseTemplate):
3695@@ -32,3 +36,33 @@ class Cluster(baseunit.BaseTemplate):
3696 class ClusterManager(baseunit.BaseTemplateManager):
3697 resource_class = Cluster
3698 template_name = 'clusters'
3699+
3700+ def resize(self, cluster_uuid, node_count,
3701+ nodes_to_remove=[], nodegroup=None):
3702+ url = self._path(cluster_uuid) + "/actions/resize"
3703+
3704+ post_body = {"node_count": node_count}
3705+ if nodes_to_remove:
3706+ post_body.update({"nodes_to_remove": nodes_to_remove})
3707+ if nodegroup:
3708+ post_body.update({"nodegroup": nodegroup})
3709+
3710+ resp, resp_body = self.api.json_request("POST", url, body=post_body)
3711+
3712+ if resp_body:
3713+ return self.resource_class(self, resp_body)
3714+
3715+ def upgrade(self, cluster_uuid, cluster_template,
3716+ max_batch_size=1, nodegroup=None):
3717+ url = self._path(cluster_uuid) + "/actions/upgrade"
3718+
3719+ post_body = {"cluster_template": cluster_template}
3720+ if max_batch_size:
3721+ post_body.update({"max_batch_size": max_batch_size})
3722+ if nodegroup:
3723+ post_body.update({"nodegroup": nodegroup})
3724+
3725+ resp, resp_body = self.api.json_request("POST", url, body=post_body)
3726+
3727+ if resp_body:
3728+ return self.resource_class(self, resp_body)
3729diff --git a/magnumclient/v1/clusters_shell.py b/magnumclient/v1/clusters_shell.py
3730index 81e77e7..e46b3f9 100644
3731--- a/magnumclient/v1/clusters_shell.py
3732+++ b/magnumclient/v1/clusters_shell.py
3733@@ -262,9 +262,8 @@ def do_cluster_config(cs, args):
3734 """
3735 args.dir = os.path.abspath(args.dir)
3736 cluster = cs.clusters.get(args.cluster)
3737- if cluster.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE',
3738- 'ROLLBACK_COMPLETE'):
3739- raise exceptions.CommandError("cluster in status %s" % cluster.status)
3740+ if (hasattr(cluster, 'api_address') and cluster.api_address is None):
3741+ print("WARNING: The cluster's api_address is not known yet.")
3742 cluster_template = cs.cluster_templates.get(cluster.cluster_template_id)
3743 opts = {
3744 'cluster_uuid': cluster.uuid,
3745diff --git a/magnumclient/v1/nodegroups.py b/magnumclient/v1/nodegroups.py
3746new file mode 100644
3747index 0000000..111d8fb
3748--- /dev/null
3749+++ b/magnumclient/v1/nodegroups.py
3750@@ -0,0 +1,84 @@
3751+# Copyright (c) 2018 European Organization for Nuclear Research.
3752+# All Rights Reserved.
3753+#
3754+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3755+# not use this file except in compliance with the License. You may obtain
3756+# a copy of the License at
3757+#
3758+# http://www.apache.org/licenses/LICENSE-2.0
3759+#
3760+# Unless required by applicable law or agreed to in writing, software
3761+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3762+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3763+# License for the specific language governing permissions and limitations
3764+# under the License.
3765+
3766+from magnumclient.common import utils
3767+from magnumclient import exceptions
3768+from magnumclient.v1 import baseunit
3769+
3770+
3771+CREATION_ATTRIBUTES = ['docker_volume_size', 'labels', 'flavor_id', 'image_id',
3772+ 'project_id', 'node_count', 'name', 'role',
3773+ 'min_node_count', 'max_node_count', 'merge_labels']
3774+
3775+
3776+class NodeGroup(baseunit.BaseTemplate):
3777+ template_name = "NodeGroups"
3778+
3779+
3780+class NodeGroupManager(baseunit.BaseTemplateManager):
3781+ resource_class = NodeGroup
3782+ template_name = 'nodegroups'
3783+ api_name = 'nodegroups'
3784+
3785+ @classmethod
3786+ def _path(cls, cluster_id, id=None):
3787+ path = '/v1/clusters/%s/%s/' % (cluster_id, cls.template_name)
3788+ if id:
3789+ path += str(id)
3790+ return path
3791+
3792+ def list(self, cluster_id, limit=None, marker=None, sort_key=None,
3793+ sort_dir=None, role=None, detail=False):
3794+ if limit is not None:
3795+ limit = int(limit)
3796+
3797+ filters = utils.common_filters(marker, limit, sort_key, sort_dir)
3798+ path = ''
3799+ if role:
3800+ filters.append('role=%s' % role)
3801+ if detail:
3802+ path += 'detail'
3803+ if filters:
3804+ path += '?' + '&'.join(filters)
3805+
3806+ if limit is None:
3807+ return self._list(self._path(cluster_id, id=path),
3808+ self.__class__.api_name)
3809+ else:
3810+ return self._list_pagination(self._path(cluster_id, id=path),
3811+ self.__class__.api_name,
3812+ limit=limit)
3813+
3814+ def get(self, cluster_id, id):
3815+ try:
3816+ return self._list(self._path(cluster_id, id=id))[0]
3817+ except IndexError:
3818+ return None
3819+
3820+ def create(self, cluster_id, **kwargs):
3821+ new = {}
3822+ for (key, value) in kwargs.items():
3823+ if key in CREATION_ATTRIBUTES:
3824+ new[key] = value
3825+ else:
3826+ raise exceptions.InvalidAttribute(
3827+ "Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
3828+ return self._create(self._path(cluster_id), new)
3829+
3830+ def delete(self, cluster_id, id):
3831+ return self._delete(self._path(cluster_id, id=id))
3832+
3833+ def update(self, cluster_id, id, patch):
3834+ return self._update(self._path(cluster_id, id=id), patch)
3835diff --git a/python_magnumclient.egg-info/PKG-INFO b/python_magnumclient.egg-info/PKG-INFO
3836new file mode 100644
3837index 0000000..8045ab2
3838--- /dev/null
3839+++ b/python_magnumclient.egg-info/PKG-INFO
3840@@ -0,0 +1,61 @@
3841+Metadata-Version: 1.1
3842+Name: python-magnumclient
3843+Version: 3.0.1
3844+Summary: Client library for Magnum API
3845+Home-page: https://docs.openstack.org/python-magnumclient/latest/
3846+Author: OpenStack
3847+Author-email: openstack-discuss@lists.openstack.org
3848+License: UNKNOWN
3849+Description: ========================
3850+ Team and repository tags
3851+ ========================
3852+
3853+ .. image:: https://governance.openstack.org/tc/badges/python-magnumclient.svg
3854+ :target: https://governance.openstack.org/tc/reference/tags/index.html
3855+
3856+ .. Change things from this point on
3857+
3858+ Python bindings to the Magnum API
3859+ =================================
3860+
3861+ .. image:: https://img.shields.io/pypi/v/python-magnumclient.svg
3862+ :target: https://pypi.org/project/python-magnumclient/
3863+ :alt: Latest Version
3864+
3865+ .. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg
3866+ :target: https://pypi.org/project/python-magnumclient/
3867+ :alt: Downloads
3868+
3869+ This is a client library for Magnum built on the Magnum API. It
3870+ provides a Python API (the ``magnumclient`` module) and a command-line
3871+ tool (``magnum``).
3872+
3873+ Development takes place via the usual OpenStack processes as outlined
3874+ in the `developer guide
3875+ <https://docs.openstack.org/infra/manual/developers.html>`_.
3876+
3877+ * License: Apache License, Version 2.0
3878+ * `PyPi`_ - package installation
3879+ * `Online Documentation`_
3880+ * `Launchpad project`_ - release management
3881+ * `Bugs`_ - issue tracking
3882+ * `Source`_
3883+
3884+ .. _PyPi: https://pypi.org/project/python-magnumclient
3885+ .. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/
3886+ .. _Launchpad project: https://launchpad.net/python-magnumclient
3887+ .. _Bugs: https://bugs.launchpad.net/python-magnumclient
3888+ .. _Source: https://opendev.org/openstack/python-magnumclient
3889+
3890+
3891+Platform: UNKNOWN
3892+Classifier: Environment :: OpenStack
3893+Classifier: Intended Audience :: Information Technology
3894+Classifier: Intended Audience :: System Administrators
3895+Classifier: License :: OSI Approved :: Apache Software License
3896+Classifier: Operating System :: POSIX :: Linux
3897+Classifier: Programming Language :: Python
3898+Classifier: Programming Language :: Python :: 2
3899+Classifier: Programming Language :: Python :: 3
3900+Classifier: Programming Language :: Python :: 3.6
3901+Classifier: Programming Language :: Python :: 3.7
3902diff --git a/python_magnumclient.egg-info/SOURCES.txt b/python_magnumclient.egg-info/SOURCES.txt
3903new file mode 100644
3904index 0000000..c63c7ee
3905--- /dev/null
3906+++ b/python_magnumclient.egg-info/SOURCES.txt
3907@@ -0,0 +1,128 @@
3908+.coveragerc
3909+.mailmap
3910+.stestr.conf
3911+.zuul.yaml
3912+AUTHORS
3913+CONTRIBUTING.rst
3914+ChangeLog
3915+LICENSE
3916+README.rst
3917+babel.cfg
3918+lower-constraints.txt
3919+requirements.txt
3920+setup.cfg
3921+setup.py
3922+test-requirements.txt
3923+tox.ini
3924+doc/requirements.txt
3925+doc/source/conf.py
3926+doc/source/contributing.rst
3927+doc/source/index.rst
3928+doc/source/installation.rst
3929+doc/source/readme.rst
3930+doc/source/usage.rst
3931+magnumclient/__init__.py
3932+magnumclient/client.py
3933+magnumclient/exceptions.py
3934+magnumclient/i18n.py
3935+magnumclient/shell.py
3936+magnumclient/version.py
3937+magnumclient/common/__init__.py
3938+magnumclient/common/base.py
3939+magnumclient/common/cliutils.py
3940+magnumclient/common/httpclient.py
3941+magnumclient/common/utils.py
3942+magnumclient/common/apiclient/__init__.py
3943+magnumclient/common/apiclient/base.py
3944+magnumclient/common/apiclient/exceptions.py
3945+magnumclient/osc/__init__.py
3946+magnumclient/osc/plugin.py
3947+magnumclient/osc/v1/__init__.py
3948+magnumclient/osc/v1/certificates.py
3949+magnumclient/osc/v1/cluster_templates.py
3950+magnumclient/osc/v1/clusters.py
3951+magnumclient/osc/v1/mservices.py
3952+magnumclient/osc/v1/nodegroups.py
3953+magnumclient/osc/v1/quotas.py
3954+magnumclient/osc/v1/stats.py
3955+magnumclient/tests/__init__.py
3956+magnumclient/tests/base.py
3957+magnumclient/tests/test_client.py
3958+magnumclient/tests/test_httpclient.py
3959+magnumclient/tests/test_magnumclient.py
3960+magnumclient/tests/test_shell.py
3961+magnumclient/tests/test_utils.py
3962+magnumclient/tests/utils.py
3963+magnumclient/tests/osc/__init__.py
3964+magnumclient/tests/osc/unit/__init__.py
3965+magnumclient/tests/osc/unit/osc_fakes.py
3966+magnumclient/tests/osc/unit/osc_utils.py
3967+magnumclient/tests/osc/unit/v1/__init__.py
3968+magnumclient/tests/osc/unit/v1/fakes.py
3969+magnumclient/tests/osc/unit/v1/test_certificates.py
3970+magnumclient/tests/osc/unit/v1/test_cluster_templates.py
3971+magnumclient/tests/osc/unit/v1/test_clusters.py
3972+magnumclient/tests/osc/unit/v1/test_mservices.py
3973+magnumclient/tests/osc/unit/v1/test_nodegroups.py
3974+magnumclient/tests/osc/unit/v1/test_quotas.py
3975+magnumclient/tests/osc/unit/v1/test_stats.py
3976+magnumclient/tests/test_csr/test.csr
3977+magnumclient/tests/v1/__init__.py
3978+magnumclient/tests/v1/shell_test_base.py
3979+magnumclient/tests/v1/test_baymodels.py
3980+magnumclient/tests/v1/test_baymodels_shell.py
3981+magnumclient/tests/v1/test_bays.py
3982+magnumclient/tests/v1/test_bays_shell.py
3983+magnumclient/tests/v1/test_certificates.py
3984+magnumclient/tests/v1/test_certificates_shell.py
3985+magnumclient/tests/v1/test_client.py
3986+magnumclient/tests/v1/test_clusters.py
3987+magnumclient/tests/v1/test_clusters_shell.py
3988+magnumclient/tests/v1/test_clustertemplates.py
3989+magnumclient/tests/v1/test_clustertemplates_shell.py
3990+magnumclient/tests/v1/test_mservices.py
3991+magnumclient/tests/v1/test_mservices_shell.py
3992+magnumclient/tests/v1/test_nodegroups.py
3993+magnumclient/tests/v1/test_quotas.py
3994+magnumclient/tests/v1/test_quotas_shell.py
3995+magnumclient/tests/v1/test_stats.py
3996+magnumclient/tests/v1/test_stats_shell.py
3997+magnumclient/v1/__init__.py
3998+magnumclient/v1/basemodels.py
3999+magnumclient/v1/baseunit.py
4000+magnumclient/v1/baymodels.py
4001+magnumclient/v1/baymodels_shell.py
4002+magnumclient/v1/bays.py
4003+magnumclient/v1/bays_shell.py
4004+magnumclient/v1/certificates.py
4005+magnumclient/v1/certificates_shell.py
4006+magnumclient/v1/client.py
4007+magnumclient/v1/cluster_templates.py
4008+magnumclient/v1/cluster_templates_shell.py
4009+magnumclient/v1/clusters.py
4010+magnumclient/v1/clusters_shell.py
4011+magnumclient/v1/mservices.py
4012+magnumclient/v1/mservices_shell.py
4013+magnumclient/v1/nodegroups.py
4014+magnumclient/v1/quotas.py
4015+magnumclient/v1/quotas_shell.py
4016+magnumclient/v1/shell.py
4017+magnumclient/v1/stats.py
4018+magnumclient/v1/stats_shell.py
4019+python_magnumclient.egg-info/PKG-INFO
4020+python_magnumclient.egg-info/SOURCES.txt
4021+python_magnumclient.egg-info/dependency_links.txt
4022+python_magnumclient.egg-info/entry_points.txt
4023+python_magnumclient.egg-info/not-zip-safe
4024+python_magnumclient.egg-info/pbr.json
4025+python_magnumclient.egg-info/requires.txt
4026+python_magnumclient.egg-info/top_level.txt
4027+releasenotes/notes/partial_osc_implementation_for_certificate-4597c20b59c152e1.yaml
4028+releasenotes/notes/partial_osc_implementation_for_quotas-33f44c0496d721f8.yaml
4029+releasenotes/source/conf.py
4030+releasenotes/source/index.rst
4031+releasenotes/source/rocky.rst
4032+releasenotes/source/stein.rst
4033+releasenotes/source/train.rst
4034+releasenotes/source/unreleased.rst
4035+tools/magnum.bash_completion
4036\ No newline at end of file
4037diff --git a/python_magnumclient.egg-info/dependency_links.txt b/python_magnumclient.egg-info/dependency_links.txt
4038new file mode 100644
4039index 0000000..8b13789
4040--- /dev/null
4041+++ b/python_magnumclient.egg-info/dependency_links.txt
4042@@ -0,0 +1 @@
4043+
4044diff --git a/python_magnumclient.egg-info/entry_points.txt b/python_magnumclient.egg-info/entry_points.txt
4045new file mode 100644
4046index 0000000..ebade23
4047--- /dev/null
4048+++ b/python_magnumclient.egg-info/entry_points.txt
4049@@ -0,0 +1,36 @@
4050+[console_scripts]
4051+magnum = magnumclient.shell:main
4052+
4053+[openstack.cli.extension]
4054+container_infra = magnumclient.osc.plugin
4055+
4056+[openstack.container_infra.v1]
4057+coe_ca_rotate = magnumclient.osc.v1.certificates:RotateCa
4058+coe_ca_show = magnumclient.osc.v1.certificates:ShowCa
4059+coe_ca_sign = magnumclient.osc.v1.certificates:SignCa
4060+coe_cluster_config = magnumclient.osc.v1.clusters:ConfigCluster
4061+coe_cluster_create = magnumclient.osc.v1.clusters:CreateCluster
4062+coe_cluster_delete = magnumclient.osc.v1.clusters:DeleteCluster
4063+coe_cluster_list = magnumclient.osc.v1.clusters:ListCluster
4064+coe_cluster_resize = magnumclient.osc.v1.clusters:ResizeCluster
4065+coe_cluster_show = magnumclient.osc.v1.clusters:ShowCluster
4066+coe_cluster_template_create = magnumclient.osc.v1.cluster_templates:CreateClusterTemplate
4067+coe_cluster_template_delete = magnumclient.osc.v1.cluster_templates:DeleteClusterTemplate
4068+coe_cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster
4069+coe_cluster_template_show = magnumclient.osc.v1.cluster_templates:ShowClusterTemplate
4070+coe_cluster_template_update = magnumclient.osc.v1.cluster_templates:UpdateClusterTemplate
4071+coe_cluster_update = magnumclient.osc.v1.clusters:UpdateCluster
4072+coe_cluster_upgrade = magnumclient.osc.v1.clusters:UpgradeCluster
4073+coe_nodegroup_create = magnumclient.osc.v1.nodegroups:CreateNodeGroup
4074+coe_nodegroup_delete = magnumclient.osc.v1.nodegroups:DeleteNodeGroup
4075+coe_nodegroup_list = magnumclient.osc.v1.nodegroups:ListNodeGroup
4076+coe_nodegroup_show = magnumclient.osc.v1.nodegroups:ShowNodeGroup
4077+coe_nodegroup_update = magnumclient.osc.v1.nodegroups:UpdateNodeGroup
4078+coe_quotas_create = magnumclient.osc.v1.quotas:CreateQuotas
4079+coe_quotas_delete = magnumclient.osc.v1.quotas:DeleteQuotas
4080+coe_quotas_list = magnumclient.osc.v1.quotas:ListQuotas
4081+coe_quotas_show = magnumclient.osc.v1.quotas:ShowQuotas
4082+coe_quotas_update = magnumclient.osc.v1.quotas:UpdateQuotas
4083+coe_service_list = magnumclient.osc.v1.mservices:ListService
4084+coe_stats_list = magnumclient.osc.v1.stats:ListStats
4085+
4086diff --git a/python_magnumclient.egg-info/not-zip-safe b/python_magnumclient.egg-info/not-zip-safe
4087new file mode 100644
4088index 0000000..8b13789
4089--- /dev/null
4090+++ b/python_magnumclient.egg-info/not-zip-safe
4091@@ -0,0 +1 @@
4092+
4093diff --git a/python_magnumclient.egg-info/pbr.json b/python_magnumclient.egg-info/pbr.json
4094new file mode 100644
4095index 0000000..b268641
4096--- /dev/null
4097+++ b/python_magnumclient.egg-info/pbr.json
4098@@ -0,0 +1 @@
4099+{"git_version": "6d1a386", "is_release": true}
4100\ No newline at end of file
4101diff --git a/python_magnumclient.egg-info/requires.txt b/python_magnumclient.egg-info/requires.txt
4102new file mode 100644
4103index 0000000..1a7483a
4104--- /dev/null
4105+++ b/python_magnumclient.egg-info/requires.txt
4106@@ -0,0 +1,15 @@
4107+Babel!=2.4.0,>=2.3.4
4108+PrettyTable<0.8,>=0.7.2
4109+cryptography>=2.1
4110+decorator>=3.4.0
4111+keystoneauth1>=3.4.0
4112+os-client-config>=1.28.0
4113+osc-lib>=1.8.0
4114+oslo.i18n>=3.15.3
4115+oslo.log>=3.36.0
4116+oslo.serialization!=2.19.1,>=2.18.0
4117+oslo.utils>=3.33.0
4118+pbr!=2.1.0,>=2.0.0
4119+requests>=2.14.2
4120+six>=1.10.0
4121+stevedore>=1.20.0
4122diff --git a/python_magnumclient.egg-info/top_level.txt b/python_magnumclient.egg-info/top_level.txt
4123new file mode 100644
4124index 0000000..66cb45a
4125--- /dev/null
4126+++ b/python_magnumclient.egg-info/top_level.txt
4127@@ -0,0 +1 @@
4128+magnumclient
4129diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
4130index 416280b..11d549e 100644
4131--- a/releasenotes/source/index.rst
4132+++ b/releasenotes/source/index.rst
4133@@ -6,6 +6,8 @@
4134 :maxdepth: 1
4135
4136 unreleased
4137+ train
4138+ stein
4139 rocky
4140
4141 OpenStack Releases
4142diff --git a/releasenotes/source/stein.rst b/releasenotes/source/stein.rst
4143new file mode 100644
4144index 0000000..efaceb6
4145--- /dev/null
4146+++ b/releasenotes/source/stein.rst
4147@@ -0,0 +1,6 @@
4148+===================================
4149+ Stein Series Release Notes
4150+===================================
4151+
4152+.. release-notes::
4153+ :branch: stable/stein
4154diff --git a/releasenotes/source/train.rst b/releasenotes/source/train.rst
4155new file mode 100644
4156index 0000000..5839003
4157--- /dev/null
4158+++ b/releasenotes/source/train.rst
4159@@ -0,0 +1,6 @@
4160+==========================
4161+Train Series Release Notes
4162+==========================
4163+
4164+.. release-notes::
4165+ :branch: stable/train
4166diff --git a/setup.cfg b/setup.cfg
4167index 4db9c44..05d064e 100644
4168--- a/setup.cfg
4169+++ b/setup.cfg
4170@@ -1,59 +1,64 @@
4171 [metadata]
4172 name = python-magnumclient
4173 summary = Client library for Magnum API
4174-description-file =
4175- README.rst
4176+description-file =
4177+ README.rst
4178 author = OpenStack
4179-author-email = openstack-dev@lists.openstack.org
4180+author-email = openstack-discuss@lists.openstack.org
4181 home-page = https://docs.openstack.org/python-magnumclient/latest/
4182-classifier =
4183- Environment :: OpenStack
4184- Intended Audience :: Information Technology
4185- Intended Audience :: System Administrators
4186- License :: OSI Approved :: Apache Software License
4187- Operating System :: POSIX :: Linux
4188- Programming Language :: Python
4189- Programming Language :: Python :: 2
4190- Programming Language :: Python :: 2.7
4191- Programming Language :: Python :: 3
4192- Programming Language :: Python :: 3.5
4193+classifier =
4194+ Environment :: OpenStack
4195+ Intended Audience :: Information Technology
4196+ Intended Audience :: System Administrators
4197+ License :: OSI Approved :: Apache Software License
4198+ Operating System :: POSIX :: Linux
4199+ Programming Language :: Python
4200+ Programming Language :: Python :: 2
4201+ Programming Language :: Python :: 3
4202+ Programming Language :: Python :: 3.6
4203+ Programming Language :: Python :: 3.7
4204
4205 [files]
4206-packages =
4207- magnumclient
4208+packages =
4209+ magnumclient
4210
4211 [entry_points]
4212-console_scripts =
4213- magnum = magnumclient.shell:main
4214-
4215-openstack.cli.extension =
4216- container_infra = magnumclient.osc.plugin
4217-
4218-openstack.container_infra.v1 =
4219- coe_cluster_template_create = magnumclient.osc.v1.cluster_templates:CreateClusterTemplate
4220- coe_cluster_template_delete = magnumclient.osc.v1.cluster_templates:DeleteClusterTemplate
4221- coe_cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster
4222- coe_cluster_template_show = magnumclient.osc.v1.cluster_templates:ShowClusterTemplate
4223- coe_cluster_template_update = magnumclient.osc.v1.cluster_templates:UpdateClusterTemplate
4224-
4225- coe_cluster_create = magnumclient.osc.v1.clusters:CreateCluster
4226- coe_cluster_list = magnumclient.osc.v1.clusters:ListCluster
4227- coe_cluster_delete = magnumclient.osc.v1.clusters:DeleteCluster
4228- coe_cluster_show = magnumclient.osc.v1.clusters:ShowCluster
4229- coe_cluster_update = magnumclient.osc.v1.clusters:UpdateCluster
4230- coe_cluster_config = magnumclient.osc.v1.clusters:ConfigCluster
4231- coe_ca_rotate = magnumclient.osc.v1.certificates:RotateCa
4232- coe_ca_show = magnumclient.osc.v1.certificates:ShowCa
4233- coe_ca_sign = magnumclient.osc.v1.certificates:SignCa
4234- coe_stats_list = magnumclient.osc.v1.stats:ListStats
4235- coe_quotas_create = magnumclient.osc.v1.quotas:CreateQuotas
4236- coe_quotas_delete = magnumclient.osc.v1.quotas:DeleteQuotas
4237- coe_quotas_update = magnumclient.osc.v1.quotas:UpdateQuotas
4238- coe_quotas_show = magnumclient.osc.v1.quotas:ShowQuotas
4239- coe_quotas_list = magnumclient.osc.v1.quotas:ListQuotas
4240-
4241- coe_service_list = magnumclient.osc.v1.mservices:ListService
4242-
4243+console_scripts =
4244+ magnum = magnumclient.shell:main
4245+openstack.cli.extension =
4246+ container_infra = magnumclient.osc.plugin
4247+openstack.container_infra.v1 =
4248+ coe_cluster_template_create = magnumclient.osc.v1.cluster_templates:CreateClusterTemplate
4249+ coe_cluster_template_delete = magnumclient.osc.v1.cluster_templates:DeleteClusterTemplate
4250+ coe_cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster
4251+ coe_cluster_template_show = magnumclient.osc.v1.cluster_templates:ShowClusterTemplate
4252+ coe_cluster_template_update = magnumclient.osc.v1.cluster_templates:UpdateClusterTemplate
4253+
4254+ coe_cluster_create = magnumclient.osc.v1.clusters:CreateCluster
4255+ coe_cluster_list = magnumclient.osc.v1.clusters:ListCluster
4256+ coe_cluster_delete = magnumclient.osc.v1.clusters:DeleteCluster
4257+ coe_cluster_show = magnumclient.osc.v1.clusters:ShowCluster
4258+ coe_cluster_update = magnumclient.osc.v1.clusters:UpdateCluster
4259+ coe_cluster_config = magnumclient.osc.v1.clusters:ConfigCluster
4260+ coe_cluster_resize = magnumclient.osc.v1.clusters:ResizeCluster
4261+ coe_cluster_upgrade = magnumclient.osc.v1.clusters:UpgradeCluster
4262+ coe_ca_rotate = magnumclient.osc.v1.certificates:RotateCa
4263+ coe_ca_show = magnumclient.osc.v1.certificates:ShowCa
4264+ coe_ca_sign = magnumclient.osc.v1.certificates:SignCa
4265+ coe_stats_list = magnumclient.osc.v1.stats:ListStats
4266+ coe_quotas_create = magnumclient.osc.v1.quotas:CreateQuotas
4267+ coe_quotas_delete = magnumclient.osc.v1.quotas:DeleteQuotas
4268+ coe_quotas_update = magnumclient.osc.v1.quotas:UpdateQuotas
4269+ coe_quotas_show = magnumclient.osc.v1.quotas:ShowQuotas
4270+ coe_quotas_list = magnumclient.osc.v1.quotas:ListQuotas
4271+
4272+ coe_service_list = magnumclient.osc.v1.mservices:ListService
4273+
4274+ coe_nodegroup_list = magnumclient.osc.v1.nodegroups:ListNodeGroup
4275+ coe_nodegroup_show = magnumclient.osc.v1.nodegroups:ShowNodeGroup
4276+ coe_nodegroup_create = magnumclient.osc.v1.nodegroups:CreateNodeGroup
4277+ coe_nodegroup_delete = magnumclient.osc.v1.nodegroups:DeleteNodeGroup
4278+ coe_nodegroup_update = magnumclient.osc.v1.nodegroups:UpdateNodeGroup
4279
4280 [compile_catalog]
4281 directory = magnumclient/locale
4282@@ -71,3 +76,8 @@ output_file = magnumclient/locale/magnumclient.pot
4283
4284 [wheel]
4285 universal = 1
4286+
4287+[egg_info]
4288+tag_build =
4289+tag_date = 0
4290+
4291diff --git a/test-requirements.txt b/test-requirements.txt
4292index f625855..fd61dfa 100644
4293--- a/test-requirements.txt
4294+++ b/test-requirements.txt
4295@@ -1,8 +1,8 @@
4296 # The order of packages is significant, because pip processes them in the order
4297 # of appearance. Changing the order has an impact on the overall integration
4298 # process, which may cause wedges in the gate later.
4299-hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
4300-bandit>=1.1.0 # Apache-2.0
4301+hacking>=2.0,<2.1 # Apache-2.0
4302+bandit!=1.6.0,>=1.1.0 # Apache-2.0
4303 coverage!=4.4,>=4.0 # Apache-2.0
4304 fixtures>=3.0.0 # Apache-2.0/BSD
4305 python-openstackclient>=3.12.0 # Apache-2.0
4306diff --git a/tools/cover.sh b/tools/cover.sh
4307deleted file mode 100755
4308index 74b25d9..0000000
4309--- a/tools/cover.sh
4310+++ /dev/null
4311@@ -1,79 +0,0 @@
4312-#!/bin/bash
4313-#
4314-# Licensed under the Apache License, Version 2.0 (the "License"); you may
4315-# not use this file except in compliance with the License. You may obtain
4316-# a copy of the License at
4317-#
4318-# http://www.apache.org/licenses/LICENSE-2.0
4319-#
4320-# Unless required by applicable law or agreed to in writing, software
4321-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4322-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4323-# License for the specific language governing permissions and limitations
4324-# under the License.
4325-
4326-ALLOWED_EXTRA_MISSING=0
4327-
4328-show_diff () {
4329- head -1 $1
4330- diff -U 0 $1 $2 | sed 1,2d
4331-}
4332-
4333-if ! git diff --exit-code || ! git diff --cached --exit-code
4334-then
4335- echo "There are uncommitted changes!"
4336- echo "Please clean git working directory and try again"
4337- exit 1
4338-fi
4339-
4340-# Checkout master and save coverage report
4341-git checkout HEAD^
4342-
4343-baseline_report=$(mktemp -t magnumclient_coverageXXXXXXX)
4344-find . -type f -name "*.pyc" -delete
4345-stestr run "$*"
4346-coverage combine
4347-coverage report --fail-under=80 --skip-covered
4348-coverage html -d cover
4349-coverage xml -o cover/coverage.xml
4350-coverage report > $baseline_report
4351-mv cover cover-master
4352-cat $baseline_report
4353-baseline_missing=$(awk 'END { print $3 }' $baseline_report)
4354-
4355-# Checkout back and save coverage report
4356-git checkout -
4357-
4358-current_report=$(mktemp -t magnumclient_coverageXXXXXXX)
4359-find . -type f -name "*.pyc" -delete
4360-stestr run "$*"
4361-coverage combine
4362-coverage report --fail-under=80 --skip-covered
4363-coverage html -d cover
4364-coverage xml -o cover/coverage.xml
4365-coverage report > $current_report
4366-current_missing=$(awk 'END { print $3 }' $current_report)
4367-
4368-# Show coverage details
4369-allowed_missing=$((baseline_missing+ALLOWED_EXTRA_MISSING))
4370-
4371-echo "Allowed to introduce missing lines : ${ALLOWED_EXTRA_MISSING}"
4372-echo "Missing lines in master : ${baseline_missing}"
4373-echo "Missing lines in proposed change : ${current_missing}"
4374-
4375-if [ $allowed_missing -ge $current_missing ]; then
4376- if [ $baseline_missing -lt $current_missing ]; then
4377- show_diff $baseline_report $current_report
4378- echo "We believe you can test your code with 100% coverage!"
4379- else
4380- echo "Thank you! You are awesome! Keep writing unit tests! :)"
4381- fi
4382- exit_code=0
4383-else
4384- show_diff $baseline_report $current_report
4385- echo "Please write more unit tests, we must maintain our test coverage :( "
4386- exit_code=1
4387-fi
4388-
4389-rm $baseline_report $current_report
4390-exit $exit_code
4391diff --git a/tox.ini b/tox.ini
4392index 391d0f4..004dc2d 100644
4393--- a/tox.ini
4394+++ b/tox.ini
4395@@ -1,6 +1,6 @@
4396 [tox]
4397-minversion = 1.6
4398-envlist = py35,py27,pypy,pep8
4399+minversion = 2.0
4400+envlist = py37,pypy,pep8
4401 skipsdist = True
4402
4403 [testenv]
4404@@ -11,7 +11,7 @@ setenv =
4405 VIRTUAL_ENV={envdir}
4406 PYTHONWARNINGS=default::DeprecationWarning
4407 deps =
4408- -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
4409+ -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/ussuri}
4410 -r{toxinidir}/requirements.txt
4411 -r{toxinidir}/test-requirements.txt
4412 commands =
4413@@ -21,14 +21,14 @@ commands =
4414 [testenv:bandit]
4415 basepython = python3
4416 deps =
4417- -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
4418+ -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/ussuri}
4419 -r{toxinidir}/test-requirements.txt
4420 commands = bandit -r magnumclient -x tests -n5 -ll
4421
4422 [testenv:pypy]
4423 basepython = python3
4424 deps =
4425- -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
4426+ -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/ussuri}
4427 setuptools<3.2
4428 -r{toxinidir}/requirements.txt
4429 -r{toxinidir}/test-requirements.txt
4430@@ -37,14 +37,6 @@ deps =
4431 basepython = python3
4432 commands = oslo_debug_helper -t magnumclient/tests {posargs}
4433
4434-[testenv:debug-py27]
4435-basepython = python2.7
4436-commands = oslo_debug_helper -t magnumclient/tests {posargs}
4437-
4438-[testenv:debug-py35]
4439-basepython = python3.5
4440-commands = oslo_debug_helper -t magnumclient/tests {posargs}
4441-
4442 [testenv:pep8]
4443 basepython = python3
4444 commands =
4445@@ -64,13 +56,20 @@ commands = {posargs}
4446
4447 [testenv:cover]
4448 basepython = python3
4449-commands = {toxinidir}/tools/cover.sh {posargs}
4450+setenv =
4451+ PYTHON=coverage run --source magnumclient --parallel-mode
4452+commands =
4453+ stestr run {posargs}
4454+ coverage combine
4455+ coverage html -d cover
4456+ coverage xml -o cover/coverage.xml
4457+ coverage report
4458
4459 [flake8]
4460 # E123, E125 skipped as they are invalid PEP-8.
4461
4462 show-source = True
4463-ignore = E123,E125
4464+ignore = E123,E125,W503,W504
4465 builtins = _
4466 exclude=.venv,.git,.tox,dist,doc,,*lib/python*,*egg,build
4467
4468@@ -89,4 +88,4 @@ basepython = python3
4469 deps = -r{toxinidir}/doc/requirements.txt
4470 commands =
4471 rm -rf releasenotes/build
4472- sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
4473\ No newline at end of file
4474+ sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html

Subscribers

People subscribed via source and target branches