Drop bay and baymodel from magnum
- Drop bay and baymodel tests - Drop bay and baymodel from controllers Depends-On: Ib85e4fda8e4ac467bd49590dc72ba5913bb9a19d Story: 2009104 Task: 42957 Task: 42959 Signed-off-by: Diogo Guerra <diogo.filipe.tomas.guerra@cern.ch> Change-Id: Ida2e42c86400438951d9804e3ce122c56a46b94f
This commit is contained in:
parent
df5bb49bf2
commit
11bcc17568
@ -1,366 +0,0 @@
|
|||||||
.. -*- rst -*-
|
|
||||||
|
|
||||||
===================
|
|
||||||
Manage Baymodels
|
|
||||||
===================
|
|
||||||
|
|
||||||
Lists, creates, shows details for, updates, and deletes baymodels.
|
|
||||||
|
|
||||||
Create new baymodel
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. rest_method:: POST /v1/baymodels/
|
|
||||||
|
|
||||||
Create new baymodel.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 201
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 400
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- labels: labels
|
|
||||||
- fixed_subnet: fixed_subnet
|
|
||||||
- master_flavor_id: master_flavor_id
|
|
||||||
- no_proxy: no_proxy
|
|
||||||
- https_proxy: https_proxy
|
|
||||||
- http_proxy: http_proxy
|
|
||||||
- tls_disabled: tls_disabled
|
|
||||||
- keypair_id: keypair_id
|
|
||||||
- public: public_type
|
|
||||||
- docker_volume_size: docker_volume_size
|
|
||||||
- server_type: server_type
|
|
||||||
- external_network_id: external_network_id
|
|
||||||
- image_id: image_id
|
|
||||||
- volume_driver: volume_driver
|
|
||||||
- registry_enabled: registry_enabled
|
|
||||||
- docker_storage_driver: docker_storage_driver
|
|
||||||
- name: name
|
|
||||||
- network_driver: network_driver
|
|
||||||
- fixed_network: fixed_network
|
|
||||||
- coe: coe
|
|
||||||
- flavor_id: flavor_id
|
|
||||||
- master_lb_enabled: master_lb_enabled
|
|
||||||
- dns_nameserver: dns_nameserver
|
|
||||||
|
|
||||||
Request Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/baymodel-create-req.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- insecure_registry: insecure_registry
|
|
||||||
- links: links
|
|
||||||
- http_proxy: http_proxy
|
|
||||||
- updated_at: updated_at
|
|
||||||
- floating_ip_enabled: floating_ip_enabled
|
|
||||||
- fixed_subnet: fixed_subnet
|
|
||||||
- master_flavor_id: master_flavor_id
|
|
||||||
- uuid: baymodel_id
|
|
||||||
- no_proxy: no_proxy
|
|
||||||
- https_proxy: https_proxy
|
|
||||||
- tls_disabled: tls_disabled
|
|
||||||
- keypair_id: keypair_id
|
|
||||||
- public: public_type
|
|
||||||
- labels: labels
|
|
||||||
- docker_volume_size: docker_volume_size
|
|
||||||
- server_type: server_type
|
|
||||||
- external_network_id: external_network_id
|
|
||||||
- cluster_distro: cluster_distro
|
|
||||||
- image_id: image_id
|
|
||||||
- volume_driver: volume_driver
|
|
||||||
- registry_enabled: registry_enabled
|
|
||||||
- docker_storage_driver: docker_storage_driver
|
|
||||||
- apiserver_port: apiserver_port
|
|
||||||
- name: name
|
|
||||||
- created_at: created_at
|
|
||||||
- network_driver: network_driver
|
|
||||||
- fixed_network: fixed_network
|
|
||||||
- coe: coe
|
|
||||||
- flavor_id: flavor_id
|
|
||||||
- master_lb_enabled: master_lb_enabled
|
|
||||||
- dns_nameserver: dns_nameserver
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/baymodel-create-resp.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
List all baymodels
|
|
||||||
==================
|
|
||||||
|
|
||||||
.. rest_method:: GET /v1/baymodels/
|
|
||||||
|
|
||||||
List all available baymodels in Magnum.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 200
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- baymodels: baymodel_list
|
|
||||||
- insecure_registry: insecure_registry
|
|
||||||
- links: links
|
|
||||||
- http_proxy: http_proxy
|
|
||||||
- updated_at: updated_at
|
|
||||||
- floating_ip_enabled: floating_ip_enabled
|
|
||||||
- fixed_subnet: fixed_subnet
|
|
||||||
- master_flavor_id: master_flavor_id
|
|
||||||
- uuid: baymodel_id
|
|
||||||
- no_proxy: no_proxy
|
|
||||||
- https_proxy: https_proxy
|
|
||||||
- tls_disabled: tls_disabled
|
|
||||||
- keypair_id: keypair_id
|
|
||||||
- public: public_type
|
|
||||||
- labels: labels
|
|
||||||
- docker_volume_size: docker_volume_size
|
|
||||||
- server_type: server_type
|
|
||||||
- external_network_id: external_network_id
|
|
||||||
- cluster_distro: cluster_distro
|
|
||||||
- image_id: image_id
|
|
||||||
- volume_driver: volume_driver
|
|
||||||
- registry_enabled: registry_enabled
|
|
||||||
- docker_storage_driver: docker_storage_driver
|
|
||||||
- apiserver_port: apiserver_port
|
|
||||||
- name: name
|
|
||||||
- created_at: created_at
|
|
||||||
- network_driver: network_driver
|
|
||||||
- fixed_network: fixed_network
|
|
||||||
- coe: coe
|
|
||||||
- flavor_id: flavor_id
|
|
||||||
- master_lb_enabled: master_lb_enabled
|
|
||||||
- dns_nameserver: dns_nameserver
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/baymodel-get-all-resp.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Show details of a baymodel
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. rest_method:: GET /v1/baymodels/{baymodel_ident}
|
|
||||||
|
|
||||||
Get all information of a baymodel in Magnum.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 200
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- baymodel_ident: baymodel_ident
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- baymodels: baymodel_list
|
|
||||||
- insecure_registry: insecure_registry
|
|
||||||
- links: links
|
|
||||||
- http_proxy: http_proxy
|
|
||||||
- updated_at: updated_at
|
|
||||||
- floating_ip_enabled: floating_ip_enabled
|
|
||||||
- fixed_subnet: fixed_subnet
|
|
||||||
- master_flavor_id: master_flavor_id
|
|
||||||
- uuid: baymodel_id
|
|
||||||
- no_proxy: no_proxy
|
|
||||||
- https_proxy: https_proxy
|
|
||||||
- tls_disabled: tls_disabled
|
|
||||||
- keypair_id: keypair_id
|
|
||||||
- public: public_type
|
|
||||||
- labels: labels
|
|
||||||
- docker_volume_size: docker_volume_size
|
|
||||||
- server_type: server_type
|
|
||||||
- external_network_id: external_network_id
|
|
||||||
- cluster_distro: cluster_distro
|
|
||||||
- image_id: image_id
|
|
||||||
- volume_driver: volume_driver
|
|
||||||
- registry_enabled: registry_enabled
|
|
||||||
- docker_storage_driver: docker_storage_driver
|
|
||||||
- apiserver_port: apiserver_port
|
|
||||||
- name: name
|
|
||||||
- created_at: created_at
|
|
||||||
- network_driver: network_driver
|
|
||||||
- fixed_network: fixed_network
|
|
||||||
- coe: coe
|
|
||||||
- flavor_id: flavor_id
|
|
||||||
- master_lb_enabled: master_lb_enabled
|
|
||||||
- dns_nameserver: dns_nameserver
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/baymodel-create-resp.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Delete a baymodel
|
|
||||||
==================
|
|
||||||
|
|
||||||
.. rest_method:: DELETE /v1/baymodels/{baymodel_ident}
|
|
||||||
|
|
||||||
Delete a baymodel.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 204
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
- 409
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- baymodel_ident: baymodel_ident
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
This request does not return anything in the response body.
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
|
|
||||||
Update information of baymodel
|
|
||||||
===============================
|
|
||||||
|
|
||||||
.. rest_method:: PATCH /v1/baymodels/{baymodel_ident}
|
|
||||||
|
|
||||||
Update information of one baymodel attributes using operations including:
|
|
||||||
``add``, ``replace`` or ``remove``. The attributes to ``add`` and ``replace``
|
|
||||||
in the form of ``key=value`` while ``remove`` only needs the keys.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 200
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 400
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- baymodel_ident: baymodel_ident
|
|
||||||
- path: path
|
|
||||||
- value: value
|
|
||||||
- op: op
|
|
||||||
|
|
||||||
Request Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/baymodel-update-req.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
Return new baymodel with updated attributes.
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- baymodels: baymodel_list
|
|
||||||
- insecure_registry: insecure_registry
|
|
||||||
- links: links
|
|
||||||
- http_proxy: http_proxy
|
|
||||||
- updated_at: updated_at
|
|
||||||
- floating_ip_enabled: floating_ip_enabled
|
|
||||||
- fixed_subnet: fixed_subnet
|
|
||||||
- master_flavor_id: master_flavor_id
|
|
||||||
- uuid: baymodel_id
|
|
||||||
- no_proxy: no_proxy
|
|
||||||
- https_proxy: https_proxy
|
|
||||||
- tls_disabled: tls_disabled
|
|
||||||
- keypair_id: keypair_id
|
|
||||||
- public: public_type
|
|
||||||
- labels: labels
|
|
||||||
- docker_volume_size: docker_volume_size
|
|
||||||
- server_type: server_type
|
|
||||||
- external_network_id: external_network_id
|
|
||||||
- cluster_distro: cluster_distro
|
|
||||||
- image_id: image_id
|
|
||||||
- volume_driver: volume_driver
|
|
||||||
- registry_enabled: registry_enabled
|
|
||||||
- docker_storage_driver: docker_storage_driver
|
|
||||||
- apiserver_port: apiserver_port
|
|
||||||
- name: name
|
|
||||||
- created_at: created_at
|
|
||||||
- network_driver: network_driver
|
|
||||||
- fixed_network: fixed_network
|
|
||||||
- coe: coe
|
|
||||||
- flavor_id: flavor_id
|
|
||||||
- master_lb_enabled: master_lb_enabled
|
|
||||||
- dns_nameserver: dns_nameserver
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/baymodel-create-resp.json
|
|
||||||
:language: javascript
|
|
@ -1,259 +0,0 @@
|
|||||||
.. -*- rst -*-
|
|
||||||
|
|
||||||
============
|
|
||||||
Manage Bay
|
|
||||||
============
|
|
||||||
|
|
||||||
Lists, creates, shows details for, updates, and deletes Bay.
|
|
||||||
|
|
||||||
Create new bay
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. rest_method:: POST /v1/bays
|
|
||||||
|
|
||||||
Create new bay based on bay model.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 202
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 400
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- name: name
|
|
||||||
- discovery_url: discovery_url
|
|
||||||
- master_count: master_count
|
|
||||||
- baymodel_id: baymodel_id
|
|
||||||
- node_count: node_count
|
|
||||||
- bay_create_timeout: bay_create_timeout
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Request for creating bay is asynchronous from Newton.
|
|
||||||
|
|
||||||
Request Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/bay-create-req.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- uuid: bay_id
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/bay-create-resp.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
List all bays
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. rest_method:: GET /v1/bays/
|
|
||||||
|
|
||||||
List all bays in Magnum.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 200
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- bays: bay_list
|
|
||||||
- status: status
|
|
||||||
- uuid: bay_id
|
|
||||||
- links: links
|
|
||||||
- stack_id: stack_id
|
|
||||||
- master_count: master_count
|
|
||||||
- baymodel_id: baymodel_id
|
|
||||||
- node_count: node_count
|
|
||||||
- bay_create_timeout: bay_create_timeout
|
|
||||||
- name: name
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/bay-get-all-resp.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Show details of a bay
|
|
||||||
=============================
|
|
||||||
|
|
||||||
.. rest_method:: GET /v1/bays/{bay_ident}
|
|
||||||
|
|
||||||
Get all information of a bay in Magnum.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 200
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- bay_ident: bay_ident
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- status: status
|
|
||||||
- uuid: bay_id
|
|
||||||
- links: links
|
|
||||||
- stack_id: stack_id
|
|
||||||
- created_at: created_at
|
|
||||||
- api_address: api_address
|
|
||||||
- discovery_url: discovery_url
|
|
||||||
- updated_at: updated_at
|
|
||||||
- master_count: master_count
|
|
||||||
- coe_version: coe_version
|
|
||||||
- baymodel_id: baymodel_id
|
|
||||||
- master_addresses: master_addresses
|
|
||||||
- node_count: node_count
|
|
||||||
- node_addresses: node_addresses
|
|
||||||
- status_reason: status_reason
|
|
||||||
- bay_create_timeout: bay_create_timeout
|
|
||||||
- name: name
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/bay-get-one-resp.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Delete a bay
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. rest_method:: DELETE /v1/bays/{bay_ident}
|
|
||||||
|
|
||||||
Delete a bay.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 204
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
- 409
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- bay_ident: bay_ident
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
This request does not return anything in the response body.
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
|
|
||||||
Update information of bay
|
|
||||||
=================================
|
|
||||||
|
|
||||||
.. rest_method:: PATCH /v1/bays/{bay_ident}
|
|
||||||
|
|
||||||
Update information of one bay attributes using operations
|
|
||||||
including: ``add``, ``replace`` or ``remove``. The attributes to ``add`` and
|
|
||||||
``replace`` in the form of ``key=value`` while ``remove`` only needs the keys.
|
|
||||||
|
|
||||||
Response Codes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. rest_status_code:: success status.yaml
|
|
||||||
|
|
||||||
- 202
|
|
||||||
|
|
||||||
.. rest_status_code:: error status.yaml
|
|
||||||
|
|
||||||
- 400
|
|
||||||
- 401
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
|
|
||||||
Request
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- bay_ident: bay_ident
|
|
||||||
- path: path
|
|
||||||
- value: value
|
|
||||||
- op: op
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Request for updating bay is asynchronous from Newton.
|
|
||||||
Currently only attribute ``node_count`` are supported for operation
|
|
||||||
``replace`` and ``remove``.
|
|
||||||
|
|
||||||
Request Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/bay-update-req.json
|
|
||||||
:language: javascript
|
|
||||||
|
|
||||||
Response
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
|
||||||
- uuid: bay_id
|
|
||||||
|
|
||||||
Response Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. literalinclude:: samples/bay-create-resp.json
|
|
||||||
:language: javascript
|
|
@ -1,17 +1,17 @@
|
|||||||
.. -*- rst -*-
|
.. -*- rst -*-
|
||||||
|
|
||||||
=====================================
|
=====================================
|
||||||
Manage certificates for bay/cluster
|
Manage certificates for cluster
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
Generates and show CA certificates for bay/cluster.
|
Generates and show CA certificates for cluster.
|
||||||
|
|
||||||
Show details about the CA certificate for a bay/cluster
|
Show details about the CA certificate for a cluster
|
||||||
=======================================================
|
=======================================================
|
||||||
|
|
||||||
.. rest_method:: GET /v1/certificates/{cluster_ident}?ca_cert_type={ca_cert_type}
|
.. rest_method:: GET /v1/certificates/{cluster_ident}?ca_cert_type={ca_cert_type}
|
||||||
|
|
||||||
Show CA certificate details that are associated with the created bay/cluster based on the
|
Show CA certificate details that are associated with the created cluster based on the
|
||||||
given CA certificate type.
|
given CA certificate type.
|
||||||
|
|
||||||
Response Codes
|
Response Codes
|
||||||
@ -34,11 +34,6 @@ Request
|
|||||||
- cluster_ident: cluster_ident
|
- cluster_ident: cluster_ident
|
||||||
- ca_cert_type: ca_cert_type
|
- ca_cert_type: ca_cert_type
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
After Newton, all terms related bay/baymodel will be renamed to cluster
|
|
||||||
and cluster template.
|
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@ -47,26 +42,20 @@ Response
|
|||||||
- X-Openstack-Request-Id: request_id
|
- X-Openstack-Request-Id: request_id
|
||||||
- cluster_uuid: cluster_id
|
- cluster_uuid: cluster_id
|
||||||
- pem: pem
|
- pem: pem
|
||||||
- bay_uuid: bay_id
|
|
||||||
- links: links
|
- links: links
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
After Newton, all terms related bay/baymodel will be renamed to cluster
|
|
||||||
and cluster template.
|
|
||||||
|
|
||||||
Response Example
|
Response Example
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. literalinclude:: samples/certificates-ca-show-resp.json
|
.. literalinclude:: samples/certificates-ca-show-resp.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
Generate the CA certificate for a bay/cluster
|
Generate the CA certificate for a cluster
|
||||||
=============================================
|
=============================================
|
||||||
|
|
||||||
.. rest_method:: POST /v1/certificates/
|
.. rest_method:: POST /v1/certificates/
|
||||||
|
|
||||||
Sign client key and generate the CA certificate for a bay/cluster
|
Sign client key and generate the CA certificate for a cluster
|
||||||
|
|
||||||
Response Codes
|
Response Codes
|
||||||
--------------
|
--------------
|
||||||
@ -86,14 +75,9 @@ Request
|
|||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- bay_uuid: bay_id
|
- cluster_uuid: cluster_id
|
||||||
- csr: csr
|
- csr: csr
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
After Newton, all terms related bay/baymodel will be renamed to cluster
|
|
||||||
and cluster template.
|
|
||||||
|
|
||||||
Request Example
|
Request Example
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -107,27 +91,22 @@ Response
|
|||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
- X-Openstack-Request-Id: request_id
|
||||||
- pem: pem
|
- pem: pem
|
||||||
- bay_uuid: bay_id
|
- cluster_uuid: cluster_id
|
||||||
- links: links
|
- links: links
|
||||||
- csr: csr
|
- csr: csr
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
After Newton, all terms related bay/baymodel will be renamed to cluster
|
|
||||||
and cluster template.
|
|
||||||
|
|
||||||
Response Example
|
Response Example
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. literalinclude:: samples/certificates-ca-sign-resp.json
|
.. literalinclude:: samples/certificates-ca-sign-resp.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
Rotate the CA certificate for a bay/cluster
|
Rotate the CA certificate for a cluster
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
.. rest_method:: PATCH /v1/certificates/{bay_uuid/cluster_uuid}
|
.. rest_method:: PATCH /v1/certificates/
|
||||||
|
|
||||||
Rotate the CA certificate for a bay/cluster and invalidate all user
|
Rotate the CA certificate for a cluster and invalidate all user
|
||||||
certificates.
|
certificates.
|
||||||
|
|
||||||
Response Codes
|
Response Codes
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
.. include:: versions.inc
|
.. include:: versions.inc
|
||||||
.. include:: urls.inc
|
.. include:: urls.inc
|
||||||
.. include:: bays.inc
|
|
||||||
.. include:: baymodels.inc
|
|
||||||
.. include:: clusters.inc
|
.. include:: clusters.inc
|
||||||
.. include:: clustertemplates.inc
|
.. include:: clustertemplates.inc
|
||||||
.. include:: certificates.inc
|
.. include:: certificates.inc
|
||||||
|
@ -8,18 +8,6 @@ request_id:
|
|||||||
with the request by default appears in the service logs.
|
with the request by default appears in the service logs.
|
||||||
|
|
||||||
# Path params
|
# Path params
|
||||||
bay_ident:
|
|
||||||
type: string
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
The UUID or name of bays in Magnum.
|
|
||||||
baymodel_ident:
|
|
||||||
description: |
|
|
||||||
The UUID or name of baymodels in Magnum.
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
ca_cert_type:
|
ca_cert_type:
|
||||||
type: string
|
type: string
|
||||||
in: path
|
in: path
|
||||||
@ -59,40 +47,6 @@ apiserver_port:
|
|||||||
required: true
|
required: true
|
||||||
description: |
|
description: |
|
||||||
The exposed port of COE API server.
|
The exposed port of COE API server.
|
||||||
bay_create_timeout:
|
|
||||||
type: integer
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
The timeout for bay creation in minutes. The value expected is a
|
|
||||||
positive integer and the default is 60 minutes. If the timeout is reached
|
|
||||||
during bay creation process, the operation will be aborted and the
|
|
||||||
bay status will be set to ``CREATE_FAILED``.
|
|
||||||
bay_id:
|
|
||||||
type: UUID
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
The UUID of the bay.
|
|
||||||
bay_list:
|
|
||||||
type: array
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
The list of all bays in Magnum.
|
|
||||||
The list of all clusters in Magnum.
|
|
||||||
baymodel_id:
|
|
||||||
type: UUID
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
The UUID of the baymodel.
|
|
||||||
baymodel_list:
|
|
||||||
type: array
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
The list of all baymodels in Magnum.
|
|
||||||
binary:
|
binary:
|
||||||
type: string
|
type: string
|
||||||
in: body
|
in: body
|
||||||
@ -105,7 +59,7 @@ cluster_distro:
|
|||||||
required: true
|
required: true
|
||||||
description: |
|
description: |
|
||||||
Display the attribute ``os_distro`` defined as appropriate metadata in
|
Display the attribute ``os_distro`` defined as appropriate metadata in
|
||||||
image for the bay/cluster driver.
|
image for the cluster driver.
|
||||||
cluster_id:
|
cluster_id:
|
||||||
type: UUID
|
type: UUID
|
||||||
in: body
|
in: body
|
||||||
@ -143,14 +97,14 @@ coe:
|
|||||||
description: |
|
description: |
|
||||||
Specify the Container Orchestration Engine to use. Supported COEs
|
Specify the Container Orchestration Engine to use. Supported COEs
|
||||||
include ``kubernetes``, ``swarm``. If your environment has
|
include ``kubernetes``, ``swarm``. If your environment has
|
||||||
additional bay/cluster drivers installed, refer to the bay/cluster driver
|
additional cluster drivers installed, refer to the cluster driver
|
||||||
documentation for the new COE names.
|
documentation for the new COE names.
|
||||||
coe_version:
|
coe_version:
|
||||||
type: string
|
type: string
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
description: |
|
description: |
|
||||||
Version info of chosen COE in bay/cluster for helping client in picking
|
Version info of chosen COE in cluster for helping client in picking
|
||||||
the right version of client.
|
the right version of client.
|
||||||
create_timeout:
|
create_timeout:
|
||||||
type: integer
|
type: integer
|
||||||
@ -214,7 +168,7 @@ discovery_url:
|
|||||||
|
|
||||||
https://discovery.etcd.io
|
https://discovery.etcd.io
|
||||||
|
|
||||||
In this case, Magnum will generate a unique url here for each bay and
|
In this case, Magnum will generate a unique url here for each uster and
|
||||||
store the info for the servers.
|
store the info for the servers.
|
||||||
in: body
|
in: body
|
||||||
format: uri
|
format: uri
|
||||||
@ -222,8 +176,8 @@ discovery_url:
|
|||||||
type: string
|
type: string
|
||||||
dns_nameserver:
|
dns_nameserver:
|
||||||
description: |
|
description: |
|
||||||
The DNS nameserver for the servers and containers in the bay/cluster to
|
The DNS nameserver for the servers and containers in the cluster to
|
||||||
use. This is configured in the private Neutron network for the bay/cluster.
|
use. This is configured in the private Neutron network for the cluster.
|
||||||
The default is ``8.8.8.8``.
|
The default is ``8.8.8.8``.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
@ -248,29 +202,29 @@ docker_volume_size:
|
|||||||
external_network_id:
|
external_network_id:
|
||||||
description: |
|
description: |
|
||||||
The name or network ID of a Neutron network to provide connectivity to the
|
The name or network ID of a Neutron network to provide connectivity to the
|
||||||
external internet for the bay/cluster. This network must be an external
|
external internet for the cluster. This network must be an external
|
||||||
network, i.e. its attribute ``router:external`` must be ``True``. The
|
network, i.e. its attribute ``router:external`` must be ``True``. The
|
||||||
servers in the bay/cluster will be connected to a private network and
|
servers in the cluster will be connected to a private network and
|
||||||
Magnum will create a router between this private network and the external
|
Magnum will create a router between this private network and the external
|
||||||
network. This will allow the servers to download images, access discovery
|
network. This will allow the servers to download images, access discovery
|
||||||
service, etc, and the containers to install packages, etc. In the opposite
|
service, etc, and the containers to install packages, etc. In the opposite
|
||||||
direction, floating IPs will be allocated from the external network to
|
direction, floating IPs will be allocated from the external network to
|
||||||
provide access from the external internet to servers and the container
|
provide access from the external internet to servers and the container
|
||||||
services hosted in the bay/cluster.
|
services hosted in the cluster.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
fixed_network:
|
fixed_network:
|
||||||
description: |
|
description: |
|
||||||
The name or network ID of a Neutron network to provide connectivity to
|
The name or network ID of a Neutron network to provide connectivity to
|
||||||
the internal network for the bay/cluster.
|
the internal network for the cluster.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
fixed_subnet:
|
fixed_subnet:
|
||||||
description: |
|
description: |
|
||||||
Fixed subnet that are using to allocate network address for nodes in
|
Fixed subnet that are using to allocate network address for nodes in
|
||||||
bay/cluster.
|
cluster.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
@ -341,8 +295,8 @@ id_s:
|
|||||||
image_id:
|
image_id:
|
||||||
description: |
|
description: |
|
||||||
The name or UUID of the base image in Glance to boot the servers for the
|
The name or UUID of the base image in Glance to boot the servers for the
|
||||||
bay/cluster. The image must have the attribute ``os_distro`` defined as
|
cluster. The image must have the attribute ``os_distro`` defined as
|
||||||
appropriate for the bay/cluster driver.
|
appropriate for the cluster driver.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
@ -355,9 +309,9 @@ insecure_registry:
|
|||||||
type: string
|
type: string
|
||||||
keypair_id:
|
keypair_id:
|
||||||
description: |
|
description: |
|
||||||
The name of the SSH keypair to configure in the bay/cluster servers
|
The name of the SSH keypair to configure in the cluster servers
|
||||||
for ssh access. Users will need the key to be able to ssh to the servers in
|
for ssh access. Users will need the key to be able to ssh to the servers in
|
||||||
the bay/cluster. The login name is specific to the bay/cluster driver, for
|
the cluster. The login name is specific to the cluster driver, for
|
||||||
example with fedora-atomic image, default login name is ``fedora``.
|
example with fedora-atomic image, default login name is ``fedora``.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
@ -365,8 +319,8 @@ keypair_id:
|
|||||||
labels:
|
labels:
|
||||||
description: |
|
description: |
|
||||||
Arbitrary labels in the form of ``key=value`` pairs. The accepted keys and
|
Arbitrary labels in the form of ``key=value`` pairs. The accepted keys and
|
||||||
valid values are defined in the bay/cluster drivers. They are used as a way
|
valid values are defined in the cluster drivers. They are used as a way
|
||||||
to pass additional parameters that are specific to a bay/cluster driver.
|
to pass additional parameters that are specific to a cluster driver.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: array
|
type: array
|
||||||
@ -384,40 +338,40 @@ master_addresses:
|
|||||||
type: array
|
type: array
|
||||||
master_count:
|
master_count:
|
||||||
description: |
|
description: |
|
||||||
The number of servers that will serve as master for the bay/cluster. The
|
The number of servers that will serve as master for the cluster. The
|
||||||
default is 1. Set to more than 1 master to enable High Availability. If
|
default is 1. Set to more than 1 master to enable High Availability. If
|
||||||
the option ``master-lb-enabled`` is specified in the baymodel/cluster
|
the option ``master-lb-enabled`` is specified in the cluster
|
||||||
template, the master servers will be placed in a load balancer pool.
|
template, the master servers will be placed in a load balancer pool.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
master_flavor_id:
|
master_flavor_id:
|
||||||
description: |
|
description: |
|
||||||
The flavor of the master node for this baymodel/cluster template.
|
The flavor of the master node for this cluster template.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
master_lb_enabled:
|
master_lb_enabled:
|
||||||
description: |
|
description: |
|
||||||
Since multiple masters may exist in a bay/cluster, a Neutron load balancer
|
Since multiple masters may exist in a cluster, a Neutron load balancer
|
||||||
is created to provide the API endpoint for the bay/cluster and to direct
|
is created to provide the API endpoint for the cluster and to direct
|
||||||
requests to the masters. In some cases, such as when the LBaaS service is
|
requests to the masters. In some cases, such as when the LBaaS service is
|
||||||
not available, this option can be set to ``false`` to create a bay/cluster
|
not available, this option can be set to ``false`` to create a cluster
|
||||||
without the load balancer. In this case, one of the masters will serve as
|
without the load balancer. In this case, one of the masters will serve as
|
||||||
the API endpoint. The default is ``true``, i.e. to create the load
|
the API endpoint. The default is ``true``, i.e. to create the load
|
||||||
balancer for the bay.
|
balancer for the cluster.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: boolean
|
type: boolean
|
||||||
master_lb_enabled_cluster:
|
master_lb_enabled_cluster:
|
||||||
description: |
|
description: |
|
||||||
Since multiple masters may exist in a bay/cluster, a Neutron load balancer
|
Since multiple masters may exist in a cluster, a Neutron load balancer
|
||||||
is created to provide the API endpoint for the bay/cluster and to direct
|
is created to provide the API endpoint for the cluster and to direct
|
||||||
requests to the masters. In some cases, such as when the LBaaS service is
|
requests to the masters. In some cases, such as when the LBaaS service is
|
||||||
not available, this option can be set to ``false`` to create a bay/cluster
|
not available, this option can be set to ``false`` to create a cluster
|
||||||
without the load balancer. In this case, one of the masters will serve as
|
without the load balancer. In this case, one of the masters will serve as
|
||||||
the API endpoint. The default is ``true``, i.e. to create the load
|
the API endpoint. The default is ``true``, i.e. to create the load
|
||||||
balancer for the bay.
|
balancer for the cluster.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
@ -443,7 +397,7 @@ network_driver:
|
|||||||
description: |
|
description: |
|
||||||
The name of a network driver for providing the networks for the containers.
|
The name of a network driver for providing the networks for the containers.
|
||||||
Note that this is different and separate from the Neutron network for the
|
Note that this is different and separate from the Neutron network for the
|
||||||
bay/cluster. The operation and networking model are specific to the
|
cluster. The operation and networking model are specific to the
|
||||||
particular driver.
|
particular driver.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
@ -464,7 +418,7 @@ node_addresses:
|
|||||||
type: array
|
type: array
|
||||||
node_count:
|
node_count:
|
||||||
description: |
|
description: |
|
||||||
The number of servers that will serve as node in the bay/cluster. The
|
The number of servers that will serve as node in the cluster. The
|
||||||
default is 1.
|
default is 1.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
@ -504,15 +458,15 @@ path:
|
|||||||
type: string
|
type: string
|
||||||
pem:
|
pem:
|
||||||
description: |
|
description: |
|
||||||
CA certificate for the bay/cluster.
|
CA certificate for the cluster.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
public_type:
|
public_type:
|
||||||
description: |
|
description: |
|
||||||
Access to a baymodel/cluster template is normally limited to the admin,
|
Access to a cluster template is normally limited to the admin,
|
||||||
owner or users within the same tenant as the owners. Setting this flag
|
owner or users within the same tenant as the owners. Setting this flag
|
||||||
makes the baymodel/cluster template public and accessible by other users.
|
makes the cluster template public and accessible by other users.
|
||||||
The default is not public.
|
The default is not public.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
@ -522,7 +476,7 @@ registry_enabled:
|
|||||||
Docker images by default are pulled from the public Docker registry,
|
Docker images by default are pulled from the public Docker registry,
|
||||||
but in some cases, users may want to use a private registry. This option
|
but in some cases, users may want to use a private registry. This option
|
||||||
provides an alternative registry based on the Registry V2: Magnum will
|
provides an alternative registry based on the Registry V2: Magnum will
|
||||||
create a local registry in the bay/cluster backed by swift to host the
|
create a local registry in the cluster backed by swift to host the
|
||||||
images. The default is to use the public registry.
|
images. The default is to use the public registry.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
@ -535,8 +489,8 @@ report_count:
|
|||||||
type: integer
|
type: integer
|
||||||
server_type:
|
server_type:
|
||||||
description: |
|
description: |
|
||||||
The servers in the bay/cluster can be ``vm`` or ``baremetal``. This
|
The servers in the cluster can be ``vm`` or ``baremetal``. This
|
||||||
parameter selects the type of server to create for the bay/cluster.
|
parameter selects the type of server to create for the cluster.
|
||||||
The default is ``vm``.
|
The default is ``vm``.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
@ -555,13 +509,13 @@ state:
|
|||||||
type: string
|
type: string
|
||||||
status:
|
status:
|
||||||
description: |
|
description: |
|
||||||
The current state of the bay/cluster.
|
The current state of the cluster.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
status_reason:
|
status_reason:
|
||||||
description: |
|
description: |
|
||||||
The reason of bay/cluster current status.
|
The reason of cluster current status.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
@ -574,8 +528,8 @@ tags:
|
|||||||
tls_disabled:
|
tls_disabled:
|
||||||
description: |
|
description: |
|
||||||
Transport Layer Security (TLS) is normally enabled to secure the
|
Transport Layer Security (TLS) is normally enabled to secure the
|
||||||
bay/cluster. In some cases, users may want to disable TLS in the
|
cluster. In some cases, users may want to disable TLS in the
|
||||||
bay/cluster, for instance during development or to troubleshoot certain
|
cluster, for instance during development or to troubleshoot certain
|
||||||
problems. Specifying this parameter will disable TLS so that users can
|
problems. Specifying this parameter will disable TLS so that users can
|
||||||
access the COE endpoints without a certificate. The default is TLS enabled.
|
access the COE endpoints without a certificate. The default is TLS enabled.
|
||||||
in: body
|
in: body
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name":"k8s",
|
|
||||||
"discovery_url":null,
|
|
||||||
"master_count":2,
|
|
||||||
"baymodel_id":"0562d357-8641-4759-8fed-8173f02c9633",
|
|
||||||
"node_count":2,
|
|
||||||
"bay_create_timeout":60
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"bays":[
|
|
||||||
{
|
|
||||||
"status":"CREATE_COMPLETE",
|
|
||||||
"uuid":"746e779a-751a-456b-a3e9-c883d734946f",
|
|
||||||
"links":[
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/v1/bays/746e779a-751a-456b-a3e9-c883d734946f",
|
|
||||||
"rel":"self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/bays/746e779a-751a-456b-a3e9-c883d734946f",
|
|
||||||
"rel":"bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stack_id":"9c6f1169-7300-4d08-a444-d2be38758719",
|
|
||||||
"master_count":1,
|
|
||||||
"baymodel_id":"0562d357-8641-4759-8fed-8173f02c9633",
|
|
||||||
"node_count":1,
|
|
||||||
"bay_create_timeout":60,
|
|
||||||
"name":"k8s"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"status":"CREATE_COMPLETE",
|
|
||||||
"uuid":"746e779a-751a-456b-a3e9-c883d734946f",
|
|
||||||
"links":[
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/v1/bays/746e779a-751a-456b-a3e9-c883d734946f",
|
|
||||||
"rel":"self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/bays/746e779a-751a-456b-a3e9-c883d734946f",
|
|
||||||
"rel":"bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stack_id":"9c6f1169-7300-4d08-a444-d2be38758719",
|
|
||||||
"created_at":"2016-08-29T06:51:31+00:00",
|
|
||||||
"api_address":"https://172.24.4.6:6443",
|
|
||||||
"discovery_url":"https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3",
|
|
||||||
"updated_at":"2016-08-29T06:53:24+00:00",
|
|
||||||
"master_count":1,
|
|
||||||
"coe_version": "v1.2.0",
|
|
||||||
"baymodel_id":"0562d357-8641-4759-8fed-8173f02c9633",
|
|
||||||
"master_addresses":[
|
|
||||||
"172.24.4.6"
|
|
||||||
],
|
|
||||||
"node_count":1,
|
|
||||||
"node_addresses":[
|
|
||||||
"172.24.4.13"
|
|
||||||
],
|
|
||||||
"status_reason":"Stack CREATE completed successfully",
|
|
||||||
"bay_create_timeout":60,
|
|
||||||
"name":"k8s"
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"insecure_registry":null,
|
|
||||||
"links":[
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/v1/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
|
|
||||||
"rel":"self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
|
|
||||||
"rel":"bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"http_proxy":"http://10.164.177.169:8080",
|
|
||||||
"updated_at":null,
|
|
||||||
"floating_ip_enabled":true,
|
|
||||||
"fixed_subnet":null,
|
|
||||||
"master_flavor_id":null,
|
|
||||||
"uuid":"085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
|
|
||||||
"no_proxy":"10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost",
|
|
||||||
"https_proxy":"http://10.164.177.169:8080",
|
|
||||||
"tls_disabled":false,
|
|
||||||
"keypair_id":"kp",
|
|
||||||
"public":false,
|
|
||||||
"labels":{
|
|
||||||
|
|
||||||
},
|
|
||||||
"docker_volume_size":3,
|
|
||||||
"server_type":"vm",
|
|
||||||
"external_network_id":"public",
|
|
||||||
"cluster_distro":"fedora-atomic",
|
|
||||||
"image_id":"fedora-atomic-latest",
|
|
||||||
"volume_driver":"cinder",
|
|
||||||
"registry_enabled":false,
|
|
||||||
"docker_storage_driver":"devicemapper",
|
|
||||||
"apiserver_port":null,
|
|
||||||
"name":"k8s-bm2",
|
|
||||||
"created_at":"2016-08-29T02:08:08+00:00",
|
|
||||||
"network_driver":"flannel",
|
|
||||||
"fixed_network":null,
|
|
||||||
"coe":"kubernetes",
|
|
||||||
"flavor_id":"m1.small",
|
|
||||||
"master_lb_enabled":true,
|
|
||||||
"dns_nameserver":"8.8.8.8"
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"baymodels":[
|
|
||||||
{
|
|
||||||
"insecure_registry":null,
|
|
||||||
"links":[
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/v1/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
|
|
||||||
"rel":"self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
|
|
||||||
"rel":"bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"http_proxy":"http://10.164.177.169:8080",
|
|
||||||
"updated_at":null,
|
|
||||||
"floating_ip_enabled":true,
|
|
||||||
"fixed_subnet":null,
|
|
||||||
"master_flavor_id":null,
|
|
||||||
"uuid":"085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
|
|
||||||
"no_proxy":"10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost",
|
|
||||||
"https_proxy":"http://10.164.177.169:8080",
|
|
||||||
"tls_disabled":false,
|
|
||||||
"keypair_id":"kp",
|
|
||||||
"public":false,
|
|
||||||
"labels":{
|
|
||||||
|
|
||||||
},
|
|
||||||
"docker_volume_size":3,
|
|
||||||
"server_type":"vm",
|
|
||||||
"external_network_id":"public",
|
|
||||||
"cluster_distro":"fedora-atomic",
|
|
||||||
"image_id":"fedora-atomic-latest",
|
|
||||||
"volume_driver":"cinder",
|
|
||||||
"registry_enabled":false,
|
|
||||||
"docker_storage_driver":"devicemapper",
|
|
||||||
"apiserver_port":null,
|
|
||||||
"name":"k8s-bm2",
|
|
||||||
"created_at":"2016-08-29T02:08:08+00:00",
|
|
||||||
"network_driver":"flannel",
|
|
||||||
"fixed_network":null,
|
|
||||||
"coe":"kubernetes",
|
|
||||||
"flavor_id":"m1.small",
|
|
||||||
"master_lb_enabled":true,
|
|
||||||
"dns_nameserver":"8.8.8.8"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
"cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
||||||
"pem":"-----BEGIN CERTIFICATE-----\nMIICzDCCAbSgAwIBAgIQOOkVcEN7TNa9E80GoUs4xDANBgkqhkiG9w0BAQsFADAO\n-----END CERTIFICATE-----\n",
|
"pem":"-----BEGIN CERTIFICATE-----\nMIICzDCCAbSgAwIBAgIQOOkVcEN7TNa9E80GoUs4xDANBgkqhkiG9w0BAQsFADAO\n-----END CERTIFICATE-----\n",
|
||||||
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
|
||||||
"links":[
|
"links":[
|
||||||
{
|
{
|
||||||
"href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
"href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
"cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
||||||
"csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n"
|
"csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n"
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"pem":"-----BEGIN CERTIFICATE-----\nMIIDxDCCAqygAwIBAgIRALgUbIjdKUy8lqErJmCxVfkwDQYJKoZIhvcNAQELBQAw\n-----END CERTIFICATE-----\n",
|
"pem":"-----BEGIN CERTIFICATE-----\nMIIDxDCCAqygAwIBAgIRALgUbIjdKUy8lqErJmCxVfkwDQYJKoZIhvcNAQELBQAw\n-----END CERTIFICATE-----\n",
|
||||||
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
"cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
||||||
"links":[
|
"links":[
|
||||||
{
|
{
|
||||||
"href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
"href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
"uuid":"731387cf-a92b-4c36-981e-3271d63e5597",
|
"uuid":"731387cf-a92b-4c36-981e-3271d63e5597",
|
||||||
"links":[
|
"links":[
|
||||||
{
|
{
|
||||||
"href":"http://10.164.180.104:9511/v1/bays/731387cf-a92b-4c36-981e-3271d63e5597",
|
"href":"http://10.164.180.104:9511/v1/clusters/731387cf-a92b-4c36-981e-3271d63e5597",
|
||||||
"rel":"self"
|
"rel":"self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"href":"http://10.164.180.104:9511/bays/731387cf-a92b-4c36-981e-3271d63e5597",
|
"href":"http://10.164.180.104:9511/clusters/731387cf-a92b-4c36-981e-3271d63e5597",
|
||||||
"rel":"bookmark"
|
"rel":"bookmark"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -26,16 +26,6 @@
|
|||||||
"rel":"bookmark"
|
"rel":"bookmark"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bays":[
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/v1/bays/",
|
|
||||||
"rel":"self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/bays/",
|
|
||||||
"rel":"bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clustertemplates":[
|
"clustertemplates":[
|
||||||
{
|
{
|
||||||
"href":"http://10.164.180.104:9511/v1/clustertemplates/",
|
"href":"http://10.164.180.104:9511/v1/clustertemplates/",
|
||||||
@ -66,15 +56,5 @@
|
|||||||
"rel":"bookmark"
|
"rel":"bookmark"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"baymodels":[
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/v1/baymodels/",
|
|
||||||
"rel":"self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href":"http://10.164.180.104:9511/baymodels/",
|
|
||||||
"rel":"bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"v1"
|
"id":"v1"
|
||||||
}
|
}
|
@ -28,9 +28,9 @@ class ExampleTemplate(template_def.BaseTemplateDefinition):
|
|||||||
super(ExampleTemplate, self).__init__()
|
super(ExampleTemplate, self).__init__()
|
||||||
|
|
||||||
self.add_output('server_address',
|
self.add_output('server_address',
|
||||||
bay_attr='api_address')
|
cluster_attr='api_address')
|
||||||
self.add_output('node_addresses',
|
self.add_output('node_addresses',
|
||||||
bay_attr='node_addresses')
|
cluster_attr='node_addresses')
|
||||||
|
|
||||||
def template_path(self):
|
def template_path(self):
|
||||||
return os.path.join(os.path.dirname(__file__), 'example.yaml')
|
return os.path.join(os.path.dirname(__file__), 'example.yaml')
|
||||||
|
@ -100,9 +100,7 @@ When a user creates a cluster, Magnum will dynamically create a service account
|
|||||||
for the cluster. The service account will be used by the cluster to
|
for the cluster. The service account will be used by the cluster to
|
||||||
access the OpenStack services (i.e. Neutron, Swift, etc.). A trust relationship
|
access the OpenStack services (i.e. Neutron, Swift, etc.). A trust relationship
|
||||||
will be created between the user who created the cluster (the "trustor") and
|
will be created between the user who created the cluster (the "trustor") and
|
||||||
the service account created for the cluster (the "trustee"). For details,
|
the service account created for the cluster (the "trustee").
|
||||||
please refer to
|
|
||||||
`Create a trustee user for each bay <https://opendev.org/openstack/magnum/src/branch/master/specs/create-trustee-user-for-each-bay.rst>`_.
|
|
||||||
|
|
||||||
If Magnum fails to create the trustee, check the magnum config file (usually
|
If Magnum fails to create the trustee, check the magnum config file (usually
|
||||||
in /etc/magnum/magnum.conf). Make sure 'trustee_*' and 'www_authenticate_uri'
|
in /etc/magnum/magnum.conf). Make sure 'trustee_*' and 'www_authenticate_uri'
|
||||||
|
@ -481,7 +481,7 @@ the table are linked to more details elsewhere in the user guide.
|
|||||||
Cluster
|
Cluster
|
||||||
-------
|
-------
|
||||||
|
|
||||||
A cluster (previously known as bay) is an instance of the ClusterTemplate
|
A cluster is an instance of the ClusterTemplate
|
||||||
of a COE. Magnum deploys a cluster by referring to the attributes
|
of a COE. Magnum deploys a cluster by referring to the attributes
|
||||||
defined in the particular ClusterTemplate as well as a few additional
|
defined in the particular ClusterTemplate as well as a few additional
|
||||||
parameters for the cluster. Magnum deploys the orchestration templates
|
parameters for the cluster. Magnum deploys the orchestration templates
|
||||||
@ -567,13 +567,6 @@ Heat. For more help on Heat stack troubleshooting, refer to the
|
|||||||
Create
|
Create
|
||||||
++++++
|
++++++
|
||||||
|
|
||||||
**NOTE** bay-<command> are the deprecated versions of these commands and are
|
|
||||||
still support in current release. They will be removed in a future version.
|
|
||||||
Any references to the term bay will be replaced in the parameters when using
|
|
||||||
the 'bay' versions of the commands. For example, in 'bay-create' --baymodel
|
|
||||||
is used as the baymodel parameter for this command instead of
|
|
||||||
--cluster-template.
|
|
||||||
|
|
||||||
The 'cluster-create' command deploys a cluster, for example::
|
The 'cluster-create' command deploys a cluster, for example::
|
||||||
|
|
||||||
openstack coe cluster create mycluster \
|
openstack coe cluster create mycluster \
|
||||||
@ -2884,14 +2877,11 @@ Supported Events
|
|||||||
----------------
|
----------------
|
||||||
|
|
||||||
The following table displays the corresponding relationship between resource
|
The following table displays the corresponding relationship between resource
|
||||||
types and operations. The bay type is deprecated and will be removed in a
|
types and operations.
|
||||||
future version. Cluster is the new equivalent term.
|
|
||||||
|
|
||||||
+---------------+----------------------------+-------------------------+
|
+---------------+----------------------------+-------------------------+
|
||||||
| resource type | supported operations | typeURI |
|
| resource type | supported operations | typeURI |
|
||||||
+===============+============================+=========================+
|
+===============+============================+=========================+
|
||||||
| bay | create, update, delete | service/magnum/bay |
|
|
||||||
+---------------+----------------------------+-------------------------+
|
|
||||||
| cluster | create, update, delete | service/magnum/cluster |
|
| cluster | create, update, delete | service/magnum/cluster |
|
||||||
+---------------+----------------------------+-------------------------+
|
+---------------+----------------------------+-------------------------+
|
||||||
|
|
||||||
|
@ -24,8 +24,6 @@ from wsme import types as wtypes
|
|||||||
|
|
||||||
from magnum.api.controllers import base as controllers_base
|
from magnum.api.controllers import base as controllers_base
|
||||||
from magnum.api.controllers import link
|
from magnum.api.controllers import link
|
||||||
from magnum.api.controllers.v1 import bay
|
|
||||||
from magnum.api.controllers.v1 import baymodel
|
|
||||||
from magnum.api.controllers.v1 import certificate
|
from magnum.api.controllers.v1 import certificate
|
||||||
from magnum.api.controllers.v1 import cluster
|
from magnum.api.controllers.v1 import cluster
|
||||||
from magnum.api.controllers.v1 import cluster_template
|
from magnum.api.controllers.v1 import cluster_template
|
||||||
@ -76,12 +74,6 @@ class V1(controllers_base.APIBase):
|
|||||||
links = [link.Link]
|
links = [link.Link]
|
||||||
"""Links that point to a specific URL for this version and documentation"""
|
"""Links that point to a specific URL for this version and documentation"""
|
||||||
|
|
||||||
baymodels = [link.Link]
|
|
||||||
"""Links to the baymodels resource"""
|
|
||||||
|
|
||||||
bays = [link.Link]
|
|
||||||
"""Links to the bays resource"""
|
|
||||||
|
|
||||||
clustertemplates = [link.Link]
|
clustertemplates = [link.Link]
|
||||||
"""Links to the clustertemplates resource"""
|
"""Links to the clustertemplates resource"""
|
||||||
|
|
||||||
@ -119,18 +111,6 @@ class V1(controllers_base.APIBase):
|
|||||||
bookmark=True, type='text/html')]
|
bookmark=True, type='text/html')]
|
||||||
v1.media_types = [MediaType('application/json',
|
v1.media_types = [MediaType('application/json',
|
||||||
'application/vnd.openstack.magnum.v1+json')]
|
'application/vnd.openstack.magnum.v1+json')]
|
||||||
v1.baymodels = [link.Link.make_link('self', pecan.request.host_url,
|
|
||||||
'baymodels', ''),
|
|
||||||
link.Link.make_link('bookmark',
|
|
||||||
pecan.request.host_url,
|
|
||||||
'baymodels', '',
|
|
||||||
bookmark=True)]
|
|
||||||
v1.bays = [link.Link.make_link('self', pecan.request.host_url,
|
|
||||||
'bays', ''),
|
|
||||||
link.Link.make_link('bookmark',
|
|
||||||
pecan.request.host_url,
|
|
||||||
'bays', '',
|
|
||||||
bookmark=True)]
|
|
||||||
v1.clustertemplates = [link.Link.make_link('self',
|
v1.clustertemplates = [link.Link.make_link('self',
|
||||||
pecan.request.host_url,
|
pecan.request.host_url,
|
||||||
'clustertemplates', ''),
|
'clustertemplates', ''),
|
||||||
@ -189,8 +169,6 @@ class V1(controllers_base.APIBase):
|
|||||||
class Controller(controllers_base.Controller):
|
class Controller(controllers_base.Controller):
|
||||||
"""Version 1 API controller root."""
|
"""Version 1 API controller root."""
|
||||||
|
|
||||||
bays = bay.BaysController()
|
|
||||||
baymodels = baymodel.BayModelsController()
|
|
||||||
clusters = cluster.ClustersController()
|
clusters = cluster.ClustersController()
|
||||||
clustertemplates = cluster_template.ClusterTemplatesController()
|
clustertemplates = cluster_template.ClusterTemplatesController()
|
||||||
quotas = quota.QuotaController()
|
quotas = quota.QuotaController()
|
||||||
|
@ -1,603 +0,0 @@
|
|||||||
# Copyright 2013 UnitedStack Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
import pecan
|
|
||||||
import six
|
|
||||||
import wsme
|
|
||||||
from wsme import types as wtypes
|
|
||||||
|
|
||||||
from magnum.api import attr_validator
|
|
||||||
from magnum.api.controllers import base
|
|
||||||
from magnum.api.controllers import link
|
|
||||||
from magnum.api.controllers.v1 import collection
|
|
||||||
from magnum.api.controllers.v1 import types
|
|
||||||
from magnum.api import expose
|
|
||||||
from magnum.api import utils as api_utils
|
|
||||||
from magnum.api.validation import validate_cluster_properties
|
|
||||||
from magnum.common import clients
|
|
||||||
from magnum.common import exception
|
|
||||||
from magnum.common import name_generator
|
|
||||||
from magnum.common import policy
|
|
||||||
from magnum import objects
|
|
||||||
from magnum.objects import fields
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BayID(wtypes.Base):
|
|
||||||
uuid = types.uuid
|
|
||||||
|
|
||||||
def __init__(self, uuid):
|
|
||||||
self.uuid = uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Bay(base.APIBase):
|
|
||||||
"""API representation of a bay.
|
|
||||||
|
|
||||||
This class enforces type checking and value constraints, and converts
|
|
||||||
between the internal object model and the API representation of a bay.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_baymodel_id = None
|
|
||||||
|
|
||||||
def _get_baymodel_id(self):
|
|
||||||
return self._baymodel_id
|
|
||||||
|
|
||||||
def _set_baymodel_id(self, value):
|
|
||||||
if value and self._baymodel_id != value:
|
|
||||||
try:
|
|
||||||
baymodel = api_utils.get_resource('ClusterTemplate', value)
|
|
||||||
self._baymodel_id = baymodel.uuid
|
|
||||||
except exception.ClusterTemplateNotFound as e:
|
|
||||||
# Change error code because 404 (NotFound) is inappropriate
|
|
||||||
# response for a POST request to create a Cluster
|
|
||||||
e.code = 400 # BadRequest
|
|
||||||
raise
|
|
||||||
elif value == wtypes.Unset:
|
|
||||||
self._baymodel_id = wtypes.Unset
|
|
||||||
|
|
||||||
uuid = types.uuid
|
|
||||||
"""Unique UUID for this bay"""
|
|
||||||
|
|
||||||
name = wtypes.StringType(min_length=1, max_length=242,
|
|
||||||
pattern='^[a-zA-Z][a-zA-Z0-9_.-]*$')
|
|
||||||
"""Name of this bay, max length is limited to 242 because of heat stack
|
|
||||||
requires max length limit to 255, and Magnum amend a uuid length"""
|
|
||||||
|
|
||||||
baymodel_id = wsme.wsproperty(wtypes.text, _get_baymodel_id,
|
|
||||||
_set_baymodel_id, mandatory=True)
|
|
||||||
"""The baymodel UUID"""
|
|
||||||
|
|
||||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
|
||||||
"""The node count for this bay. Default to 1 if not set"""
|
|
||||||
|
|
||||||
master_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
|
||||||
"""The number of master nodes for this bay. Default to 1 if not set"""
|
|
||||||
|
|
||||||
docker_volume_size = wtypes.IntegerType(minimum=1)
|
|
||||||
"""The size in GB of the docker volume"""
|
|
||||||
|
|
||||||
labels = wtypes.DictType(wtypes.text, types.MultiType(wtypes.text,
|
|
||||||
six.integer_types,
|
|
||||||
bool,
|
|
||||||
float))
|
|
||||||
"""One or more key/value pairs"""
|
|
||||||
|
|
||||||
master_flavor_id = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The master flavor of this Bay"""
|
|
||||||
|
|
||||||
flavor_id = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The flavor of this Bay"""
|
|
||||||
|
|
||||||
bay_create_timeout = wsme.wsattr(wtypes.IntegerType(minimum=0), default=60)
|
|
||||||
"""Timeout for creating the bay in minutes. Default to 60 if not set"""
|
|
||||||
|
|
||||||
links = wsme.wsattr([link.Link], readonly=True)
|
|
||||||
"""A list containing a self link and associated bay links"""
|
|
||||||
|
|
||||||
stack_id = wsme.wsattr(wtypes.text, readonly=True)
|
|
||||||
"""Stack id of the heat stack"""
|
|
||||||
|
|
||||||
status = wtypes.Enum(wtypes.text, *fields.ClusterStatus.ALL)
|
|
||||||
"""Status of the bay from the heat stack"""
|
|
||||||
|
|
||||||
status_reason = wtypes.text
|
|
||||||
"""Status reason of the bay from the heat stack"""
|
|
||||||
|
|
||||||
discovery_url = wtypes.text
|
|
||||||
"""Url used for bay node discovery"""
|
|
||||||
|
|
||||||
api_address = wsme.wsattr(wtypes.text, readonly=True)
|
|
||||||
"""Api address of cluster master node"""
|
|
||||||
|
|
||||||
coe_version = wsme.wsattr(wtypes.text, readonly=True)
|
|
||||||
"""Version of the COE software currently running in this cluster.
|
|
||||||
Example: swarm version or kubernetes version."""
|
|
||||||
|
|
||||||
container_version = wsme.wsattr(wtypes.text, readonly=True)
|
|
||||||
"""Version of the container software. Example: docker version."""
|
|
||||||
|
|
||||||
node_addresses = wsme.wsattr([wtypes.text], readonly=True)
|
|
||||||
"""IP addresses of cluster slave nodes"""
|
|
||||||
|
|
||||||
master_addresses = wsme.wsattr([wtypes.text], readonly=True)
|
|
||||||
"""IP addresses of cluster master nodes"""
|
|
||||||
|
|
||||||
bay_faults = wsme.wsattr(wtypes.DictType(wtypes.text, wtypes.text))
|
|
||||||
"""Fault info collected from the heat resources of this bay"""
|
|
||||||
|
|
||||||
fixed_network = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The fixed network name to attach to the Cluster"""
|
|
||||||
|
|
||||||
fixed_subnet = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The fixed subnet name to attach to the Cluster"""
|
|
||||||
|
|
||||||
floating_ip_enabled = wsme.wsattr(types.boolean, default=True)
|
|
||||||
"""Indicates whether created clusters should have a floating ip or not."""
|
|
||||||
|
|
||||||
master_lb_enabled = wsme.wsattr(types.boolean)
|
|
||||||
"""Indicates whether created clusters should have a load balancer for master
|
|
||||||
nodes or not.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super(Bay, self).__init__()
|
|
||||||
|
|
||||||
self.fields = []
|
|
||||||
for field in objects.Cluster.fields:
|
|
||||||
# Skip fields we do not expose.
|
|
||||||
if not hasattr(self, field):
|
|
||||||
continue
|
|
||||||
self.fields.append(field)
|
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
|
||||||
|
|
||||||
# Set the renamed attributes for bay backwards compatibility
|
|
||||||
self.fields.append('baymodel_id')
|
|
||||||
if 'baymodel_id' in kwargs.keys():
|
|
||||||
setattr(self, 'cluster_template_id',
|
|
||||||
kwargs.get('baymodel_id', None))
|
|
||||||
setattr(self, 'baymodel_id',
|
|
||||||
kwargs.get('baymodel_id', None))
|
|
||||||
else:
|
|
||||||
setattr(self, 'baymodel_id', kwargs.get('cluster_template_id',
|
|
||||||
None))
|
|
||||||
|
|
||||||
self.fields.append('bay_create_timeout')
|
|
||||||
if 'bay_create_timeout' in kwargs.keys():
|
|
||||||
setattr(self, 'create_timeout',
|
|
||||||
kwargs.get('bay_create_timeout', wtypes.Unset))
|
|
||||||
setattr(self, 'bay_create_timeout',
|
|
||||||
kwargs.get('bay_create_timeout', wtypes.Unset))
|
|
||||||
else:
|
|
||||||
setattr(self, 'bay_create_timeout', kwargs.get('create_timeout',
|
|
||||||
wtypes.Unset))
|
|
||||||
|
|
||||||
self.fields.append('bay_faults')
|
|
||||||
if 'bay_faults' in kwargs.keys():
|
|
||||||
setattr(self, 'faults',
|
|
||||||
kwargs.get('bay_faults', wtypes.Unset))
|
|
||||||
setattr(self, 'bay_faults',
|
|
||||||
kwargs.get('bay_faults', wtypes.Unset))
|
|
||||||
else:
|
|
||||||
setattr(self, 'bay_faults', kwargs.get('faults', wtypes.Unset))
|
|
||||||
|
|
||||||
nodegroup_fields = ['node_count', 'master_count',
|
|
||||||
'node_addresses', 'master_addresses']
|
|
||||||
for field in nodegroup_fields:
|
|
||||||
self.fields.append(field)
|
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _convert_with_links(bay, url, expand=True):
|
|
||||||
if not expand:
|
|
||||||
bay.unset_fields_except(['uuid', 'name', 'baymodel_id',
|
|
||||||
'docker_volume_size', 'labels',
|
|
||||||
'master_flavor_id', 'flavor_id',
|
|
||||||
'node_count', 'status',
|
|
||||||
'bay_create_timeout', 'master_count',
|
|
||||||
'stack_id'])
|
|
||||||
|
|
||||||
bay.links = [link.Link.make_link('self', url,
|
|
||||||
'bays', bay.uuid),
|
|
||||||
link.Link.make_link('bookmark', url,
|
|
||||||
'bays', bay.uuid,
|
|
||||||
bookmark=True)]
|
|
||||||
return bay
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def convert_with_links(cls, rpc_bay, expand=True):
|
|
||||||
bay = Bay(**rpc_bay.as_dict())
|
|
||||||
return cls._convert_with_links(bay, pecan.request.host_url, expand)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls, expand=True):
|
|
||||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
|
||||||
name='example',
|
|
||||||
baymodel_id='4a96ac4b-2447-43f1-8ca6-9fd6f36d146d',
|
|
||||||
node_count=2,
|
|
||||||
master_count=1,
|
|
||||||
docker_volume_size=1,
|
|
||||||
labels={},
|
|
||||||
master_flavor_id=None,
|
|
||||||
flavor_id=None,
|
|
||||||
bay_create_timeout=15,
|
|
||||||
stack_id='49dc23f5-ffc9-40c3-9d34-7be7f9e34d63',
|
|
||||||
status=fields.ClusterStatus.CREATE_COMPLETE,
|
|
||||||
status_reason="CREATE completed successfully",
|
|
||||||
api_address='172.24.4.3',
|
|
||||||
node_addresses=['172.24.4.4', '172.24.4.5'],
|
|
||||||
created_at=timeutils.utcnow(),
|
|
||||||
updated_at=timeutils.utcnow(),
|
|
||||||
coe_version=None,
|
|
||||||
container_version=None)
|
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
|
|
||||||
|
|
||||||
def as_dict(self):
|
|
||||||
"""Render this object as a dict of its fields."""
|
|
||||||
|
|
||||||
# Override this for old bay values
|
|
||||||
d = super(Bay, self).as_dict()
|
|
||||||
|
|
||||||
d['cluster_template_id'] = d['baymodel_id']
|
|
||||||
del d['baymodel_id']
|
|
||||||
|
|
||||||
d['create_timeout'] = d['bay_create_timeout']
|
|
||||||
del d['bay_create_timeout']
|
|
||||||
|
|
||||||
if 'bay_faults' in d.keys():
|
|
||||||
d['faults'] = d['bay_faults']
|
|
||||||
del d['bay_faults']
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
class BayPatchType(types.JsonPatchType):
|
|
||||||
_api_base = Bay
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def internal_attrs():
|
|
||||||
internal_attrs = ['/api_address', '/node_addresses',
|
|
||||||
'/master_addresses', '/stack_id',
|
|
||||||
'/ca_cert_ref', '/magnum_cert_ref',
|
|
||||||
'/trust_id', '/trustee_user_name',
|
|
||||||
'/trustee_password', '/trustee_user_id',
|
|
||||||
'/etcd_ca_cert_ref', '/front_proxy_ca_cert_ref']
|
|
||||||
return types.JsonPatchType.internal_attrs() + internal_attrs
|
|
||||||
|
|
||||||
|
|
||||||
class BayCollection(collection.Collection):
|
|
||||||
"""API representation of a collection of bays."""
|
|
||||||
|
|
||||||
bays = [Bay]
|
|
||||||
"""A list containing bays objects"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self._type = 'bays'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def convert_with_links(rpc_bays, limit, url=None, expand=False, **kwargs):
|
|
||||||
collection = BayCollection()
|
|
||||||
collection.bays = [Bay.convert_with_links(p, expand)
|
|
||||||
for p in rpc_bays]
|
|
||||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
||||||
return collection
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
sample = cls()
|
|
||||||
sample.bays = [Bay.sample(expand=False)]
|
|
||||||
return sample
|
|
||||||
|
|
||||||
|
|
||||||
class BaysController(base.Controller):
|
|
||||||
"""REST controller for Bays."""
|
|
||||||
def __init__(self):
|
|
||||||
super(BaysController, self).__init__()
|
|
||||||
|
|
||||||
_custom_actions = {
|
|
||||||
'detail': ['GET'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _generate_name_for_bay(self, context):
|
|
||||||
"""Generate a random name like: zeta-22-bay."""
|
|
||||||
name_gen = name_generator.NameGenerator()
|
|
||||||
name = name_gen.generate()
|
|
||||||
return name + '-bay'
|
|
||||||
|
|
||||||
def _get_bays_collection(self, marker, limit,
|
|
||||||
sort_key, sort_dir, expand=False,
|
|
||||||
resource_url=None):
|
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
|
||||||
|
|
||||||
marker_obj = None
|
|
||||||
if marker:
|
|
||||||
marker_obj = objects.Cluster.get_by_uuid(pecan.request.context,
|
|
||||||
marker)
|
|
||||||
|
|
||||||
bays = objects.Cluster.list(pecan.request.context, limit,
|
|
||||||
marker_obj, sort_key=sort_key,
|
|
||||||
sort_dir=sort_dir)
|
|
||||||
|
|
||||||
return BayCollection.convert_with_links(bays, limit,
|
|
||||||
url=resource_url,
|
|
||||||
expand=expand,
|
|
||||||
sort_key=sort_key,
|
|
||||||
sort_dir=sort_dir)
|
|
||||||
|
|
||||||
@expose.expose(BayCollection, types.uuid, int, wtypes.text,
|
|
||||||
wtypes.text)
|
|
||||||
def get_all(self, marker=None, limit=None, sort_key='id',
|
|
||||||
sort_dir='asc'):
|
|
||||||
"""Retrieve a list of bays.
|
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
|
||||||
:param sort_key: column to sort results by. Default: id.
|
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'bay:get_all',
|
|
||||||
action='bay:get_all')
|
|
||||||
return self._get_bays_collection(marker, limit, sort_key,
|
|
||||||
sort_dir)
|
|
||||||
|
|
||||||
@expose.expose(BayCollection, types.uuid, int, wtypes.text,
|
|
||||||
wtypes.text)
|
|
||||||
def detail(self, marker=None, limit=None, sort_key='id',
|
|
||||||
sort_dir='asc'):
|
|
||||||
"""Retrieve a list of bays with detail.
|
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
|
||||||
:param sort_key: column to sort results by. Default: id.
|
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'bay:detail',
|
|
||||||
action='bay:detail')
|
|
||||||
|
|
||||||
# NOTE(lucasagomes): /detail should only work against collections
|
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
|
||||||
if parent != "bays":
|
|
||||||
raise exception.HTTPNotFound
|
|
||||||
|
|
||||||
expand = True
|
|
||||||
resource_url = '/'.join(['bays', 'detail'])
|
|
||||||
return self._get_bays_collection(marker, limit,
|
|
||||||
sort_key, sort_dir, expand,
|
|
||||||
resource_url)
|
|
||||||
|
|
||||||
def _collect_fault_info(self, context, bay):
|
|
||||||
"""Collect fault info from heat resources of given bay
|
|
||||||
|
|
||||||
and store them into bay.bay_faults.
|
|
||||||
"""
|
|
||||||
osc = clients.OpenStackClients(context)
|
|
||||||
filters = {'status': 'FAILED'}
|
|
||||||
try:
|
|
||||||
failed_resources = osc.heat().resources.list(
|
|
||||||
bay.stack_id, nested_depth=2, filters=filters)
|
|
||||||
except Exception as e:
|
|
||||||
failed_resources = []
|
|
||||||
LOG.warning("Failed to retrieve failed resources for "
|
|
||||||
"bay %(bay)s from Heat stack %(stack)s "
|
|
||||||
"due to error: %(e)s",
|
|
||||||
{'bay': bay.uuid, 'stack': bay.stack_id, 'e': e},
|
|
||||||
exc_info=True)
|
|
||||||
|
|
||||||
return {res.resource_name: res.resource_status_reason
|
|
||||||
for res in failed_resources}
|
|
||||||
|
|
||||||
@expose.expose(Bay, types.uuid_or_name)
|
|
||||||
def get_one(self, bay_ident):
|
|
||||||
"""Retrieve information about the given bay.
|
|
||||||
|
|
||||||
:param bay_ident: UUID of a bay or logical name of the bay.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
bay = api_utils.get_resource('Cluster', bay_ident)
|
|
||||||
policy.enforce(context, 'bay:get', bay.as_dict(),
|
|
||||||
action='bay:get')
|
|
||||||
|
|
||||||
bay = Bay.convert_with_links(bay)
|
|
||||||
|
|
||||||
if bay.status in fields.ClusterStatus.STATUS_FAILED:
|
|
||||||
bay.bay_faults = self._collect_fault_info(context, bay)
|
|
||||||
|
|
||||||
return bay
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.1", "1.1")
|
|
||||||
@expose.expose(Bay, body=Bay, status_code=201)
|
|
||||||
def post(self, bay):
|
|
||||||
"""Create a new bay.
|
|
||||||
|
|
||||||
:param bay: a bay within the request body.
|
|
||||||
"""
|
|
||||||
new_bay, node_count, master_count = self._post(bay)
|
|
||||||
res_bay = pecan.request.rpcapi.cluster_create(new_bay,
|
|
||||||
master_count, node_count,
|
|
||||||
bay.bay_create_timeout)
|
|
||||||
|
|
||||||
# Set the HTTP Location Header
|
|
||||||
pecan.response.location = link.build_url('bays', res_bay.uuid)
|
|
||||||
return Bay.convert_with_links(res_bay)
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.2") # noqa
|
|
||||||
@expose.expose(BayID, body=Bay, status_code=202)
|
|
||||||
def post(self, bay): # noqa
|
|
||||||
"""Create a new bay.
|
|
||||||
|
|
||||||
:param bay: a bay within the request body.
|
|
||||||
"""
|
|
||||||
new_bay, node_count, master_count = self._post(bay)
|
|
||||||
pecan.request.rpcapi.cluster_create_async(new_bay,
|
|
||||||
master_count, node_count,
|
|
||||||
bay.bay_create_timeout)
|
|
||||||
return BayID(new_bay.uuid)
|
|
||||||
|
|
||||||
def _post(self, bay):
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'bay:create',
|
|
||||||
action='bay:create')
|
|
||||||
baymodel = objects.ClusterTemplate.get_by_uuid(context,
|
|
||||||
bay.baymodel_id)
|
|
||||||
|
|
||||||
# If docker_volume_size is not present, use baymodel value
|
|
||||||
if bay.docker_volume_size == wtypes.Unset:
|
|
||||||
bay.docker_volume_size = baymodel.docker_volume_size
|
|
||||||
|
|
||||||
# If labels is not present, use baymodel value
|
|
||||||
if bay.labels is None:
|
|
||||||
bay.labels = baymodel.labels
|
|
||||||
|
|
||||||
# If master_flavor_id is not present, use baymodel value
|
|
||||||
if bay.master_flavor_id == wtypes.Unset or not bay.master_flavor_id:
|
|
||||||
bay.master_flavor_id = baymodel.master_flavor_id
|
|
||||||
|
|
||||||
# If flavor_id is not present, use baymodel value
|
|
||||||
if bay.flavor_id == wtypes.Unset or not bay.flavor_id:
|
|
||||||
bay.flavor_id = baymodel.flavor_id
|
|
||||||
|
|
||||||
bay_dict = bay.as_dict()
|
|
||||||
bay_dict['keypair'] = baymodel.keypair_id
|
|
||||||
attr_validator.validate_os_resources(context, baymodel.as_dict(),
|
|
||||||
bay_dict)
|
|
||||||
attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict())
|
|
||||||
|
|
||||||
bay_dict['project_id'] = context.project_id
|
|
||||||
bay_dict['user_id'] = context.user_id
|
|
||||||
# NOTE(yuywz): We will generate a random human-readable name for
|
|
||||||
# bay if the name is not specified by user.
|
|
||||||
name = bay_dict.get('name') or self._generate_name_for_bay(context)
|
|
||||||
node_count = bay_dict.pop('node_count')
|
|
||||||
master_count = bay_dict.pop('master_count')
|
|
||||||
bay_dict['name'] = name
|
|
||||||
bay_dict['coe_version'] = None
|
|
||||||
bay_dict['container_version'] = None
|
|
||||||
|
|
||||||
new_bay = objects.Cluster(context, **bay_dict)
|
|
||||||
new_bay.uuid = uuid.uuid4()
|
|
||||||
return new_bay, node_count, master_count
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.1", "1.1")
|
|
||||||
@wsme.validate(types.uuid, [BayPatchType])
|
|
||||||
@expose.expose(Bay, types.uuid_or_name, body=[BayPatchType])
|
|
||||||
def patch(self, bay_ident, patch):
|
|
||||||
"""Update an existing bay.
|
|
||||||
|
|
||||||
:param bay_ident: UUID or logical name of a bay.
|
|
||||||
:param patch: a json PATCH document to apply to this bay.
|
|
||||||
"""
|
|
||||||
bay, node_count = self._patch(bay_ident, patch)
|
|
||||||
res_bay = pecan.request.rpcapi.cluster_update(bay, node_count)
|
|
||||||
return Bay.convert_with_links(res_bay)
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.2", "1.2") # noqa
|
|
||||||
@wsme.validate(types.uuid, [BayPatchType])
|
|
||||||
@expose.expose(BayID, types.uuid_or_name, body=[BayPatchType],
|
|
||||||
status_code=202)
|
|
||||||
def patch(self, bay_ident, patch): # noqa
|
|
||||||
"""Update an existing bay.
|
|
||||||
|
|
||||||
:param bay_ident: UUID or logical name of a bay.
|
|
||||||
:param patch: a json PATCH document to apply to this bay.
|
|
||||||
"""
|
|
||||||
bay, node_count = self._patch(bay_ident, patch)
|
|
||||||
pecan.request.rpcapi.cluster_update_async(bay, node_count)
|
|
||||||
return BayID(bay.uuid)
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.3") # noqa
|
|
||||||
@wsme.validate(types.uuid, bool, [BayPatchType])
|
|
||||||
@expose.expose(BayID, types.uuid_or_name, types.boolean,
|
|
||||||
body=[BayPatchType], status_code=202)
|
|
||||||
def patch(self, bay_ident, rollback=False, patch=None): # noqa
|
|
||||||
"""Update an existing bay.
|
|
||||||
|
|
||||||
:param bay_ident: UUID or logical name of a bay.
|
|
||||||
:param rollback: whether to rollback bay on update failure.
|
|
||||||
:param patch: a json PATCH document to apply to this bay.
|
|
||||||
"""
|
|
||||||
bay, node_count = self._patch(bay_ident, patch)
|
|
||||||
pecan.request.rpcapi.cluster_update_async(bay, node_count,
|
|
||||||
rollback=rollback)
|
|
||||||
return BayID(bay.uuid)
|
|
||||||
|
|
||||||
def _patch(self, bay_ident, patch):
|
|
||||||
context = pecan.request.context
|
|
||||||
bay = api_utils.get_resource('Cluster', bay_ident)
|
|
||||||
policy.enforce(context, 'bay:update', bay.as_dict(),
|
|
||||||
action='bay:update')
|
|
||||||
|
|
||||||
bay_to_cluster_attrs = {
|
|
||||||
'baymodel_id': 'cluster_template_id',
|
|
||||||
'bay_create_timeout': 'create_timeout'
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
bay_dict = bay.as_dict()
|
|
||||||
new_bay = Bay(**api_utils.apply_jsonpatch(bay_dict, patch))
|
|
||||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
|
||||||
raise exception.PatchError(patch=patch, reason=e)
|
|
||||||
|
|
||||||
# NOTE(ttsiouts): magnum.objects.Cluster.node_count will be a
|
|
||||||
# property so we won't be able to store it in the object. So
|
|
||||||
# instead of object_what_changed compare the new and the old
|
|
||||||
# clusters.
|
|
||||||
delta = set()
|
|
||||||
for field in new_bay.fields:
|
|
||||||
cluster_field = field
|
|
||||||
if cluster_field in bay_to_cluster_attrs:
|
|
||||||
cluster_field = bay_to_cluster_attrs[field]
|
|
||||||
if cluster_field not in bay_dict:
|
|
||||||
continue
|
|
||||||
if getattr(new_bay, field) != bay_dict[cluster_field]:
|
|
||||||
delta.add(cluster_field)
|
|
||||||
|
|
||||||
validate_cluster_properties(delta)
|
|
||||||
return bay, new_bay.node_count
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.1", "1.1")
|
|
||||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
|
||||||
def delete(self, bay_ident):
|
|
||||||
"""Delete a bay.
|
|
||||||
|
|
||||||
:param bay_ident: UUID of a bay or logical name of the bay.
|
|
||||||
"""
|
|
||||||
bay = self._delete(bay_ident)
|
|
||||||
|
|
||||||
pecan.request.rpcapi.cluster_delete(bay.uuid)
|
|
||||||
|
|
||||||
@base.Controller.api_version("1.2") # noqa
|
|
||||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
|
||||||
def delete(self, bay_ident): # noqa
|
|
||||||
"""Delete a bay.
|
|
||||||
|
|
||||||
:param bay_ident: UUID of a bay or logical name of the bay.
|
|
||||||
"""
|
|
||||||
bay = self._delete(bay_ident)
|
|
||||||
|
|
||||||
pecan.request.rpcapi.cluster_delete_async(bay.uuid)
|
|
||||||
|
|
||||||
def _delete(self, bay_ident):
|
|
||||||
context = pecan.request.context
|
|
||||||
bay = api_utils.get_resource('Cluster', bay_ident)
|
|
||||||
policy.enforce(context, 'bay:delete', bay.as_dict(),
|
|
||||||
action='bay:delete')
|
|
||||||
return bay
|
|
@ -1,422 +0,0 @@
|
|||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
import pecan
|
|
||||||
import wsme
|
|
||||||
from wsme import types as wtypes
|
|
||||||
|
|
||||||
from magnum.api import attr_validator
|
|
||||||
from magnum.api.controllers import base
|
|
||||||
from magnum.api.controllers import link
|
|
||||||
from magnum.api.controllers.v1 import collection
|
|
||||||
from magnum.api.controllers.v1 import types
|
|
||||||
from magnum.api import expose
|
|
||||||
from magnum.api import utils as api_utils
|
|
||||||
from magnum.api import validation
|
|
||||||
from magnum.common import clients
|
|
||||||
from magnum.common import exception
|
|
||||||
from magnum.common import name_generator
|
|
||||||
from magnum.common import policy
|
|
||||||
from magnum import objects
|
|
||||||
from magnum.objects import fields
|
|
||||||
|
|
||||||
|
|
||||||
class BayModel(base.APIBase):
|
|
||||||
"""API representation of a Baymodel.
|
|
||||||
|
|
||||||
This class enforces type checking and value constraints, and converts
|
|
||||||
between the internal object model and the API representation of a Baymodel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
uuid = types.uuid
|
|
||||||
"""Unique UUID for this Baymodel"""
|
|
||||||
|
|
||||||
name = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The name of the Baymodel"""
|
|
||||||
|
|
||||||
coe = wtypes.Enum(wtypes.text, *fields.ClusterType.ALL, mandatory=True)
|
|
||||||
"""The Container Orchestration Engine for this bay model"""
|
|
||||||
|
|
||||||
image_id = wsme.wsattr(wtypes.StringType(min_length=1, max_length=255),
|
|
||||||
mandatory=True)
|
|
||||||
"""The image name or UUID to use as a base image for this Baymodel"""
|
|
||||||
|
|
||||||
flavor_id = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The flavor of this Baymodel"""
|
|
||||||
|
|
||||||
master_flavor_id = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The flavor of the master node for this Baymodel"""
|
|
||||||
|
|
||||||
dns_nameserver = wtypes.IPv4AddressType()
|
|
||||||
"""The DNS nameserver address"""
|
|
||||||
|
|
||||||
keypair_id = wsme.wsattr(wtypes.StringType(min_length=1, max_length=255),
|
|
||||||
mandatory=True)
|
|
||||||
"""The name of the nova ssh keypair"""
|
|
||||||
|
|
||||||
external_network_id = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The external network to attach to the Bay"""
|
|
||||||
|
|
||||||
fixed_network = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The fixed network name to attach to the Bay"""
|
|
||||||
|
|
||||||
fixed_subnet = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The fixed subnet name to attach to the Bay"""
|
|
||||||
|
|
||||||
network_driver = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The name of the driver used for instantiating container networks"""
|
|
||||||
|
|
||||||
apiserver_port = wtypes.IntegerType(minimum=1024, maximum=65535)
|
|
||||||
"""The API server port for k8s"""
|
|
||||||
|
|
||||||
docker_volume_size = wtypes.IntegerType(minimum=1)
|
|
||||||
"""The size in GB of the docker volume"""
|
|
||||||
|
|
||||||
cluster_distro = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The Cluster distro for the bay, e.g. coreos, fedora-atomic, etc."""
|
|
||||||
|
|
||||||
links = wsme.wsattr([link.Link], readonly=True)
|
|
||||||
"""A list containing a self link and associated Baymodel links"""
|
|
||||||
|
|
||||||
http_proxy = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""Address of a proxy that will receive all HTTP requests and relay them.
|
|
||||||
The format is a URL including a port number.
|
|
||||||
"""
|
|
||||||
|
|
||||||
https_proxy = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""Address of a proxy that will receive all HTTPS requests and relay them.
|
|
||||||
The format is a URL including a port number.
|
|
||||||
"""
|
|
||||||
|
|
||||||
no_proxy = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""A comma separated list of IPs for which proxies should not be
|
|
||||||
used in the bay
|
|
||||||
"""
|
|
||||||
|
|
||||||
volume_driver = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""The name of the driver used for instantiating container volumes"""
|
|
||||||
|
|
||||||
registry_enabled = wsme.wsattr(types.boolean, default=False)
|
|
||||||
"""Indicates whether the docker registry is enabled"""
|
|
||||||
|
|
||||||
labels = wtypes.DictType(wtypes.text, wtypes.text)
|
|
||||||
"""One or more key/value pairs"""
|
|
||||||
|
|
||||||
tls_disabled = wsme.wsattr(types.boolean, default=False)
|
|
||||||
"""Indicates whether TLS should be disabled"""
|
|
||||||
|
|
||||||
public = wsme.wsattr(types.boolean, default=False)
|
|
||||||
"""Indicates whether the Baymodel is public or not."""
|
|
||||||
|
|
||||||
server_type = wsme.wsattr(wtypes.Enum(wtypes.text, *fields.ServerType.ALL),
|
|
||||||
default='vm')
|
|
||||||
"""Server type for this bay model"""
|
|
||||||
|
|
||||||
insecure_registry = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""Insecure registry URL when creating a Baymodel"""
|
|
||||||
|
|
||||||
docker_storage_driver = wtypes.StringType(min_length=1, max_length=255)
|
|
||||||
"""Docker storage driver"""
|
|
||||||
|
|
||||||
master_lb_enabled = wsme.wsattr(types.boolean, default=False)
|
|
||||||
"""Indicates whether created bays should have a load balancer for master
|
|
||||||
nodes or not.
|
|
||||||
"""
|
|
||||||
|
|
||||||
floating_ip_enabled = wsme.wsattr(types.boolean, default=True)
|
|
||||||
"""Indicates whether created bays should have a floating ip or not."""
|
|
||||||
|
|
||||||
hidden = wsme.wsattr(types.boolean, default=False)
|
|
||||||
"""Indicates whether the Baymodel is hidden or not."""
|
|
||||||
|
|
||||||
tags = wtypes.StringType(min_length=0, max_length=255)
|
|
||||||
"""A comma separated list of tags."""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.fields = []
|
|
||||||
for field in objects.ClusterTemplate.fields:
|
|
||||||
# Skip fields we do not expose.
|
|
||||||
if not hasattr(self, field):
|
|
||||||
continue
|
|
||||||
self.fields.append(field)
|
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _convert_with_links(baymodel, url):
|
|
||||||
baymodel.links = [link.Link.make_link('self', url,
|
|
||||||
'baymodels', baymodel.uuid),
|
|
||||||
link.Link.make_link('bookmark', url,
|
|
||||||
'baymodels', baymodel.uuid,
|
|
||||||
bookmark=True)]
|
|
||||||
return baymodel
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def convert_with_links(cls, rpc_baymodel):
|
|
||||||
baymodel = BayModel(**rpc_baymodel.as_dict())
|
|
||||||
return cls._convert_with_links(baymodel, pecan.request.host_url)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
sample = cls(
|
|
||||||
uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
|
||||||
name='example',
|
|
||||||
image_id='Fedora-k8s',
|
|
||||||
flavor_id='m1.small',
|
|
||||||
master_flavor_id='m1.small',
|
|
||||||
dns_nameserver='8.8.1.1',
|
|
||||||
keypair_id='keypair1',
|
|
||||||
external_network_id='ffc44e4a-2319-4062-bce0-9ae1c38b05ba',
|
|
||||||
fixed_network='private',
|
|
||||||
fixed_subnet='private-subnet',
|
|
||||||
network_driver='libnetwork',
|
|
||||||
volume_driver='cinder',
|
|
||||||
apiserver_port=8080,
|
|
||||||
docker_volume_size=25,
|
|
||||||
docker_storage_driver='devicemapper',
|
|
||||||
cluster_distro='fedora-atomic',
|
|
||||||
coe=fields.ClusterType.KUBERNETES,
|
|
||||||
http_proxy='http://proxy.com:123',
|
|
||||||
https_proxy='https://proxy.com:123',
|
|
||||||
no_proxy='192.168.0.1,192.168.0.2,192.168.0.3',
|
|
||||||
labels={'key1': 'val1', 'key2': 'val2'},
|
|
||||||
server_type='vm',
|
|
||||||
insecure_registry='10.238.100.100:5000',
|
|
||||||
created_at=timeutils.utcnow(),
|
|
||||||
updated_at=timeutils.utcnow(),
|
|
||||||
public=False,
|
|
||||||
master_lb_enabled=False,
|
|
||||||
floating_ip_enabled=True,
|
|
||||||
hidden=False,
|
|
||||||
)
|
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9511')
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelPatchType(types.JsonPatchType):
|
|
||||||
_api_base = BayModel
|
|
||||||
_extra_non_removable_attrs = {'/network_driver', '/external_network_id',
|
|
||||||
'/tls_disabled', '/public', '/server_type',
|
|
||||||
'/coe', '/registry_enabled',
|
|
||||||
'/cluster_distro', '/hidden'}
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelCollection(collection.Collection):
|
|
||||||
"""API representation of a collection of Baymodels."""
|
|
||||||
|
|
||||||
baymodels = [BayModel]
|
|
||||||
"""A list containing Baymodel objects"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self._type = 'baymodels'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def convert_with_links(rpc_baymodels, limit, url=None, **kwargs):
|
|
||||||
collection = BayModelCollection()
|
|
||||||
collection.baymodels = [BayModel.convert_with_links(p)
|
|
||||||
for p in rpc_baymodels]
|
|
||||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
||||||
return collection
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
sample = cls()
|
|
||||||
sample.baymodels = [BayModel.sample()]
|
|
||||||
return sample
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelsController(base.Controller):
|
|
||||||
"""REST controller for Baymodels."""
|
|
||||||
|
|
||||||
_custom_actions = {
|
|
||||||
'detail': ['GET'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _generate_name_for_baymodel(self, context):
|
|
||||||
"""Generate a random name like: zeta-22-model."""
|
|
||||||
|
|
||||||
name_gen = name_generator.NameGenerator()
|
|
||||||
name = name_gen.generate()
|
|
||||||
return name + '-model'
|
|
||||||
|
|
||||||
def _get_baymodels_collection(self, marker, limit,
|
|
||||||
sort_key, sort_dir, resource_url=None):
|
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
|
||||||
|
|
||||||
marker_obj = None
|
|
||||||
if marker:
|
|
||||||
marker_obj = objects.ClusterTemplate.get_by_uuid(
|
|
||||||
pecan.request.context, marker)
|
|
||||||
|
|
||||||
baymodels = objects.ClusterTemplate.list(pecan.request.context, limit,
|
|
||||||
marker_obj, sort_key=sort_key,
|
|
||||||
sort_dir=sort_dir)
|
|
||||||
|
|
||||||
return BayModelCollection.convert_with_links(baymodels, limit,
|
|
||||||
url=resource_url,
|
|
||||||
sort_key=sort_key,
|
|
||||||
sort_dir=sort_dir)
|
|
||||||
|
|
||||||
@expose.expose(BayModelCollection, types.uuid, int, wtypes.text,
|
|
||||||
wtypes.text)
|
|
||||||
def get_all(self, marker=None, limit=None, sort_key='id',
|
|
||||||
sort_dir='asc'):
|
|
||||||
"""Retrieve a list of Baymodels.
|
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
|
||||||
:param sort_key: column to sort results by. Default: id.
|
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'baymodel:get_all',
|
|
||||||
action='baymodel:get_all')
|
|
||||||
return self._get_baymodels_collection(marker, limit, sort_key,
|
|
||||||
sort_dir)
|
|
||||||
|
|
||||||
@expose.expose(BayModelCollection, types.uuid, int, wtypes.text,
|
|
||||||
wtypes.text)
|
|
||||||
def detail(self, marker=None, limit=None, sort_key='id',
|
|
||||||
sort_dir='asc'):
|
|
||||||
"""Retrieve a list of Baymodels with detail.
|
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
|
||||||
:param sort_key: column to sort results by. Default: id.
|
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'baymodel:detail',
|
|
||||||
action='baymodel:detail')
|
|
||||||
|
|
||||||
# NOTE(lucasagomes): /detail should only work against collections
|
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
|
||||||
if parent != "baymodels":
|
|
||||||
raise exception.HTTPNotFound
|
|
||||||
|
|
||||||
resource_url = '/'.join(['baymodels', 'detail'])
|
|
||||||
return self._get_baymodels_collection(marker, limit,
|
|
||||||
sort_key, sort_dir, resource_url)
|
|
||||||
|
|
||||||
@expose.expose(BayModel, types.uuid_or_name)
|
|
||||||
def get_one(self, baymodel_ident):
|
|
||||||
"""Retrieve information about the given Baymodel.
|
|
||||||
|
|
||||||
:param baymodel_ident: UUID or logical name of a baymodel.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
baymodel = api_utils.get_resource('ClusterTemplate', baymodel_ident)
|
|
||||||
if not baymodel.public:
|
|
||||||
policy.enforce(context, 'baymodel:get', baymodel.as_dict(),
|
|
||||||
action='baymodel:get')
|
|
||||||
|
|
||||||
return BayModel.convert_with_links(baymodel)
|
|
||||||
|
|
||||||
@expose.expose(BayModel, body=BayModel, status_code=201)
|
|
||||||
@validation.enforce_server_type()
|
|
||||||
@validation.enforce_network_driver_types_create()
|
|
||||||
@validation.enforce_volume_driver_types_create()
|
|
||||||
@validation.enforce_volume_storage_size_create()
|
|
||||||
def post(self, baymodel):
|
|
||||||
"""Create a new Baymodel.
|
|
||||||
|
|
||||||
:param baymodel: a Baymodel within the request body.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'baymodel:create',
|
|
||||||
action='baymodel:create')
|
|
||||||
baymodel_dict = baymodel.as_dict()
|
|
||||||
cli = clients.OpenStackClients(context)
|
|
||||||
attr_validator.validate_os_resources(context, baymodel_dict)
|
|
||||||
image_data = attr_validator.validate_image(cli,
|
|
||||||
baymodel_dict['image_id'])
|
|
||||||
baymodel_dict['cluster_distro'] = image_data['os_distro']
|
|
||||||
baymodel_dict['project_id'] = context.project_id
|
|
||||||
baymodel_dict['user_id'] = context.user_id
|
|
||||||
# check permissions for making baymodel public
|
|
||||||
if baymodel_dict['public']:
|
|
||||||
if not policy.enforce(context, "baymodel:publish", None,
|
|
||||||
do_raise=False):
|
|
||||||
raise exception.ClusterTemplatePublishDenied()
|
|
||||||
|
|
||||||
# NOTE(yuywz): We will generate a random human-readable name for
|
|
||||||
# baymodel if the name is not specified by user.
|
|
||||||
arg_name = baymodel_dict.get('name')
|
|
||||||
name = arg_name or self._generate_name_for_baymodel(context)
|
|
||||||
baymodel_dict['name'] = name
|
|
||||||
|
|
||||||
new_baymodel = objects.ClusterTemplate(context, **baymodel_dict)
|
|
||||||
new_baymodel.create()
|
|
||||||
# Set the HTTP Location Header
|
|
||||||
pecan.response.location = link.build_url('baymodels',
|
|
||||||
new_baymodel.uuid)
|
|
||||||
return BayModel.convert_with_links(new_baymodel)
|
|
||||||
|
|
||||||
@wsme.validate(types.uuid_or_name, [BayModelPatchType])
|
|
||||||
@expose.expose(BayModel, types.uuid_or_name, body=[BayModelPatchType])
|
|
||||||
@validation.enforce_network_driver_types_update()
|
|
||||||
@validation.enforce_volume_driver_types_update()
|
|
||||||
def patch(self, baymodel_ident, patch):
|
|
||||||
"""Update an existing Baymodel.
|
|
||||||
|
|
||||||
:param baymodel_ident: UUID or logic name of a Baymodel.
|
|
||||||
:param patch: a json PATCH document to apply to this Baymodel.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
baymodel = api_utils.get_resource('ClusterTemplate', baymodel_ident)
|
|
||||||
policy.enforce(context, 'baymodel:update', baymodel.as_dict(),
|
|
||||||
action='baymodel:update')
|
|
||||||
try:
|
|
||||||
baymodel_dict = baymodel.as_dict()
|
|
||||||
new_baymodel = BayModel(**api_utils.apply_jsonpatch(
|
|
||||||
baymodel_dict,
|
|
||||||
patch))
|
|
||||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
|
||||||
raise exception.PatchError(patch=patch, reason=e)
|
|
||||||
|
|
||||||
new_baymodel_dict = new_baymodel.as_dict()
|
|
||||||
attr_validator.validate_os_resources(context, new_baymodel_dict)
|
|
||||||
# check permissions when updating baymodel public flag
|
|
||||||
if baymodel.public != new_baymodel.public:
|
|
||||||
if not policy.enforce(context, "baymodel:publish", None,
|
|
||||||
do_raise=False):
|
|
||||||
raise exception.ClusterTemplatePublishDenied()
|
|
||||||
|
|
||||||
# Update only the fields that have changed
|
|
||||||
for field in objects.ClusterTemplate.fields:
|
|
||||||
try:
|
|
||||||
patch_val = getattr(new_baymodel, field)
|
|
||||||
except AttributeError:
|
|
||||||
# Ignore fields that aren't exposed in the API
|
|
||||||
continue
|
|
||||||
if patch_val == wtypes.Unset:
|
|
||||||
patch_val = None
|
|
||||||
if baymodel[field] != patch_val:
|
|
||||||
baymodel[field] = patch_val
|
|
||||||
|
|
||||||
baymodel.save()
|
|
||||||
return BayModel.convert_with_links(baymodel)
|
|
||||||
|
|
||||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
|
||||||
def delete(self, baymodel_ident):
|
|
||||||
"""Delete a Baymodel.
|
|
||||||
|
|
||||||
:param baymodel_ident: UUID or logical name of a Baymodel.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
baymodel = api_utils.get_resource('ClusterTemplate', baymodel_ident)
|
|
||||||
policy.enforce(context, 'baymodel:delete', baymodel.as_dict(),
|
|
||||||
action='baymodel:delete')
|
|
||||||
baymodel.destroy()
|
|
@ -71,10 +71,6 @@ class Certificate(base.APIBase):
|
|||||||
elif value == wtypes.Unset:
|
elif value == wtypes.Unset:
|
||||||
self._cluster_uuid = wtypes.Unset
|
self._cluster_uuid = wtypes.Unset
|
||||||
|
|
||||||
bay_uuid = wsme.wsproperty(wtypes.text, _get_cluster_uuid,
|
|
||||||
_set_cluster_uuid)
|
|
||||||
"""The bay UUID or id"""
|
|
||||||
|
|
||||||
cluster_uuid = wsme.wsproperty(wtypes.text, _get_cluster_uuid,
|
cluster_uuid = wsme.wsproperty(wtypes.text, _get_cluster_uuid,
|
||||||
_set_cluster_uuid)
|
_set_cluster_uuid)
|
||||||
"""The cluster UUID or id"""
|
"""The cluster UUID or id"""
|
||||||
@ -102,10 +98,6 @@ class Certificate(base.APIBase):
|
|||||||
self.fields.append(field)
|
self.fields.append(field)
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||||
|
|
||||||
# set the attribute for bay_uuid for backwards compatibility
|
|
||||||
self.fields.append('bay_uuid')
|
|
||||||
setattr(self, 'bay_uuid', kwargs.get('bay_uuid', self._cluster_uuid))
|
|
||||||
|
|
||||||
def get_cluster(self):
|
def get_cluster(self):
|
||||||
if not self._cluster:
|
if not self._cluster:
|
||||||
self._cluster = api_utils.get_resource('Cluster',
|
self._cluster = api_utils.get_resource('Cluster',
|
||||||
@ -115,7 +107,7 @@ class Certificate(base.APIBase):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(certificate, url, expand=True):
|
def _convert_with_links(certificate, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
certificate.unset_fields_except(['bay_uuid', 'cluster_uuid',
|
certificate.unset_fields_except(['cluster_uuid',
|
||||||
'csr', 'pem', 'ca_cert_type'])
|
'csr', 'pem', 'ca_cert_type'])
|
||||||
|
|
||||||
certificate.links = [link.Link.make_link('self', url,
|
certificate.links = [link.Link.make_link('self', url,
|
||||||
@ -135,8 +127,7 @@ class Certificate(base.APIBase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sample(cls, expand=True):
|
def sample(cls, expand=True):
|
||||||
sample = cls(bay_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae',
|
sample = cls(cluster_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae',
|
||||||
cluster_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae',
|
|
||||||
created_at=timeutils.utcnow(),
|
created_at=timeutils.utcnow(),
|
||||||
csr='AAA....AAA',
|
csr='AAA....AAA',
|
||||||
ca_cert_type='kubernetes')
|
ca_cert_type='kubernetes')
|
||||||
|
@ -33,10 +33,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
* 1.8 - Add upgrade API
|
* 1.8 - Add upgrade API
|
||||||
* 1.9 - Add nodegroup API
|
* 1.9 - Add nodegroup API
|
||||||
* 1.10 - Allow nodegroups with 0 nodes
|
* 1.10 - Allow nodegroups with 0 nodes
|
||||||
|
* 1.11 - Remove bay and baymodel objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_VER = '1.1'
|
BASE_VER = '1.1'
|
||||||
CURRENT_MAX_VER = '1.10'
|
CURRENT_MAX_VER = '1.11'
|
||||||
|
|
||||||
|
|
||||||
class Version(object):
|
class Version(object):
|
||||||
|
@ -104,3 +104,8 @@ user documentation.
|
|||||||
|
|
||||||
Allow the cluster to be created with node_count = 0 as well as to update
|
Allow the cluster to be created with node_count = 0 as well as to update
|
||||||
existing nodegroups to have 0 nodes.
|
existing nodegroups to have 0 nodes.
|
||||||
|
|
||||||
|
1.11
|
||||||
|
---
|
||||||
|
|
||||||
|
Drop bay and baymodels objects from magnum source code
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from magnum.common.policies import base
|
from magnum.common.policies import base
|
||||||
from magnum.common.policies import bay
|
|
||||||
from magnum.common.policies import baymodel
|
|
||||||
from magnum.common.policies import certificate
|
from magnum.common.policies import certificate
|
||||||
from magnum.common.policies import cluster
|
from magnum.common.policies import cluster
|
||||||
from magnum.common.policies import cluster_template
|
from magnum.common.policies import cluster_template
|
||||||
@ -30,8 +28,6 @@ from magnum.common.policies import stats
|
|||||||
def list_rules():
|
def list_rules():
|
||||||
return itertools.chain(
|
return itertools.chain(
|
||||||
base.list_rules(),
|
base.list_rules(),
|
||||||
bay.list_rules(),
|
|
||||||
baymodel.list_rules(),
|
|
||||||
certificate.list_rules(),
|
certificate.list_rules(),
|
||||||
cluster.list_rules(),
|
cluster.list_rules(),
|
||||||
cluster_template.list_rules(),
|
cluster_template.list_rules(),
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
from oslo_policy import policy
|
|
||||||
|
|
||||||
from magnum.common.policies import base
|
|
||||||
|
|
||||||
BAY = 'bay:%s'
|
|
||||||
|
|
||||||
rules = [
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAY % 'create',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Create a new bay.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/bays',
|
|
||||||
'method': 'POST'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAY % 'delete',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Delete a bay.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/bays/{bay_ident}',
|
|
||||||
'method': 'DELETE'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAY % 'detail',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Retrieve a list of bays with detail.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/bays',
|
|
||||||
'method': 'GET'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAY % 'get',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Retrieve information about the given bay.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/bays/{bay_ident}',
|
|
||||||
'method': 'GET'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAY % 'get_all',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Retrieve a list of bays.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/bays/',
|
|
||||||
'method': 'GET'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAY % 'update',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Update an existing bay.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/bays/{bay_ident}',
|
|
||||||
'method': 'PATCH'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def list_rules():
|
|
||||||
return rules
|
|
@ -1,106 +0,0 @@
|
|||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
from oslo_policy import policy
|
|
||||||
|
|
||||||
from magnum.common.policies import base
|
|
||||||
|
|
||||||
BAYMODEL = 'baymodel:%s'
|
|
||||||
|
|
||||||
rules = [
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'create',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Create a new baymodel.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels',
|
|
||||||
'method': 'POST'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'delete',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Delete a baymodel.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels/{baymodel_ident}',
|
|
||||||
'method': 'DELETE'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'detail',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Retrieve a list of baymodel with detail.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels',
|
|
||||||
'method': 'GET'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'get',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Retrieve information about the given baymodel.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels/{baymodel_ident}',
|
|
||||||
'method': 'GET'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'get_all',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Retrieve a list of baymodel.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels',
|
|
||||||
'method': 'GET'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'update',
|
|
||||||
check_str=base.RULE_DENY_CLUSTER_USER,
|
|
||||||
description='Update an existing baymodel.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels/{baymodel_ident}',
|
|
||||||
'method': 'PATCH'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
policy.DocumentedRuleDefault(
|
|
||||||
name=BAYMODEL % 'publish',
|
|
||||||
check_str=base.RULE_ADMIN_API,
|
|
||||||
description='Publish an existing baymodel.',
|
|
||||||
operations=[
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels',
|
|
||||||
'method': 'POST'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'path': '/v1/baymodels',
|
|
||||||
'method': 'PATCH'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def list_rules():
|
|
||||||
return rules
|
|
@ -34,10 +34,10 @@ rules = [
|
|||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=CERTIFICATE % 'get',
|
name=CERTIFICATE % 'get',
|
||||||
check_str=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
|
check_str=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
|
||||||
description='Retrieve CA information about the given bay/cluster.',
|
description='Retrieve CA information about the given cluster.',
|
||||||
operations=[
|
operations=[
|
||||||
{
|
{
|
||||||
'path': '/v1/certificates/{bay_uuid/cluster_uuid}',
|
'path': '/v1/certificates/{cluster_uuid}',
|
||||||
'method': 'GET'
|
'method': 'GET'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -45,10 +45,10 @@ rules = [
|
|||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=CERTIFICATE % 'rotate_ca',
|
name=CERTIFICATE % 'rotate_ca',
|
||||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||||
description='Rotate the CA certificate on the given bay/cluster.',
|
description='Rotate the CA certificate on the given cluster.',
|
||||||
operations=[
|
operations=[
|
||||||
{
|
{
|
||||||
'path': '/v1/certificates/{bay_uuid/cluster_uuid}',
|
'path': '/v1/certificates/{cluster_uuid}',
|
||||||
'method': 'PATCH'
|
'method': 'PATCH'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -25,19 +25,18 @@ cluster_heat_opts = [
|
|||||||
'during cluster creation if timeout is set as the poll '
|
'during cluster creation if timeout is set as the poll '
|
||||||
'will continue until cluster creation either ends '
|
'will continue until cluster creation either ends '
|
||||||
'or times out.'),
|
'or times out.'),
|
||||||
deprecated_group='bay_heat'),
|
),
|
||||||
cfg.IntOpt('wait_interval',
|
cfg.IntOpt('wait_interval',
|
||||||
default=1,
|
default=1,
|
||||||
help=('Sleep time interval between two attempts of querying '
|
help=('Sleep time interval between two attempts of querying '
|
||||||
'the Heat stack. This interval is in seconds.'),
|
'the Heat stack. This interval is in seconds.'),
|
||||||
deprecated_group='bay_heat'),
|
),
|
||||||
cfg.IntOpt('create_timeout',
|
cfg.IntOpt('create_timeout',
|
||||||
default=60,
|
default=60,
|
||||||
help=('The length of time to let cluster creation continue. '
|
help=('The length of time to let cluster creation continue. '
|
||||||
'This interval is in minutes. The default is 60 minutes.'
|
'This interval is in minutes. The default is 60 minutes.'
|
||||||
),
|
),
|
||||||
deprecated_group='bay_heat',
|
)
|
||||||
deprecated_name='bay_create_timeout')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ cluster_template_opts = [
|
|||||||
"cluster-templates. Use 'all' keyword to allow all "
|
"cluster-templates. Use 'all' keyword to allow all "
|
||||||
"drivers supported for kubernetes cluster-templates. "
|
"drivers supported for kubernetes cluster-templates. "
|
||||||
"Supported network drivers include flannel."),
|
"Supported network drivers include flannel."),
|
||||||
deprecated_group='baymodel'),
|
),
|
||||||
cfg.StrOpt('kubernetes_default_network_driver',
|
cfg.StrOpt('kubernetes_default_network_driver',
|
||||||
default='flannel',
|
default='flannel',
|
||||||
help=_("Default network driver for kubernetes "
|
help=_("Default network driver for kubernetes "
|
||||||
"cluster-templates."),
|
"cluster-templates."),
|
||||||
deprecated_group='baymodel'),
|
),
|
||||||
cfg.ListOpt('swarm_allowed_network_drivers',
|
cfg.ListOpt('swarm_allowed_network_drivers',
|
||||||
default=['all'],
|
default=['all'],
|
||||||
help=_("Allowed network drivers for docker swarm "
|
help=_("Allowed network drivers for docker swarm "
|
||||||
@ -37,7 +37,7 @@ cluster_template_opts = [
|
|||||||
"drivers supported for swarm cluster-templates. "
|
"drivers supported for swarm cluster-templates. "
|
||||||
"Supported network drivers include docker and flannel."
|
"Supported network drivers include docker and flannel."
|
||||||
),
|
),
|
||||||
deprecated_group='baymodel'),
|
),
|
||||||
cfg.StrOpt('swarm_default_network_driver',
|
cfg.StrOpt('swarm_default_network_driver',
|
||||||
default='docker',
|
default='docker',
|
||||||
help=_("Default network driver for docker swarm "
|
help=_("Default network driver for docker swarm "
|
||||||
|
@ -107,7 +107,7 @@ class Cluster(Base):
|
|||||||
|
|
||||||
__tablename__ = 'cluster'
|
__tablename__ = 'cluster'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
schema.UniqueConstraint('uuid', name='uniq_bay0uuid'),
|
schema.UniqueConstraint('uuid'),
|
||||||
table_args()
|
table_args()
|
||||||
)
|
)
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
@ -158,7 +158,7 @@ class ClusterTemplate(Base):
|
|||||||
|
|
||||||
__tablename__ = 'cluster_template'
|
__tablename__ = 'cluster_template'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
schema.UniqueConstraint('uuid', name='uniq_baymodel0uuid'),
|
schema.UniqueConstraint('uuid'),
|
||||||
table_args()
|
table_args()
|
||||||
)
|
)
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
@ -1,170 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from tempest.lib import exceptions
|
|
||||||
|
|
||||||
from magnum.tests.functional.api.v1.models import bay_model
|
|
||||||
from magnum.tests.functional.common import client
|
|
||||||
from magnum.tests.functional.common import utils
|
|
||||||
|
|
||||||
|
|
||||||
class BayClient(client.MagnumClient):
|
|
||||||
"""Encapsulates REST calls and maps JSON to/from models"""
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def bays_uri(cls, filters=None):
|
|
||||||
"""Construct bays uri with optional filters
|
|
||||||
|
|
||||||
:param filters: Optional k:v dict that's converted to url query
|
|
||||||
:returns: url string
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = "/bays"
|
|
||||||
if filters:
|
|
||||||
url = cls.add_filters(url, filters)
|
|
||||||
return url
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def bay_uri(cls, bay_id):
|
|
||||||
"""Construct bay uri
|
|
||||||
|
|
||||||
:param bay_id: bay uuid or name
|
|
||||||
:returns: url string
|
|
||||||
"""
|
|
||||||
|
|
||||||
return "{0}/{1}".format(cls.bays_uri(), bay_id)
|
|
||||||
|
|
||||||
def list_bays(self, filters=None, **kwargs):
|
|
||||||
"""Makes GET /bays request and returns BayCollection
|
|
||||||
|
|
||||||
Abstracts REST call to return all bays
|
|
||||||
|
|
||||||
:param filters: Optional k:v dict that's converted to url query
|
|
||||||
:returns: response object and BayCollection object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.get(self.bays_uri(filters), **kwargs)
|
|
||||||
return self.deserialize(resp, body, bay_model.BayCollection)
|
|
||||||
|
|
||||||
def get_bay(self, bay_id, **kwargs):
|
|
||||||
"""Makes GET /bay request and returns BayEntity
|
|
||||||
|
|
||||||
Abstracts REST call to return a single bay based on uuid or name
|
|
||||||
|
|
||||||
:param bay_id: bay uuid or name
|
|
||||||
:returns: response object and BayCollection object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.get(self.bay_uri(bay_id))
|
|
||||||
return self.deserialize(resp, body, bay_model.BayEntity)
|
|
||||||
|
|
||||||
def post_bay(self, model, **kwargs):
|
|
||||||
"""Makes POST /bay request and returns BayEntity
|
|
||||||
|
|
||||||
Abstracts REST call to create new bay
|
|
||||||
|
|
||||||
:param model: BayEntity
|
|
||||||
:returns: response object and BayEntity object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.post(
|
|
||||||
self.bays_uri(),
|
|
||||||
body=model.to_json(), **kwargs)
|
|
||||||
return self.deserialize(resp, body, bay_model.BayEntity)
|
|
||||||
|
|
||||||
def patch_bay(self, bay_id, baypatch_listmodel, **kwargs):
|
|
||||||
"""Makes PATCH /bay request and returns BayEntity
|
|
||||||
|
|
||||||
Abstracts REST call to update bay attributes
|
|
||||||
|
|
||||||
:param bay_id: UUID of bay
|
|
||||||
:param baypatch_listmodel: BayPatchCollection
|
|
||||||
:returns: response object and BayEntity object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.patch(
|
|
||||||
self.bay_uri(bay_id),
|
|
||||||
body=baypatch_listmodel.to_json(), **kwargs)
|
|
||||||
return self.deserialize(resp, body, bay_model.BayEntity)
|
|
||||||
|
|
||||||
def delete_bay(self, bay_id, **kwargs):
|
|
||||||
"""Makes DELETE /bay request and returns response object
|
|
||||||
|
|
||||||
Abstracts REST call to delete bay based on uuid or name
|
|
||||||
|
|
||||||
:param bay_id: UUID or name of bay
|
|
||||||
:returns: response object
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.delete(self.bay_uri(bay_id), **kwargs)
|
|
||||||
|
|
||||||
def wait_for_bay_to_delete(self, bay_id):
|
|
||||||
utils.wait_for_condition(
|
|
||||||
lambda: self.does_bay_not_exist(bay_id), 10, 600)
|
|
||||||
|
|
||||||
def wait_for_created_bay(self, bay_id, delete_on_error=True):
|
|
||||||
try:
|
|
||||||
utils.wait_for_condition(
|
|
||||||
lambda: self.does_bay_exist(bay_id), 10, 1800)
|
|
||||||
except Exception:
|
|
||||||
# In error state. Clean up the bay id if desired
|
|
||||||
self.LOG.error('Bay %s entered an exception state.', bay_id)
|
|
||||||
if delete_on_error:
|
|
||||||
self.LOG.error('We will attempt to delete bays now.')
|
|
||||||
self.delete_bay(bay_id)
|
|
||||||
self.wait_for_bay_to_delete(bay_id)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def wait_for_final_state(self, bay_id):
|
|
||||||
utils.wait_for_condition(
|
|
||||||
lambda: self.is_bay_in_final_state(bay_id), 10, 1800)
|
|
||||||
|
|
||||||
def is_bay_in_final_state(self, bay_id):
|
|
||||||
try:
|
|
||||||
resp, model = self.get_bay(bay_id)
|
|
||||||
if model.status in ['CREATED', 'CREATE_COMPLETE',
|
|
||||||
'ERROR', 'CREATE_FAILED']:
|
|
||||||
self.LOG.info('Bay %s succeeded.', bay_id)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except exceptions.NotFound:
|
|
||||||
self.LOG.warning('Bay %s is not found.', bay_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def does_bay_exist(self, bay_id):
|
|
||||||
try:
|
|
||||||
resp, model = self.get_bay(bay_id)
|
|
||||||
if model.status in ['CREATED', 'CREATE_COMPLETE']:
|
|
||||||
self.LOG.info('Bay %s is created.', bay_id)
|
|
||||||
return True
|
|
||||||
elif model.status in ['ERROR', 'CREATE_FAILED']:
|
|
||||||
self.LOG.error('Bay %s is in fail state.', bay_id)
|
|
||||||
raise exceptions.ServerFault(
|
|
||||||
"Got into an error condition: %s for %s",
|
|
||||||
(model.status, bay_id))
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except exceptions.NotFound:
|
|
||||||
self.LOG.warning('Bay %s is not found.', bay_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def does_bay_not_exist(self, bay_id):
|
|
||||||
try:
|
|
||||||
self.get_bay(bay_id)
|
|
||||||
except exceptions.NotFound:
|
|
||||||
self.LOG.warning('Bay %s is not found.', bay_id)
|
|
||||||
return True
|
|
||||||
return False
|
|
@ -1,105 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from magnum.tests.functional.api.v1.models import baymodel_model
|
|
||||||
from magnum.tests.functional.common import client
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelClient(client.MagnumClient):
|
|
||||||
"""Encapsulates REST calls and maps JSON to/from models"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def baymodels_uri(cls, filters=None):
|
|
||||||
"""Construct baymodels uri with optional filters
|
|
||||||
|
|
||||||
:param filters: Optional k:v dict that's converted to url query
|
|
||||||
:returns: url string
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = "/baymodels"
|
|
||||||
if filters:
|
|
||||||
url = cls.add_filters(url, filters)
|
|
||||||
return url
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def baymodel_uri(cls, baymodel_id):
|
|
||||||
"""Construct baymodel uri
|
|
||||||
|
|
||||||
:param baymodel_id: baymodel uuid or name
|
|
||||||
:returns: url string
|
|
||||||
"""
|
|
||||||
|
|
||||||
return "{0}/{1}".format(cls.baymodels_uri(), baymodel_id)
|
|
||||||
|
|
||||||
def list_baymodels(self, filters=None, **kwargs):
|
|
||||||
"""Makes GET /baymodels request and returns BayModelCollection
|
|
||||||
|
|
||||||
Abstracts REST call to return all baymodels
|
|
||||||
|
|
||||||
:param filters: Optional k:v dict that's converted to url query
|
|
||||||
:returns: response object and BayModelCollection object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.get(self.baymodels_uri(filters), **kwargs)
|
|
||||||
return self.deserialize(resp, body, baymodel_model.BayModelCollection)
|
|
||||||
|
|
||||||
def get_baymodel(self, baymodel_id, **kwargs):
|
|
||||||
"""Makes GET /baymodel request and returns BayModelEntity
|
|
||||||
|
|
||||||
Abstracts REST call to return a single baymodel based on uuid or name
|
|
||||||
|
|
||||||
:param baymodel_id: baymodel uuid or name
|
|
||||||
:returns: response object and BayModelCollection object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.get(self.baymodel_uri(baymodel_id))
|
|
||||||
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
|
|
||||||
|
|
||||||
def post_baymodel(self, model, **kwargs):
|
|
||||||
"""Makes POST /baymodel request and returns BayModelEntity
|
|
||||||
|
|
||||||
Abstracts REST call to create new baymodel
|
|
||||||
|
|
||||||
:param model: BayModelEntity
|
|
||||||
:returns: response object and BayModelEntity object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.post(
|
|
||||||
self.baymodels_uri(),
|
|
||||||
body=model.to_json(), **kwargs)
|
|
||||||
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
|
|
||||||
|
|
||||||
def patch_baymodel(self, baymodel_id, baymodelpatch_listmodel, **kwargs):
|
|
||||||
"""Makes PATCH /baymodel request and returns BayModelEntity
|
|
||||||
|
|
||||||
Abstracts REST call to update baymodel attributes
|
|
||||||
|
|
||||||
:param baymodel_id: UUID of baymodel
|
|
||||||
:param baymodelpatch_listmodel: BayModelPatchCollection
|
|
||||||
:returns: response object and BayModelEntity object
|
|
||||||
"""
|
|
||||||
|
|
||||||
resp, body = self.patch(
|
|
||||||
self.baymodel_uri(baymodel_id),
|
|
||||||
body=baymodelpatch_listmodel.to_json(), **kwargs)
|
|
||||||
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
|
|
||||||
|
|
||||||
def delete_baymodel(self, baymodel_id, **kwargs):
|
|
||||||
"""Makes DELETE /baymodel request and returns response object
|
|
||||||
|
|
||||||
Abstracts REST call to delete baymodel based on uuid or name
|
|
||||||
|
|
||||||
:param baymodel_id: UUID or name of baymodel
|
|
||||||
:returns: response object
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.delete(self.baymodel_uri(baymodel_id), **kwargs)
|
|
@ -1,30 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from magnum.tests.functional.common import models
|
|
||||||
|
|
||||||
|
|
||||||
class BayData(models.BaseModel):
|
|
||||||
"""Data that encapsulates bay attributes"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BayEntity(models.EntityModel):
|
|
||||||
"""Entity Model that represents a single instance of BayData"""
|
|
||||||
ENTITY_NAME = 'bay'
|
|
||||||
MODEL_TYPE = BayData
|
|
||||||
|
|
||||||
|
|
||||||
class BayCollection(models.CollectionModel):
|
|
||||||
"""Collection Model that represents a list of BayData objects"""
|
|
||||||
COLLECTION_NAME = 'baylists'
|
|
||||||
MODEL_TYPE = BayData
|
|
@ -1,30 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from magnum.tests.functional.common import models
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelData(models.BaseModel):
|
|
||||||
"""Data that encapsulates baymodel attributes"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelEntity(models.EntityModel):
|
|
||||||
"""Entity Model that represents a single instance of BayModelData"""
|
|
||||||
ENTITY_NAME = 'baymodel'
|
|
||||||
MODEL_TYPE = BayModelData
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelCollection(models.CollectionModel):
|
|
||||||
"""Collection Model that represents a list of BayModelData objects"""
|
|
||||||
COLLECTION_NAME = 'baymodellists'
|
|
||||||
MODEL_TYPE = BayModelData
|
|
@ -1,76 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from magnum.tests.functional.common import models
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelPatchData(models.BaseModel):
|
|
||||||
"""Data that encapsulates baymodelpatch attributes"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelPatchEntity(models.EntityModel):
|
|
||||||
"""Entity Model that represents a single instance of BayModelPatchData"""
|
|
||||||
ENTITY_NAME = 'baymodelpatch'
|
|
||||||
MODEL_TYPE = BayModelPatchData
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelPatchCollection(models.CollectionModel):
|
|
||||||
"""Collection Model that represents a list of BayModelPatchData objects"""
|
|
||||||
MODEL_TYPE = BayModelPatchData
|
|
||||||
COLLECTION_NAME = 'baymodelpatchlist'
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
"""Converts BayModelPatchCollection to json
|
|
||||||
|
|
||||||
Retrieves list from COLLECTION_NAME attribute and converts each object
|
|
||||||
to dict, appending it to a list. Then converts the entire list to json
|
|
||||||
|
|
||||||
This is required due to COLLECTION_NAME holding a list of objects that
|
|
||||||
needed to be converted to dict individually
|
|
||||||
|
|
||||||
:returns: json object
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = getattr(self, BayModelPatchCollection.COLLECTION_NAME)
|
|
||||||
collection = []
|
|
||||||
for d in data:
|
|
||||||
collection.append(d.to_dict())
|
|
||||||
return jsonutils.dumps(collection)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, data):
|
|
||||||
"""Converts dict to BayModelPatchData
|
|
||||||
|
|
||||||
Converts data dict to list of BayModelPatchData objects and stores it
|
|
||||||
in COLLECTION_NAME
|
|
||||||
|
|
||||||
Example of dict data:
|
|
||||||
|
|
||||||
[{
|
|
||||||
"path": "/name",
|
|
||||||
"value": "myname",
|
|
||||||
"op": "replace"
|
|
||||||
}]
|
|
||||||
|
|
||||||
:param data: dict of patch data
|
|
||||||
:returns: json object
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = cls()
|
|
||||||
collection = []
|
|
||||||
for d in data:
|
|
||||||
collection.append(cls.MODEL_TYPE.from_dict(d))
|
|
||||||
setattr(model, cls.COLLECTION_NAME, collection)
|
|
||||||
return model
|
|
@ -1,76 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from magnum.tests.functional.common import models
|
|
||||||
|
|
||||||
|
|
||||||
class BayPatchData(models.BaseModel):
|
|
||||||
"""Data that encapsulates baypatch attributes"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BayPatchEntity(models.EntityModel):
|
|
||||||
"""Entity Model that represents a single instance of BayPatchData"""
|
|
||||||
ENTITY_NAME = 'baypatch'
|
|
||||||
MODEL_TYPE = BayPatchData
|
|
||||||
|
|
||||||
|
|
||||||
class BayPatchCollection(models.CollectionModel):
|
|
||||||
"""Collection Model that represents a list of BayPatchData objects"""
|
|
||||||
MODEL_TYPE = BayPatchData
|
|
||||||
COLLECTION_NAME = 'baypatchlist'
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
"""Converts BayPatchCollection to json
|
|
||||||
|
|
||||||
Retrieves list from COLLECTION_NAME attribute and converts each object
|
|
||||||
to dict, appending it to a list. Then converts the entire list to json
|
|
||||||
|
|
||||||
This is required due to COLLECTION_NAME holding a list of objects that
|
|
||||||
needed to be converted to dict individually
|
|
||||||
|
|
||||||
:returns: json object
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = getattr(self, BayPatchCollection.COLLECTION_NAME)
|
|
||||||
collection = []
|
|
||||||
for d in data:
|
|
||||||
collection.append(d.to_dict())
|
|
||||||
return jsonutils.dumps(collection)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, data):
|
|
||||||
"""Converts dict to BayPatchData
|
|
||||||
|
|
||||||
Converts data dict to list of BayPatchData objects and stores it
|
|
||||||
in COLLECTION_NAME
|
|
||||||
|
|
||||||
Example of dict data:
|
|
||||||
|
|
||||||
[{
|
|
||||||
"path": "/name",
|
|
||||||
"value": "myname",
|
|
||||||
"op": "replace"
|
|
||||||
}]
|
|
||||||
|
|
||||||
:param data: dict of patch data
|
|
||||||
:returns: json object
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = cls()
|
|
||||||
collection = []
|
|
||||||
for d in data:
|
|
||||||
collection.append(cls.MODEL_TYPE.from_dict(d))
|
|
||||||
setattr(model, cls.COLLECTION_NAME, collection)
|
|
||||||
return model
|
|
@ -17,10 +17,6 @@ import struct
|
|||||||
|
|
||||||
from tempest.lib.common.utils import data_utils
|
from tempest.lib.common.utils import data_utils
|
||||||
|
|
||||||
from magnum.tests.functional.api.v1.models import bay_model
|
|
||||||
from magnum.tests.functional.api.v1.models import baymodel_model
|
|
||||||
from magnum.tests.functional.api.v1.models import baymodelpatch_model
|
|
||||||
from magnum.tests.functional.api.v1.models import baypatch_model
|
|
||||||
from magnum.tests.functional.api.v1.models import cert_model
|
from magnum.tests.functional.api.v1.models import cert_model
|
||||||
from magnum.tests.functional.api.v1.models import cluster_model
|
from magnum.tests.functional.api.v1.models import cluster_model
|
||||||
from magnum.tests.functional.api.v1.models import cluster_template_model
|
from magnum.tests.functional.api.v1.models import cluster_template_model
|
||||||
@ -89,236 +85,6 @@ def gen_no_proxy():
|
|||||||
return ",".join(gen_random_ip() for x in range(3))
|
return ",".join(gen_random_ip() for x in range(3))
|
||||||
|
|
||||||
|
|
||||||
def baymodel_data(**kwargs):
|
|
||||||
"""Generates random baymodel data
|
|
||||||
|
|
||||||
Keypair and image id cannot be random for the baymodel to be valid due to
|
|
||||||
validations for the presence of keypair and image id prior to baymodel
|
|
||||||
creation.
|
|
||||||
|
|
||||||
:param keypair_id: keypair name
|
|
||||||
:param image_id: image id or name
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"name": data_utils.rand_name('bay'),
|
|
||||||
"coe": "swarm-mode",
|
|
||||||
"tls_disabled": False,
|
|
||||||
"network_driver": None,
|
|
||||||
"volume_driver": None,
|
|
||||||
"labels": {},
|
|
||||||
"public": False,
|
|
||||||
"dns_nameserver": "8.8.8.8",
|
|
||||||
"flavor_id": data_utils.rand_name('bay'),
|
|
||||||
"master_flavor_id": data_utils.rand_name('bay'),
|
|
||||||
"external_network_id": config.Config.nic_id,
|
|
||||||
"keypair_id": data_utils.rand_name('bay'),
|
|
||||||
"image_id": data_utils.rand_name('bay')
|
|
||||||
}
|
|
||||||
|
|
||||||
data.update(kwargs)
|
|
||||||
model = baymodel_model.BayModelEntity.from_dict(data)
|
|
||||||
|
|
||||||
return model
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_replace_patch_data(path, value=data_utils.rand_name('bay')):
|
|
||||||
"""Generates random baymodel patch data
|
|
||||||
|
|
||||||
:param path: path to replace
|
|
||||||
:param value: value to replace in patch
|
|
||||||
:returns: BayModelPatchCollection with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = [{
|
|
||||||
"path": path,
|
|
||||||
"value": value,
|
|
||||||
"op": "replace"
|
|
||||||
}]
|
|
||||||
return baymodelpatch_model.BayModelPatchCollection.from_dict(data)
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_remove_patch_data(path):
|
|
||||||
"""Generates baymodel patch data by removing value
|
|
||||||
|
|
||||||
:param path: path to remove
|
|
||||||
:returns: BayModelPatchCollection with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = [{
|
|
||||||
"path": path,
|
|
||||||
"op": "remove"
|
|
||||||
}]
|
|
||||||
return baymodelpatch_model.BayModelPatchCollection.from_dict(data)
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_data_with_valid_keypair_image_flavor():
|
|
||||||
"""Generates random baymodel data with valid keypair,image and flavor
|
|
||||||
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return baymodel_data(keypair_id=config.Config.keypair_id,
|
|
||||||
image_id=config.Config.image_id,
|
|
||||||
flavor_id=config.Config.flavor_id,
|
|
||||||
master_flavor_id=config.Config.master_flavor_id)
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_data_with_missing_image():
|
|
||||||
"""Generates random baymodel data with missing image
|
|
||||||
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return baymodel_data(keypair_id=config.Config.keypair_id,
|
|
||||||
flavor_id=config.Config.flavor_id,
|
|
||||||
master_flavor_id=config.Config.master_flavor_id)
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_data_with_missing_flavor():
|
|
||||||
"""Generates random baymodel data with missing flavor
|
|
||||||
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return baymodel_data(keypair_id=config.Config.keypair_id,
|
|
||||||
image_id=config.Config.image_id)
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_data_with_missing_keypair():
|
|
||||||
"""Generates random baymodel data with missing keypair
|
|
||||||
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return baymodel_data(image_id=config.Config.image_id,
|
|
||||||
flavor_id=config.Config.flavor_id,
|
|
||||||
master_flavor_id=config.Config.master_flavor_id)
|
|
||||||
|
|
||||||
|
|
||||||
def baymodel_valid_data_with_specific_coe(coe):
|
|
||||||
"""Generates random baymodel data with valid keypair and image
|
|
||||||
|
|
||||||
:param coe: coe
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return baymodel_data(keypair_id=config.Config.keypair_id,
|
|
||||||
image_id=config.Config.image_id, coe=coe)
|
|
||||||
|
|
||||||
|
|
||||||
def valid_swarm_mode_baymodel(is_public=False):
|
|
||||||
"""Generates a valid swarm mode baymodel with valid data
|
|
||||||
|
|
||||||
:returns: BayModelEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return baymodel_data(image_id=config.Config.image_id,
|
|
||||||
flavor_id=config.Config.flavor_id, public=is_public,
|
|
||||||
dns_nameserver=config.Config.dns_nameserver,
|
|
||||||
master_flavor_id=config.Config.master_flavor_id,
|
|
||||||
keypair_id=config.Config.keypair_id, coe="swarm-mode",
|
|
||||||
cluster_distro=None,
|
|
||||||
external_network_id=config.Config.nic_id,
|
|
||||||
http_proxy=None, https_proxy=None, no_proxy=None,
|
|
||||||
network_driver=None, volume_driver=None, labels={},
|
|
||||||
tls_disabled=False)
|
|
||||||
|
|
||||||
|
|
||||||
def bay_data(name=data_utils.rand_name('bay'),
|
|
||||||
baymodel_id=data_utils.rand_uuid(),
|
|
||||||
node_count=random_int(1, 5), discovery_url=gen_random_ip(),
|
|
||||||
bay_create_timeout=random_int(1, 30),
|
|
||||||
master_count=random_int(1, 5)):
|
|
||||||
"""Generates random bay data
|
|
||||||
|
|
||||||
BayModel_id cannot be random for the bay to be valid due to
|
|
||||||
validations for the presence of baymodel prior to baymodel
|
|
||||||
creation.
|
|
||||||
|
|
||||||
:param name: bay name (must be unique)
|
|
||||||
:param baymodel_id: baymodel unique id (must already exist)
|
|
||||||
:param node_count: number of agents for bay
|
|
||||||
:param discovery_url: url provided for node discovery
|
|
||||||
:param bay_create_timeout: timeout in minutes for bay create
|
|
||||||
:param master_count: number of master nodes for the bay
|
|
||||||
:returns: BayEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"name": name,
|
|
||||||
"baymodel_id": baymodel_id,
|
|
||||||
"node_count": node_count,
|
|
||||||
"discovery_url": None,
|
|
||||||
"bay_create_timeout": bay_create_timeout,
|
|
||||||
"master_count": master_count
|
|
||||||
}
|
|
||||||
model = bay_model.BayEntity.from_dict(data)
|
|
||||||
|
|
||||||
return model
|
|
||||||
|
|
||||||
|
|
||||||
def valid_bay_data(baymodel_id, name=data_utils.rand_name('bay'), node_count=1,
|
|
||||||
master_count=1, bay_create_timeout=None):
|
|
||||||
"""Generates random bay data with valid
|
|
||||||
|
|
||||||
:param baymodel_id: baymodel unique id that already exists
|
|
||||||
:param name: bay name (must be unique)
|
|
||||||
:param node_count: number of agents for bay
|
|
||||||
:returns: BayEntity with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
return bay_data(baymodel_id=baymodel_id, name=name,
|
|
||||||
master_count=master_count, node_count=node_count,
|
|
||||||
bay_create_timeout=bay_create_timeout)
|
|
||||||
|
|
||||||
|
|
||||||
def bay_name_patch_data(name=data_utils.rand_name('bay')):
|
|
||||||
"""Generates random baymodel patch data
|
|
||||||
|
|
||||||
:param name: name to replace in patch
|
|
||||||
:returns: BayPatchCollection with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = [{
|
|
||||||
"path": "/name",
|
|
||||||
"value": name,
|
|
||||||
"op": "replace"
|
|
||||||
}]
|
|
||||||
return baypatch_model.BayPatchCollection.from_dict(data)
|
|
||||||
|
|
||||||
|
|
||||||
def bay_api_addy_patch_data(address='0.0.0.0'):
|
|
||||||
"""Generates random bay patch data
|
|
||||||
|
|
||||||
:param name: name to replace in patch
|
|
||||||
:returns: BayPatchCollection with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = [{
|
|
||||||
"path": "/api_address",
|
|
||||||
"value": address,
|
|
||||||
"op": "replace"
|
|
||||||
}]
|
|
||||||
return baypatch_model.BayPatchCollection.from_dict(data)
|
|
||||||
|
|
||||||
|
|
||||||
def bay_node_count_patch_data(node_count=2):
|
|
||||||
"""Generates random bay patch data
|
|
||||||
|
|
||||||
:param name: name to replace in patch
|
|
||||||
:returns: BayPatchCollection with generated data
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = [{
|
|
||||||
"path": "/node_count",
|
|
||||||
"value": node_count,
|
|
||||||
"op": "replace"
|
|
||||||
}]
|
|
||||||
return baypatch_model.BayPatchCollection.from_dict(data)
|
|
||||||
|
|
||||||
|
|
||||||
def cert_data(cluster_uuid, csr_data):
|
def cert_data(cluster_uuid, csr_data):
|
||||||
data = {
|
data = {
|
||||||
"cluster_uuid": cluster_uuid,
|
"cluster_uuid": cluster_uuid,
|
||||||
|
@ -13,8 +13,6 @@
|
|||||||
from tempest import clients
|
from tempest import clients
|
||||||
from tempest.common import credentials_factory as common_creds
|
from tempest.common import credentials_factory as common_creds
|
||||||
|
|
||||||
from magnum.tests.functional.api.v1.clients import bay_client
|
|
||||||
from magnum.tests.functional.api.v1.clients import baymodel_client
|
|
||||||
from magnum.tests.functional.api.v1.clients import cert_client
|
from magnum.tests.functional.api.v1.clients import cert_client
|
||||||
from magnum.tests.functional.api.v1.clients import cluster_client
|
from magnum.tests.functional.api.v1.clients import cluster_client
|
||||||
from magnum.tests.functional.api.v1.clients import cluster_template_client
|
from magnum.tests.functional.api.v1.clients import cluster_template_client
|
||||||
@ -32,11 +30,7 @@ class Manager(clients.Manager):
|
|||||||
self.auth_provider.orig_base_url = self.auth_provider.base_url
|
self.auth_provider.orig_base_url = self.auth_provider.base_url
|
||||||
self.auth_provider.base_url = self.bypassed_base_url
|
self.auth_provider.base_url = self.bypassed_base_url
|
||||||
auth = self.auth_provider
|
auth = self.auth_provider
|
||||||
if request_type == 'baymodel':
|
if request_type == 'cert':
|
||||||
self.client = baymodel_client.BayModelClient(auth)
|
|
||||||
elif request_type == 'bay':
|
|
||||||
self.client = bay_client.BayClient(auth)
|
|
||||||
elif request_type == 'cert':
|
|
||||||
self.client = cert_client.CertClient(auth)
|
self.client = cert_client.CertClient(auth)
|
||||||
elif request_type == 'cluster_template':
|
elif request_type == 'cluster_template':
|
||||||
self.client = cluster_template_client.ClusterTemplateClient(auth)
|
self.client = cluster_template_client.ClusterTemplateClient(auth)
|
||||||
|
@ -41,7 +41,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
[{u'href': u'http://localhost/v1/',
|
[{u'href': u'http://localhost/v1/',
|
||||||
u'rel': u'self'}],
|
u'rel': u'self'}],
|
||||||
u'status': u'CURRENT',
|
u'status': u'CURRENT',
|
||||||
u'max_version': u'1.10',
|
u'max_version': u'1.11',
|
||||||
u'min_version': u'1.1'}]}
|
u'min_version': u'1.1'}]}
|
||||||
|
|
||||||
self.v1_expected = {
|
self.v1_expected = {
|
||||||
@ -58,14 +58,6 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
u'rel': u'self'},
|
u'rel': u'self'},
|
||||||
{u'href': u'http://localhost/stats/',
|
{u'href': u'http://localhost/stats/',
|
||||||
u'rel': u'bookmark'}],
|
u'rel': u'bookmark'}],
|
||||||
u'bays': [{u'href': u'http://localhost/v1/bays/',
|
|
||||||
u'rel': u'self'},
|
|
||||||
{u'href': u'http://localhost/bays/',
|
|
||||||
u'rel': u'bookmark'}],
|
|
||||||
u'baymodels': [{u'href': u'http://localhost/v1/baymodels/',
|
|
||||||
u'rel': u'self'},
|
|
||||||
{u'href': u'http://localhost/baymodels/',
|
|
||||||
u'rel': u'bookmark'}],
|
|
||||||
u'clusters': [{u'href': u'http://localhost/v1/clusters/',
|
u'clusters': [{u'href': u'http://localhost/v1/clusters/',
|
||||||
u'rel': u'self'},
|
u'rel': u'self'},
|
||||||
{u'href': u'http://localhost/clusters/',
|
{u'href': u'http://localhost/clusters/',
|
||||||
|
@ -1,971 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
from wsme import types as wtypes
|
|
||||||
|
|
||||||
from magnum.api import attr_validator
|
|
||||||
from magnum.api.controllers.v1 import bay as api_bay
|
|
||||||
from magnum.common import exception
|
|
||||||
from magnum.conductor import api as rpcapi
|
|
||||||
from magnum import objects
|
|
||||||
from magnum.tests import base
|
|
||||||
from magnum.tests.unit.api import base as api_base
|
|
||||||
from magnum.tests.unit.api import utils as apiutils
|
|
||||||
from magnum.tests.unit.db import utils as db_utils
|
|
||||||
from magnum.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestBayObject(base.TestCase):
|
|
||||||
|
|
||||||
def test_bay_init(self):
|
|
||||||
bay_dict = apiutils.bay_post_data(baymodel_id=None)
|
|
||||||
del bay_dict['node_count']
|
|
||||||
del bay_dict['master_count']
|
|
||||||
del bay_dict['bay_create_timeout']
|
|
||||||
bay = api_bay.Bay(**bay_dict)
|
|
||||||
self.assertEqual(1, bay.node_count)
|
|
||||||
self.assertEqual(1, bay.master_count)
|
|
||||||
self.assertEqual(60, bay.bay_create_timeout)
|
|
||||||
|
|
||||||
# test unset value for baymodel_id
|
|
||||||
bay.baymodel_id = wtypes.Unset
|
|
||||||
self.assertEqual(wtypes.Unset, bay.baymodel_id)
|
|
||||||
|
|
||||||
# test backwards compatibility of bay fields with new objects
|
|
||||||
bay_dict['bay_create_timeout'] = 15
|
|
||||||
bay_dict['bay_faults'] = {'testfault': 'fault'}
|
|
||||||
bay = api_bay.Bay(**bay_dict)
|
|
||||||
self.assertEqual(15, bay.bay_create_timeout)
|
|
||||||
self.assertEqual(15, bay.create_timeout)
|
|
||||||
self.assertIn('testfault', bay.bay_faults)
|
|
||||||
self.assertIn('testfault', bay.faults)
|
|
||||||
|
|
||||||
def test_as_dict_faults(self):
|
|
||||||
bay_dict = apiutils.bay_post_data(baymodel_id=None)
|
|
||||||
del bay_dict['node_count']
|
|
||||||
del bay_dict['master_count']
|
|
||||||
del bay_dict['bay_create_timeout']
|
|
||||||
bay = api_bay.Bay(**bay_dict)
|
|
||||||
bay.bay_faults = {'testfault': 'fault'}
|
|
||||||
dict = bay.as_dict()
|
|
||||||
self.assertEqual({'testfault': 'fault'}, dict['faults'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestListBay(api_base.FunctionalTest):
|
|
||||||
|
|
||||||
_bay_attrs = ("name", "baymodel_id", "node_count", "status",
|
|
||||||
"master_count", "stack_id", "bay_create_timeout")
|
|
||||||
|
|
||||||
_expand_bay_attrs = ("name", "baymodel_id", "node_count", "status",
|
|
||||||
"api_address", "discovery_url", "node_addresses",
|
|
||||||
"master_count", "master_addresses", "stack_id",
|
|
||||||
"bay_create_timeout", "status_reason")
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListBay, self).setUp()
|
|
||||||
obj_utils.create_test_cluster_template(self.context)
|
|
||||||
|
|
||||||
def test_empty(self):
|
|
||||||
response = self.get_json('/bays')
|
|
||||||
self.assertEqual([], response['bays'])
|
|
||||||
|
|
||||||
def test_one(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
response = self.get_json('/bays')
|
|
||||||
self.assertEqual(bay.uuid, response['bays'][0]["uuid"])
|
|
||||||
self._verify_attrs(self._bay_attrs, response['bays'][0])
|
|
||||||
|
|
||||||
# Verify atts that should not appear from bay's get_all response
|
|
||||||
none_attrs = set(self._expand_bay_attrs) - set(self._bay_attrs)
|
|
||||||
self._verify_attrs(none_attrs, response['bays'][0], positive=False)
|
|
||||||
|
|
||||||
def test_get_one(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
response = self.get_json('/bays/%s' % bay['uuid'])
|
|
||||||
self.assertEqual(bay.uuid, response['uuid'])
|
|
||||||
self._verify_attrs(self._expand_bay_attrs, response)
|
|
||||||
|
|
||||||
@mock.patch('magnum.common.clients.OpenStackClients.heat')
|
|
||||||
def test_get_one_failed_bay(self, mock_heat):
|
|
||||||
fake_resources = mock.MagicMock()
|
|
||||||
fake_resources.resource_name = 'fake_name'
|
|
||||||
fake_resources.resource_status_reason = 'fake_reason'
|
|
||||||
|
|
||||||
ht = mock.MagicMock()
|
|
||||||
ht.resources.list.return_value = [fake_resources]
|
|
||||||
mock_heat.return_value = ht
|
|
||||||
|
|
||||||
bay = obj_utils.create_test_cluster(self.context,
|
|
||||||
status='CREATE_FAILED')
|
|
||||||
response = self.get_json('/bays/%s' % bay['uuid'])
|
|
||||||
self.assertEqual(bay.uuid, response['uuid'])
|
|
||||||
self.assertEqual({'fake_name': 'fake_reason'}, response['bay_faults'])
|
|
||||||
|
|
||||||
@mock.patch('magnum.common.clients.OpenStackClients.heat')
|
|
||||||
def test_get_one_failed_bay_heatclient_exception(self, mock_heat):
|
|
||||||
mock_heat.resources.list.side_effect = Exception('fake')
|
|
||||||
bay = obj_utils.create_test_cluster(self.context,
|
|
||||||
status='CREATE_FAILED')
|
|
||||||
response = self.get_json('/bays/%s' % bay['uuid'])
|
|
||||||
self.assertEqual(bay.uuid, response['uuid'])
|
|
||||||
self.assertEqual({}, response['bay_faults'])
|
|
||||||
|
|
||||||
def test_get_one_by_name(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
response = self.get_json('/bays/%s' % bay['name'])
|
|
||||||
self.assertEqual(bay.uuid, response['uuid'])
|
|
||||||
self._verify_attrs(self._expand_bay_attrs, response)
|
|
||||||
|
|
||||||
def test_get_one_by_name_not_found(self):
|
|
||||||
response = self.get_json(
|
|
||||||
'/bays/not_found',
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_get_one_by_name_multiple_bay(self):
|
|
||||||
obj_utils.create_test_cluster(self.context, name='test_bay',
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
obj_utils.create_test_cluster(self.context, name='test_bay',
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
response = self.get_json('/bays/test_bay', expect_errors=True)
|
|
||||||
self.assertEqual(409, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_get_all_with_pagination_marker(self):
|
|
||||||
bay_list = []
|
|
||||||
for id_ in range(4):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context, id=id_,
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
bay_list.append(bay)
|
|
||||||
|
|
||||||
response = self.get_json('/bays?limit=3&marker=%s'
|
|
||||||
% bay_list[2].uuid)
|
|
||||||
self.assertEqual(1, len(response['bays']))
|
|
||||||
self.assertEqual(bay_list[-1].uuid, response['bays'][0]['uuid'])
|
|
||||||
|
|
||||||
def test_detail(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
response = self.get_json('/bays/detail')
|
|
||||||
self.assertEqual(bay.uuid, response['bays'][0]["uuid"])
|
|
||||||
self._verify_attrs(self._expand_bay_attrs, response['bays'][0])
|
|
||||||
|
|
||||||
def test_detail_with_pagination_marker(self):
|
|
||||||
bay_list = []
|
|
||||||
for id_ in range(4):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context, id=id_,
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
bay_list.append(bay)
|
|
||||||
|
|
||||||
response = self.get_json('/bays/detail?limit=3&marker=%s'
|
|
||||||
% bay_list[2].uuid)
|
|
||||||
self.assertEqual(1, len(response['bays']))
|
|
||||||
self.assertEqual(bay_list[-1].uuid, response['bays'][0]['uuid'])
|
|
||||||
self._verify_attrs(self._expand_bay_attrs, response['bays'][0])
|
|
||||||
|
|
||||||
def test_detail_against_single(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
response = self.get_json('/bays/%s/detail' % bay['uuid'],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
|
|
||||||
def test_many(self):
|
|
||||||
bm_list = []
|
|
||||||
for id_ in range(5):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context, id=id_,
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
bm_list.append(bay.uuid)
|
|
||||||
response = self.get_json('/bays')
|
|
||||||
self.assertEqual(len(bm_list), len(response['bays']))
|
|
||||||
uuids = [b['uuid'] for b in response['bays']]
|
|
||||||
self.assertEqual(sorted(bm_list), sorted(uuids))
|
|
||||||
|
|
||||||
def test_links(self):
|
|
||||||
uuid = uuidutils.generate_uuid()
|
|
||||||
obj_utils.create_test_cluster(self.context, id=1, uuid=uuid)
|
|
||||||
response = self.get_json('/bays/%s' % uuid)
|
|
||||||
self.assertIn('links', response.keys())
|
|
||||||
self.assertEqual(2, len(response['links']))
|
|
||||||
self.assertIn(uuid, response['links'][0]['href'])
|
|
||||||
for link in response['links']:
|
|
||||||
bookmark = link['rel'] == 'bookmark'
|
|
||||||
self.assertTrue(self.validate_link(link['href'],
|
|
||||||
bookmark=bookmark))
|
|
||||||
|
|
||||||
def test_collection_links(self):
|
|
||||||
for id_ in range(5):
|
|
||||||
obj_utils.create_test_cluster(self.context, id=id_,
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
response = self.get_json('/bays/?limit=3')
|
|
||||||
self.assertEqual(3, len(response['bays']))
|
|
||||||
|
|
||||||
next_marker = response['bays'][-1]['uuid']
|
|
||||||
self.assertIn(next_marker, response['next'])
|
|
||||||
|
|
||||||
def test_collection_links_default_limit(self):
|
|
||||||
cfg.CONF.set_override('max_limit', 3, 'api')
|
|
||||||
for id_ in range(5):
|
|
||||||
obj_utils.create_test_cluster(self.context, id=id_,
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
response = self.get_json('/bays')
|
|
||||||
self.assertEqual(3, len(response['bays']))
|
|
||||||
|
|
||||||
next_marker = response['bays'][-1]['uuid']
|
|
||||||
self.assertIn(next_marker, response['next'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestPatch(api_base.FunctionalTest):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestPatch, self).setUp()
|
|
||||||
self.cluster_template = obj_utils.create_test_cluster_template(
|
|
||||||
self.context)
|
|
||||||
self.bay = obj_utils.create_test_cluster(self.context,
|
|
||||||
name='bay_example_A',
|
|
||||||
node_count=3)
|
|
||||||
p = mock.patch.object(rpcapi.API, 'cluster_update')
|
|
||||||
self.mock_bay_update = p.start()
|
|
||||||
self.mock_bay_update.side_effect = self._simulate_rpc_bay_update
|
|
||||||
self.addCleanup(p.stop)
|
|
||||||
|
|
||||||
def _simulate_rpc_bay_update(self, bay, node_count, rollback=False):
|
|
||||||
bay.status = 'UPDATE_IN_PROGRESS'
|
|
||||||
bay.save()
|
|
||||||
default_ng_worker = bay.default_ng_worker
|
|
||||||
default_ng_worker.node_count = node_count
|
|
||||||
default_ng_worker.save()
|
|
||||||
return bay
|
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
|
||||||
def test_replace_ok(self, mock_utcnow):
|
|
||||||
new_node_count = 4
|
|
||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
|
||||||
mock_utcnow.return_value = test_time
|
|
||||||
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/node_count',
|
|
||||||
'value': new_node_count,
|
|
||||||
'op': 'replace'}])
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(200, response.status_code)
|
|
||||||
|
|
||||||
response = self.get_json('/bays/%s' % self.bay.uuid)
|
|
||||||
self.assertEqual(new_node_count, response['node_count'])
|
|
||||||
return_updated_at = timeutils.parse_isotime(
|
|
||||||
response['updated_at']).replace(tzinfo=None)
|
|
||||||
self.assertEqual(test_time, return_updated_at)
|
|
||||||
# Assert nothing else was changed
|
|
||||||
self.assertEqual(self.bay.uuid, response['uuid'])
|
|
||||||
self.assertEqual(self.bay.cluster_template_id, response['baymodel_id'])
|
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
|
||||||
def test_replace_ok_by_name(self, mock_utcnow):
|
|
||||||
new_node_count = 4
|
|
||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
|
||||||
mock_utcnow.return_value = test_time
|
|
||||||
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.name,
|
|
||||||
[{'path': '/node_count',
|
|
||||||
'value': new_node_count,
|
|
||||||
'op': 'replace'}])
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(200, response.status_code)
|
|
||||||
|
|
||||||
response = self.get_json('/bays/%s' % self.bay.uuid)
|
|
||||||
self.assertEqual(new_node_count, response['node_count'])
|
|
||||||
return_updated_at = timeutils.parse_isotime(
|
|
||||||
response['updated_at']).replace(tzinfo=None)
|
|
||||||
self.assertEqual(test_time, return_updated_at)
|
|
||||||
# Assert nothing else was changed
|
|
||||||
self.assertEqual(self.bay.uuid, response['uuid'])
|
|
||||||
self.assertEqual(self.bay.cluster_template_id, response['baymodel_id'])
|
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
|
||||||
def test_replace_ok_by_name_not_found(self, mock_utcnow):
|
|
||||||
name = 'not_found'
|
|
||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
|
||||||
mock_utcnow.return_value = test_time
|
|
||||||
|
|
||||||
response = self.patch_json('/bays/%s' % name,
|
|
||||||
[{'path': '/name', 'value': name,
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(404, response.status_code)
|
|
||||||
|
|
||||||
def test_replace_baymodel_id_failed(self):
|
|
||||||
cluster_template = obj_utils.create_test_cluster_template(
|
|
||||||
self.context,
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/baymodel_id',
|
|
||||||
'value': cluster_template.uuid,
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_code)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
|
||||||
def test_replace_ok_by_name_multiple_bay(self, mock_utcnow):
|
|
||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
|
||||||
mock_utcnow.return_value = test_time
|
|
||||||
|
|
||||||
obj_utils.create_test_cluster(self.context, name='test_bay',
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
obj_utils.create_test_cluster(self.context, name='test_bay',
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
|
|
||||||
response = self.patch_json('/bays/test_bay',
|
|
||||||
[{'path': '/name', 'value': 'test_bay',
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(409, response.status_code)
|
|
||||||
|
|
||||||
def test_replace_non_existent_baymodel_id(self):
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/baymodel_id',
|
|
||||||
'value': uuidutils.generate_uuid(),
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_code)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_replace_invalid_node_count(self):
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/node_count', 'value': -1,
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_code)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_replace_non_existent_bay(self):
|
|
||||||
response = self.patch_json('/bays/%s' % uuidutils.generate_uuid(),
|
|
||||||
[{'path': '/name',
|
|
||||||
'value': 'bay_example_B',
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_replace_bay_name_failed(self):
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/name',
|
|
||||||
'value': 'bay_example_B',
|
|
||||||
'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_add_non_existent_property(self):
|
|
||||||
response = self.patch_json(
|
|
||||||
'/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/foo', 'value': 'bar', 'op': 'add'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
@mock.patch.object(rpcapi.API, 'cluster_update_async')
|
|
||||||
def test_update_bay_async(self, mock_update):
|
|
||||||
response = self.patch_json(
|
|
||||||
'/bays/%s' % self.bay.name,
|
|
||||||
[{'path': '/node_count', 'value': 4,
|
|
||||||
'op': 'replace'}],
|
|
||||||
headers={'OpenStack-API-Version': 'container-infra 1.2'})
|
|
||||||
|
|
||||||
self.assertEqual(202, response.status_code)
|
|
||||||
|
|
||||||
@mock.patch.object(rpcapi.API, 'cluster_update_async')
|
|
||||||
def test_update_bay_with_rollback_enabled(self, mock_update):
|
|
||||||
node_count = 4
|
|
||||||
response = self.patch_json(
|
|
||||||
'/bays/%s/?rollback=True' % self.bay.name,
|
|
||||||
[{'path': '/node_count', 'value': node_count,
|
|
||||||
'op': 'replace'}],
|
|
||||||
headers={'OpenStack-API-Version': 'container-infra 1.3'})
|
|
||||||
|
|
||||||
mock_update.assert_called_once_with(mock.ANY, node_count,
|
|
||||||
rollback=True)
|
|
||||||
self.assertEqual(202, response.status_code)
|
|
||||||
|
|
||||||
def test_remove_ok(self):
|
|
||||||
response = self.get_json('/bays/%s' % self.bay.uuid)
|
|
||||||
self.assertIsNotNone(response['name'])
|
|
||||||
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/node_count', 'op': 'remove'}])
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(200, response.status_code)
|
|
||||||
|
|
||||||
response = self.get_json('/bays/%s' % self.bay.uuid)
|
|
||||||
# only allow node_count for bay, and default value is 1
|
|
||||||
self.assertEqual(1, response['node_count'])
|
|
||||||
# Assert nothing else was changed
|
|
||||||
self.assertEqual(self.bay.uuid, response['uuid'])
|
|
||||||
self.assertEqual(self.bay.cluster_template_id, response['baymodel_id'])
|
|
||||||
self.assertEqual(self.bay.name, response['name'])
|
|
||||||
self.assertEqual(self.bay.master_count, response['master_count'])
|
|
||||||
|
|
||||||
def test_remove_mandatory_property_fail(self):
|
|
||||||
mandatory_properties = ('/uuid', '/baymodel_id')
|
|
||||||
for p in mandatory_properties:
|
|
||||||
response = self.patch_json('/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': p, 'op': 'remove'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_remove_non_existent_property(self):
|
|
||||||
response = self.patch_json(
|
|
||||||
'/bays/%s' % self.bay.uuid,
|
|
||||||
[{'path': '/non-existent', 'op': 'remove'}],
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_code)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestPost(api_base.FunctionalTest):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestPost, self).setUp()
|
|
||||||
self.cluster_template = obj_utils.create_test_cluster_template(
|
|
||||||
self.context)
|
|
||||||
p = mock.patch.object(rpcapi.API, 'cluster_create')
|
|
||||||
self.mock_bay_create = p.start()
|
|
||||||
self.mock_bay_create.side_effect = self._simulate_rpc_bay_create
|
|
||||||
self.addCleanup(p.stop)
|
|
||||||
p = mock.patch.object(attr_validator, 'validate_os_resources')
|
|
||||||
self.mock_valid_os_res = p.start()
|
|
||||||
self.addCleanup(p.stop)
|
|
||||||
|
|
||||||
def _simulate_rpc_bay_create(self, bay, master_count, node_count,
|
|
||||||
bay_create_timeout):
|
|
||||||
bay.create()
|
|
||||||
db_utils.create_nodegroups_for_cluster(
|
|
||||||
cluster_id=bay.uuid, node_count=node_count,
|
|
||||||
master_count=master_count)
|
|
||||||
return bay
|
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
|
||||||
def test_create_bay(self, mock_utcnow):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
|
||||||
mock_utcnow.return_value = test_time
|
|
||||||
|
|
||||||
response = self.post_json('/bays', bdict)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
# Check location header
|
|
||||||
self.assertIsNotNone(response.location)
|
|
||||||
self.assertTrue(uuidutils.is_uuid_like(response.json['uuid']))
|
|
||||||
self.assertNotIn('updated_at', response.json.keys)
|
|
||||||
return_created_at = timeutils.parse_isotime(
|
|
||||||
response.json['created_at']).replace(tzinfo=None)
|
|
||||||
self.assertEqual(test_time, return_created_at)
|
|
||||||
self.assertEqual(bdict['bay_create_timeout'],
|
|
||||||
response.json['bay_create_timeout'])
|
|
||||||
|
|
||||||
def test_create_bay_set_project_id_and_user_id(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
|
|
||||||
def _simulate_rpc_bay_create(bay, node_count, master_count,
|
|
||||||
bay_create_timeout):
|
|
||||||
self.assertEqual(self.context.project_id, bay.project_id)
|
|
||||||
self.assertEqual(self.context.user_id, bay.user_id)
|
|
||||||
bay.create()
|
|
||||||
db_utils.create_nodegroups_for_cluster(
|
|
||||||
cluster_id=bay.uuid, node_count=node_count,
|
|
||||||
master_count=master_count)
|
|
||||||
return bay
|
|
||||||
self.mock_bay_create.side_effect = _simulate_rpc_bay_create
|
|
||||||
|
|
||||||
self.post_json('/bays', bdict)
|
|
||||||
|
|
||||||
def test_create_bay_doesnt_contain_id(self):
|
|
||||||
with mock.patch.object(self.dbapi, 'create_cluster',
|
|
||||||
wraps=self.dbapi.create_cluster) as cc_mock:
|
|
||||||
bdict = apiutils.bay_post_data(name='bay_example_A')
|
|
||||||
response = self.post_json('/bays', bdict)
|
|
||||||
self.assertEqual(bdict['name'], response.json['name'])
|
|
||||||
cc_mock.assert_called_once_with(mock.ANY)
|
|
||||||
# Check that 'id' is not in first arg of positional args
|
|
||||||
self.assertNotIn('id', cc_mock.call_args[0][0])
|
|
||||||
|
|
||||||
def test_create_bay_generate_uuid(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
del bdict['uuid']
|
|
||||||
|
|
||||||
response = self.post_json('/bays', bdict)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(bdict['name'], response.json['name'])
|
|
||||||
self.assertTrue(uuidutils.is_uuid_like(response.json['uuid']))
|
|
||||||
|
|
||||||
def test_create_bay_no_baymodel_id(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
del bdict['baymodel_id']
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_non_existent_baymodel_id(self):
|
|
||||||
bdict = apiutils.bay_post_data(baymodel_id=uuidutils.generate_uuid())
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_baymodel_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(baymodel_id=self.cluster_template.name)
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_node_count_zero(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['node_count'] = 0
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_node_count_negative(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['node_count'] = -1
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_no_node_count(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
del bdict['node_count']
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(1, response.json['node_count'])
|
|
||||||
|
|
||||||
def test_create_bay_with_master_count_zero(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['master_count'] = 0
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_no_master_count(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
del bdict['master_count']
|
|
||||||
response = self.post_json('/bays', bdict)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(1, response.json['master_count'])
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_long_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='x' * 243)
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_integer_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='123456')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_integer_str_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='123456test_bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_hyphen_invalid_at_start_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='-test_bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_period_invalid_at_start_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='.test_bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_underscore_invalid_at_start_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='_test_bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_valid_str_int_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='test_bay123456')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_hyphen_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='test-bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_period_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='test.bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_period_at_end_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='testbay.')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_hyphen_at_end_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='testbay-')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_underscore_at_end_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='testbay_')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_mix_special_char_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='test.-_bay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_capital_letter_start_valid_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='Testbay')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(response.json['name'], bdict['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_empty_name(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_without_name(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
del bdict['name']
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertIsNotNone(response.json['name'])
|
|
||||||
|
|
||||||
def test_create_bay_with_timeout_none(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['bay_create_timeout'] = None
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_no_timeout(self):
|
|
||||||
def _simulate_rpc_bay_create(bay, node_count, master_count,
|
|
||||||
bay_create_timeout):
|
|
||||||
self.assertEqual(60, bay_create_timeout)
|
|
||||||
bay.create()
|
|
||||||
db_utils.create_nodegroups_for_cluster(
|
|
||||||
cluster_id=bay.uuid, node_count=node_count,
|
|
||||||
master_count=master_count)
|
|
||||||
return bay
|
|
||||||
self.mock_bay_create.side_effect = _simulate_rpc_bay_create
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
del bdict['bay_create_timeout']
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_timeout_negative(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['bay_create_timeout'] = -1
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_create_bay_with_timeout_zero(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['bay_create_timeout'] = 0
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_flavor(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
self.mock_valid_os_res.side_effect = exception.FlavorNotFound(
|
|
||||||
'test-flavor')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(self.mock_valid_os_res.called)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_ext_network(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
self.mock_valid_os_res.side_effect = exception.ExternalNetworkNotFound(
|
|
||||||
'test-net')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(self.mock_valid_os_res.called)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_invalid_keypair(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
self.mock_valid_os_res.side_effect = exception.KeyPairNotFound(
|
|
||||||
'test-key')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(self.mock_valid_os_res.called)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_nonexist_image(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
self.mock_valid_os_res.side_effect = exception.ImageNotFound(
|
|
||||||
'test-img')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(self.mock_valid_os_res.called)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_multi_images_same_name(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
self.mock_valid_os_res.side_effect = exception.Conflict('test-img')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(self.mock_valid_os_res.called)
|
|
||||||
self.assertEqual(409, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_on_os_distro_image(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
self.mock_valid_os_res.side_effect = exception.OSDistroFieldNotFound(
|
|
||||||
'img')
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(self.mock_valid_os_res.called)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_no_lb_one_node(self):
|
|
||||||
cluster_template = obj_utils.create_test_cluster_template(
|
|
||||||
self.context, name='foo', uuid='foo', master_lb_enabled=False)
|
|
||||||
bdict = apiutils.bay_post_data(baymodel_id=cluster_template.name,
|
|
||||||
master_count=1)
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_no_lb_multi_node(self):
|
|
||||||
cluster_template = obj_utils.create_test_cluster_template(
|
|
||||||
self.context, name='foo', uuid='foo', master_lb_enabled=False)
|
|
||||||
bdict = apiutils.bay_post_data(baymodel_id=cluster_template.name,
|
|
||||||
master_count=3, master_lb_enabled=False)
|
|
||||||
response = self.post_json('/bays', bdict, expect_errors=True)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(400, response.status_int)
|
|
||||||
|
|
||||||
def test_create_bay_with_docker_volume_size(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
bdict['docker_volume_size'] = 3
|
|
||||||
response = self.post_json('/bays', bdict)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
bay, timeout = self.mock_bay_create.call_args
|
|
||||||
self.assertEqual(3, bay[0].docker_volume_size)
|
|
||||||
|
|
||||||
def test_create_bay_without_docker_volume_size(self):
|
|
||||||
bdict = apiutils.bay_post_data()
|
|
||||||
# Remove the default docker_volume_size from the bay dict.
|
|
||||||
del bdict['docker_volume_size']
|
|
||||||
response = self.post_json('/bays', bdict)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
bay, timeout = self.mock_bay_create.call_args
|
|
||||||
# Verify docker_volume_size from BayModel is used
|
|
||||||
self.assertEqual(20, bay[0].docker_volume_size)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDelete(api_base.FunctionalTest):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDelete, self).setUp()
|
|
||||||
self.cluster_template = obj_utils.create_test_cluster_template(
|
|
||||||
self.context)
|
|
||||||
self.bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
p = mock.patch.object(rpcapi.API, 'cluster_delete')
|
|
||||||
self.mock_bay_delete = p.start()
|
|
||||||
self.mock_bay_delete.side_effect = self._simulate_rpc_bay_delete
|
|
||||||
self.addCleanup(p.stop)
|
|
||||||
|
|
||||||
def _simulate_rpc_bay_delete(self, bay_uuid):
|
|
||||||
bay = objects.Cluster.get_by_uuid(self.context, bay_uuid)
|
|
||||||
bay.destroy()
|
|
||||||
ngs = objects.NodeGroup.list(self.context, bay_uuid)
|
|
||||||
for ng in ngs:
|
|
||||||
ng.destroy()
|
|
||||||
|
|
||||||
def test_delete_bay(self):
|
|
||||||
self.delete('/bays/%s' % self.bay.uuid)
|
|
||||||
response = self.get_json('/bays/%s' % self.bay.uuid,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_delete_bay_not_found(self):
|
|
||||||
uuid = uuidutils.generate_uuid()
|
|
||||||
response = self.delete('/bays/%s' % uuid, expect_errors=True)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_delete_bay_with_name_not_found(self):
|
|
||||||
response = self.delete('/bays/not_found', expect_errors=True)
|
|
||||||
self.assertEqual(404, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
def test_delete_bay_with_name(self):
|
|
||||||
response = self.delete('/bays/%s' % self.bay.name,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(204, response.status_int)
|
|
||||||
|
|
||||||
def test_delete_multiple_bay_by_name(self):
|
|
||||||
obj_utils.create_test_cluster(self.context, name='test_bay',
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
obj_utils.create_test_cluster(self.context, name='test_bay',
|
|
||||||
uuid=uuidutils.generate_uuid())
|
|
||||||
response = self.delete('/bays/test_bay', expect_errors=True)
|
|
||||||
self.assertEqual(409, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(response.json['errors'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestBayPolicyEnforcement(api_base.FunctionalTest):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBayPolicyEnforcement, self).setUp()
|
|
||||||
obj_utils.create_test_cluster_template(self.context)
|
|
||||||
|
|
||||||
def _common_policy_check(self, rule, func, *arg, **kwarg):
|
|
||||||
self.policy.set_rules({rule: "project:non_fake"})
|
|
||||||
response = func(*arg, **kwarg)
|
|
||||||
self.assertEqual(403, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(
|
|
||||||
"Policy doesn't allow %s to be performed." % rule,
|
|
||||||
response.json['errors'][0]['detail'])
|
|
||||||
|
|
||||||
def test_policy_disallow_get_all(self):
|
|
||||||
self._common_policy_check(
|
|
||||||
"bay:get_all", self.get_json, '/bays', expect_errors=True)
|
|
||||||
|
|
||||||
def test_policy_disallow_get_one(self):
|
|
||||||
self.bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
self._common_policy_check(
|
|
||||||
"bay:get", self.get_json, '/bays/%s' % self.bay.uuid,
|
|
||||||
expect_errors=True)
|
|
||||||
|
|
||||||
def test_policy_disallow_detail(self):
|
|
||||||
self._common_policy_check(
|
|
||||||
"bay:detail", self.get_json,
|
|
||||||
'/bays/%s/detail' % uuidutils.generate_uuid(),
|
|
||||||
expect_errors=True)
|
|
||||||
|
|
||||||
def test_policy_disallow_update(self):
|
|
||||||
self.bay = obj_utils.create_test_cluster(self.context,
|
|
||||||
name='bay_example_A',
|
|
||||||
node_count=3)
|
|
||||||
self._common_policy_check(
|
|
||||||
"bay:update", self.patch_json, '/bays/%s' % self.bay.name,
|
|
||||||
[{'path': '/name', 'value': "new_name", 'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
|
|
||||||
def test_policy_disallow_create(self):
|
|
||||||
bdict = apiutils.bay_post_data(name='bay_example_A')
|
|
||||||
self._common_policy_check(
|
|
||||||
"bay:create", self.post_json, '/bays', bdict, expect_errors=True)
|
|
||||||
|
|
||||||
def _simulate_rpc_bay_delete(self, bay_uuid):
|
|
||||||
bay = objects.Cluster.get_by_uuid(self.context, bay_uuid)
|
|
||||||
bay.destroy()
|
|
||||||
|
|
||||||
def test_policy_disallow_delete(self):
|
|
||||||
p = mock.patch.object(rpcapi.API, 'cluster_delete')
|
|
||||||
self.mock_bay_delete = p.start()
|
|
||||||
self.mock_bay_delete.side_effect = self._simulate_rpc_bay_delete
|
|
||||||
self.addCleanup(p.stop)
|
|
||||||
self.bay = obj_utils.create_test_cluster(self.context)
|
|
||||||
self._common_policy_check(
|
|
||||||
"bay:delete", self.delete, '/bays/%s' % self.bay.uuid,
|
|
||||||
expect_errors=True)
|
|
||||||
|
|
||||||
def _owner_check(self, rule, func, *args, **kwargs):
|
|
||||||
self.policy.set_rules({rule: "user_id:%(user_id)s"})
|
|
||||||
response = func(*args, **kwargs)
|
|
||||||
self.assertEqual(403, response.status_int)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertTrue(
|
|
||||||
"Policy doesn't allow %s to be performed." % rule,
|
|
||||||
response.json['errors'][0]['detail'])
|
|
||||||
|
|
||||||
def test_policy_only_owner_get_one(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context, user_id='another')
|
|
||||||
self._owner_check("bay:get", self.get_json, '/bays/%s' % bay.uuid,
|
|
||||||
expect_errors=True)
|
|
||||||
|
|
||||||
def test_policy_only_owner_update(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context, user_id='another')
|
|
||||||
self._owner_check(
|
|
||||||
"bay:update", self.patch_json, '/bays/%s' % bay.uuid,
|
|
||||||
[{'path': '/name', 'value': "new_name", 'op': 'replace'}],
|
|
||||||
expect_errors=True)
|
|
||||||
|
|
||||||
def test_policy_only_owner_delete(self):
|
|
||||||
bay = obj_utils.create_test_cluster(self.context, user_id='another')
|
|
||||||
self._owner_check("bay:delete", self.delete, '/bays/%s' % bay.uuid,
|
|
||||||
expect_errors=True)
|
|
File diff suppressed because it is too large
Load Diff
@ -62,8 +62,6 @@ class TestGetCaCertificate(api_base.FunctionalTest):
|
|||||||
headers=HEADERS)
|
headers=HEADERS)
|
||||||
|
|
||||||
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
|
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
|
||||||
# check that bay is still valid as well
|
|
||||||
self.assertEqual(self.cluster.uuid, response['bay_uuid'])
|
|
||||||
self.assertEqual(fake_cert['csr'], response['csr'])
|
self.assertEqual(fake_cert['csr'], response['csr'])
|
||||||
self.assertEqual(fake_cert['pem'], response['pem'])
|
self.assertEqual(fake_cert['pem'], response['pem'])
|
||||||
|
|
||||||
@ -77,8 +75,6 @@ class TestGetCaCertificate(api_base.FunctionalTest):
|
|||||||
headers=HEADERS)
|
headers=HEADERS)
|
||||||
|
|
||||||
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
|
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
|
||||||
# check that bay is still valid as well
|
|
||||||
self.assertEqual(self.cluster.uuid, response['bay_uuid'])
|
|
||||||
self.assertEqual(fake_cert['csr'], response['csr'])
|
self.assertEqual(fake_cert['csr'], response['csr'])
|
||||||
self.assertEqual(fake_cert['pem'], response['pem'])
|
self.assertEqual(fake_cert['pem'], response['pem'])
|
||||||
|
|
||||||
@ -149,23 +145,6 @@ class TestPost(api_base.FunctionalTest):
|
|||||||
self.assertEqual(201, response.status_int)
|
self.assertEqual(201, response.status_int)
|
||||||
self.assertEqual(new_cert['cluster_uuid'],
|
self.assertEqual(new_cert['cluster_uuid'],
|
||||||
response.json['cluster_uuid'])
|
response.json['cluster_uuid'])
|
||||||
# verify bay_uuid is still valid as well
|
|
||||||
self.assertEqual(new_cert['cluster_uuid'], response.json['bay_uuid'])
|
|
||||||
self.assertEqual('fake-pem', response.json['pem'])
|
|
||||||
|
|
||||||
# Test that bay_uuid is still backward compatible
|
|
||||||
def test_create_cert_by_bay_name(self, ):
|
|
||||||
new_cert = api_utils.cert_post_data(cluster_uuid=self.cluster.uuid)
|
|
||||||
del new_cert['pem']
|
|
||||||
new_cert['bay_uuid'] = new_cert['cluster_uuid']
|
|
||||||
del new_cert['cluster_uuid']
|
|
||||||
|
|
||||||
response = self.post_json('/certificates', new_cert, headers=HEADERS)
|
|
||||||
self.assertEqual('application/json', response.content_type)
|
|
||||||
self.assertEqual(201, response.status_int)
|
|
||||||
self.assertEqual(self.cluster.uuid, response.json['cluster_uuid'])
|
|
||||||
# verify bay_uuid is still valid as well
|
|
||||||
self.assertEqual(self.cluster.uuid, response.json['bay_uuid'])
|
|
||||||
self.assertEqual('fake-pem', response.json['pem'])
|
self.assertEqual('fake-pem', response.json['pem'])
|
||||||
|
|
||||||
def test_create_cert_by_cluster_name(self, ):
|
def test_create_cert_by_cluster_name(self, ):
|
||||||
|
@ -49,7 +49,7 @@ class TestClusterObject(base.TestCase):
|
|||||||
cluster.cluster_template_id = wtypes.Unset
|
cluster.cluster_template_id = wtypes.Unset
|
||||||
self.assertEqual(wtypes.Unset, cluster.cluster_template_id)
|
self.assertEqual(wtypes.Unset, cluster.cluster_template_id)
|
||||||
|
|
||||||
# test backwards compatibility of bay fields with new objects
|
# test backwards compatibility of cluster fields with new objects
|
||||||
cluster_dict['create_timeout'] = 15
|
cluster_dict['create_timeout'] = 15
|
||||||
cluster = api_cluster.Cluster(**cluster_dict)
|
cluster = api_cluster.Cluster(**cluster_dict)
|
||||||
self.assertEqual(15, cluster.create_timeout)
|
self.assertEqual(15, cluster.create_timeout)
|
||||||
|
@ -16,8 +16,6 @@ import datetime
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from magnum.api.controllers.v1 import bay as bay_controller
|
|
||||||
from magnum.api.controllers.v1 import baymodel as baymodel_controller
|
|
||||||
from magnum.api.controllers.v1 import cluster as cluster_controller
|
from magnum.api.controllers.v1 import cluster as cluster_controller
|
||||||
from magnum.api.controllers.v1 import cluster_template as cluster_tmp_ctrl
|
from magnum.api.controllers.v1 import cluster_template as cluster_tmp_ctrl
|
||||||
from magnum.api.controllers.v1 import federation as federation_controller
|
from magnum.api.controllers.v1 import federation as federation_controller
|
||||||
@ -30,29 +28,12 @@ def remove_internal(values, internal):
|
|||||||
return {k: v for (k, v) in values.items() if k not in int_attr}
|
return {k: v for (k, v) in values.items() if k not in int_attr}
|
||||||
|
|
||||||
|
|
||||||
def baymodel_post_data(**kw):
|
|
||||||
baymodel = utils.get_test_cluster_template(**kw)
|
|
||||||
internal = baymodel_controller.BayModelPatchType.internal_attrs()
|
|
||||||
return remove_internal(baymodel, internal)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_template_post_data(**kw):
|
def cluster_template_post_data(**kw):
|
||||||
cluster_template = utils.get_test_cluster_template(**kw)
|
cluster_template = utils.get_test_cluster_template(**kw)
|
||||||
internal = cluster_tmp_ctrl.ClusterTemplatePatchType.internal_attrs()
|
internal = cluster_tmp_ctrl.ClusterTemplatePatchType.internal_attrs()
|
||||||
return remove_internal(cluster_template, internal)
|
return remove_internal(cluster_template, internal)
|
||||||
|
|
||||||
|
|
||||||
def bay_post_data(**kw):
|
|
||||||
kw.update({'for_api_use': True})
|
|
||||||
bay = utils.get_test_cluster(**kw)
|
|
||||||
bay['baymodel_id'] = kw.get('baymodel_id', bay['cluster_template_id'])
|
|
||||||
bay['bay_create_timeout'] = kw.get('bay_create_timeout', 15)
|
|
||||||
del bay['cluster_template_id']
|
|
||||||
del bay['create_timeout']
|
|
||||||
internal = bay_controller.BayPatchType.internal_attrs()
|
|
||||||
return remove_internal(bay, internal)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_post_data(**kw):
|
def cluster_post_data(**kw):
|
||||||
kw.update({'for_api_use': True})
|
kw.update({'for_api_use': True})
|
||||||
cluster = utils.get_test_cluster(**kw)
|
cluster = utils.get_test_cluster(**kw)
|
||||||
|
Loading…
Reference in New Issue
Block a user