From 11bcc17568e4bfd0fdee4cf04edcff3eace10ad4 Mon Sep 17 00:00:00 2001 From: Diogo Guerra Date: Fri, 6 Aug 2021 16:45:34 +0200 Subject: [PATCH] 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 Change-Id: Ida2e42c86400438951d9804e3ce122c56a46b94f --- api-ref/source/baymodels.inc | 366 ------ api-ref/source/bays.inc | 259 ---- api-ref/source/certificates.inc | 43 +- api-ref/source/index.rst | 2 - api-ref/source/parameters.yaml | 126 +- api-ref/source/samples/bay-create-req.json | 8 - api-ref/source/samples/bay-get-all-resp.json | 24 - api-ref/source/samples/bay-get-one-resp.json | 32 - .../source/samples/baymodel-create-resp.json | 44 - .../source/samples/baymodel-get-all-resp.json | 48 - .../samples/certificates-ca-show-resp.json | 1 - .../samples/certificates-ca-sign-req.json | 4 +- .../samples/certificates-ca-sign-resp.json | 4 +- .../source/samples/cluster-get-all-resp.json | 4 +- .../source/samples/versions-01-get-resp.json | 22 +- .../example/example_template/__init__.py | 4 +- doc/source/admin/troubleshooting-guide.rst | 4 +- doc/source/user/index.rst | 14 +- magnum/api/controllers/v1/__init__.py | 22 - magnum/api/controllers/v1/bay.py | 603 ---------- magnum/api/controllers/v1/baymodel.py | 422 ------- magnum/api/controllers/v1/certificate.py | 13 +- magnum/api/controllers/versions.py | 3 +- magnum/api/rest_api_version_history.rst | 5 + magnum/common/policies/__init__.py | 4 - magnum/common/policies/bay.py | 91 -- magnum/common/policies/baymodel.py | 106 -- magnum/common/policies/certificate.py | 8 +- magnum/conf/cluster_heat.py | 7 +- magnum/conf/cluster_templates.py | 6 +- magnum/db/sqlalchemy/models.py | 4 +- .../functional/api/v1/clients/bay_client.py | 170 --- .../api/v1/clients/baymodel_client.py | 105 -- .../functional/api/v1/models/bay_model.py | 30 - .../api/v1/models/baymodel_model.py | 30 - .../api/v1/models/baymodelpatch_model.py | 76 -- .../api/v1/models/baypatch_model.py | 76 -- magnum/tests/functional/common/datagen.py | 234 ---- magnum/tests/functional/common/manager.py | 8 +- .../tests/unit/api/controllers/test_root.py | 10 +- .../tests/unit/api/controllers/v1/test_bay.py | 971 --------------- .../unit/api/controllers/v1/test_baymodel.py | 1052 ----------------- .../api/controllers/v1/test_certificate.py | 21 - .../unit/api/controllers/v1/test_cluster.py | 2 +- magnum/tests/unit/api/utils.py | 19 - 45 files changed, 87 insertions(+), 5020 deletions(-) delete mode 100644 api-ref/source/baymodels.inc delete mode 100644 api-ref/source/bays.inc delete mode 100644 api-ref/source/samples/bay-create-req.json delete mode 100644 api-ref/source/samples/bay-get-all-resp.json delete mode 100644 api-ref/source/samples/bay-get-one-resp.json delete mode 100644 api-ref/source/samples/baymodel-create-resp.json delete mode 100644 api-ref/source/samples/baymodel-get-all-resp.json delete mode 100755 magnum/api/controllers/v1/bay.py delete mode 100644 magnum/api/controllers/v1/baymodel.py delete mode 100644 magnum/common/policies/bay.py delete mode 100644 magnum/common/policies/baymodel.py delete mode 100755 magnum/tests/functional/api/v1/clients/bay_client.py delete mode 100644 magnum/tests/functional/api/v1/clients/baymodel_client.py delete mode 100644 magnum/tests/functional/api/v1/models/bay_model.py delete mode 100644 magnum/tests/functional/api/v1/models/baymodel_model.py delete mode 100644 magnum/tests/functional/api/v1/models/baymodelpatch_model.py delete mode 100644 magnum/tests/functional/api/v1/models/baypatch_model.py delete mode 100644 magnum/tests/unit/api/controllers/v1/test_bay.py delete mode 100644 magnum/tests/unit/api/controllers/v1/test_baymodel.py diff --git a/api-ref/source/baymodels.inc b/api-ref/source/baymodels.inc deleted file mode 100644 index 86b917c1ac..0000000000 --- a/api-ref/source/baymodels.inc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/api-ref/source/bays.inc b/api-ref/source/bays.inc deleted file mode 100644 index f17a8a4668..0000000000 --- a/api-ref/source/bays.inc +++ /dev/null @@ -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 diff --git a/api-ref/source/certificates.inc b/api-ref/source/certificates.inc index 0e91a975f9..74c62e466f 100644 --- a/api-ref/source/certificates.inc +++ b/api-ref/source/certificates.inc @@ -1,17 +1,17 @@ .. -*- 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} -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. Response Codes @@ -34,11 +34,6 @@ Request - cluster_ident: cluster_ident - ca_cert_type: ca_cert_type -.. note:: - - After Newton, all terms related bay/baymodel will be renamed to cluster - and cluster template. - Response -------- @@ -47,26 +42,20 @@ Response - X-Openstack-Request-Id: request_id - cluster_uuid: cluster_id - pem: pem - - bay_uuid: bay_id - links: links -.. note:: - - After Newton, all terms related bay/baymodel will be renamed to cluster - and cluster template. - Response Example ---------------- .. literalinclude:: samples/certificates-ca-show-resp.json :language: javascript -Generate the CA certificate for a bay/cluster +Generate the CA certificate for a cluster ============================================= .. 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 -------------- @@ -86,14 +75,9 @@ Request .. rest_parameters:: parameters.yaml - - bay_uuid: bay_id + - cluster_uuid: cluster_id - csr: csr -.. note:: - - After Newton, all terms related bay/baymodel will be renamed to cluster - and cluster template. - Request Example ---------------- @@ -107,27 +91,22 @@ Response - X-Openstack-Request-Id: request_id - pem: pem - - bay_uuid: bay_id + - cluster_uuid: cluster_id - links: links - csr: csr -.. note:: - - After Newton, all terms related bay/baymodel will be renamed to cluster - and cluster template. - Response Example ---------------- .. literalinclude:: samples/certificates-ca-sign-resp.json :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. Response Codes diff --git a/api-ref/source/index.rst b/api-ref/source/index.rst index 9c829ec1b6..5539cebcc1 100644 --- a/api-ref/source/index.rst +++ b/api-ref/source/index.rst @@ -8,8 +8,6 @@ .. include:: versions.inc .. include:: urls.inc -.. include:: bays.inc -.. include:: baymodels.inc .. include:: clusters.inc .. include:: clustertemplates.inc .. include:: certificates.inc diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 8758a64666..dd85ba4c43 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -8,18 +8,6 @@ request_id: with the request by default appears in the service logs. # 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: type: string in: path @@ -59,40 +47,6 @@ apiserver_port: required: true description: | 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: type: string in: body @@ -105,7 +59,7 @@ cluster_distro: required: true description: | Display the attribute ``os_distro`` defined as appropriate metadata in - image for the bay/cluster driver. + image for the cluster driver. cluster_id: type: UUID in: body @@ -143,14 +97,14 @@ coe: description: | Specify the Container Orchestration Engine to use. Supported COEs 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. coe_version: type: string in: body required: true 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. create_timeout: type: integer @@ -214,7 +168,7 @@ discovery_url: 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. in: body format: uri @@ -222,8 +176,8 @@ discovery_url: type: string dns_nameserver: description: | - The DNS nameserver for the servers and containers in the bay/cluster to - use. This is configured in the private Neutron network for the bay/cluster. + The DNS nameserver for the servers and containers in the cluster to + use. This is configured in the private Neutron network for the cluster. The default is ``8.8.8.8``. in: body required: true @@ -248,29 +202,29 @@ docker_volume_size: external_network_id: description: | 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 - 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 network. This will allow the servers to download images, access discovery service, etc, and the containers to install packages, etc. In the opposite direction, floating IPs will be allocated from the external network to provide access from the external internet to servers and the container - services hosted in the bay/cluster. + services hosted in the cluster. in: body required: true type: string fixed_network: description: | 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 required: false type: string fixed_subnet: description: | Fixed subnet that are using to allocate network address for nodes in - bay/cluster. + cluster. in: body required: false type: string @@ -341,8 +295,8 @@ id_s: image_id: description: | 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 - appropriate for the bay/cluster driver. + cluster. The image must have the attribute ``os_distro`` defined as + appropriate for the cluster driver. in: body required: true type: string @@ -355,9 +309,9 @@ insecure_registry: type: string keypair_id: 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 - 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``. in: body required: true @@ -365,8 +319,8 @@ keypair_id: labels: description: | 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 - to pass additional parameters that are specific to a bay/cluster driver. + valid values are defined in the cluster drivers. They are used as a way + to pass additional parameters that are specific to a cluster driver. in: body required: false type: array @@ -384,40 +338,40 @@ master_addresses: type: array master_count: 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 - 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. in: body required: true type: integer master_flavor_id: description: | - The flavor of the master node for this baymodel/cluster template. + The flavor of the master node for this cluster template. in: body required: false type: string master_lb_enabled: description: | - Since multiple masters may exist in a bay/cluster, a Neutron load balancer - is created to provide the API endpoint for the bay/cluster and to direct + Since multiple masters may exist in a cluster, a Neutron load balancer + 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 - 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 the API endpoint. The default is ``true``, i.e. to create the load - balancer for the bay. + balancer for the cluster. in: body required: true type: boolean master_lb_enabled_cluster: description: | - Since multiple masters may exist in a bay/cluster, a Neutron load balancer - is created to provide the API endpoint for the bay/cluster and to direct + Since multiple masters may exist in a cluster, a Neutron load balancer + 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 - 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 the API endpoint. The default is ``true``, i.e. to create the load - balancer for the bay. + balancer for the cluster. in: body required: false type: boolean @@ -443,7 +397,7 @@ network_driver: description: | 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 - bay/cluster. The operation and networking model are specific to the + cluster. The operation and networking model are specific to the particular driver. in: body required: true @@ -464,7 +418,7 @@ node_addresses: type: array node_count: 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. in: body required: true @@ -504,15 +458,15 @@ path: type: string pem: description: | - CA certificate for the bay/cluster. + CA certificate for the cluster. in: body required: true type: string public_type: 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 - 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. in: body required: true @@ -522,7 +476,7 @@ registry_enabled: 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 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. in: body required: false @@ -535,8 +489,8 @@ report_count: type: integer server_type: description: | - The servers in the bay/cluster can be ``vm`` or ``baremetal``. This - parameter selects the type of server to create for the bay/cluster. + The servers in the cluster can be ``vm`` or ``baremetal``. This + parameter selects the type of server to create for the cluster. The default is ``vm``. in: body required: true @@ -555,13 +509,13 @@ state: type: string status: description: | - The current state of the bay/cluster. + The current state of the cluster. in: body required: true type: string status_reason: description: | - The reason of bay/cluster current status. + The reason of cluster current status. in: body required: true type: string @@ -574,8 +528,8 @@ tags: tls_disabled: description: | Transport Layer Security (TLS) is normally enabled to secure the - bay/cluster. In some cases, users may want to disable TLS in the - bay/cluster, for instance during development or to troubleshoot certain + cluster. In some cases, users may want to disable TLS in the + cluster, for instance during development or to troubleshoot certain problems. Specifying this parameter will disable TLS so that users can access the COE endpoints without a certificate. The default is TLS enabled. in: body diff --git a/api-ref/source/samples/bay-create-req.json b/api-ref/source/samples/bay-create-req.json deleted file mode 100644 index bb3dd04a66..0000000000 --- a/api-ref/source/samples/bay-create-req.json +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/api-ref/source/samples/bay-get-all-resp.json b/api-ref/source/samples/bay-get-all-resp.json deleted file mode 100644 index 9e970c4bcf..0000000000 --- a/api-ref/source/samples/bay-get-all-resp.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/api-ref/source/samples/bay-get-one-resp.json b/api-ref/source/samples/bay-get-one-resp.json deleted file mode 100644 index 93cca3cb99..0000000000 --- a/api-ref/source/samples/bay-get-one-resp.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/api-ref/source/samples/baymodel-create-resp.json b/api-ref/source/samples/baymodel-create-resp.json deleted file mode 100644 index 7b1f4f412b..0000000000 --- a/api-ref/source/samples/baymodel-create-resp.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/api-ref/source/samples/baymodel-get-all-resp.json b/api-ref/source/samples/baymodel-get-all-resp.json deleted file mode 100644 index e7c7d6c207..0000000000 --- a/api-ref/source/samples/baymodel-get-all-resp.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/api-ref/source/samples/certificates-ca-show-resp.json b/api-ref/source/samples/certificates-ca-show-resp.json index 531af55073..a8cae4c173 100644 --- a/api-ref/source/samples/certificates-ca-show-resp.json +++ b/api-ref/source/samples/certificates-ca-show-resp.json @@ -1,7 +1,6 @@ { "cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4", "pem":"-----BEGIN CERTIFICATE-----\nMIICzDCCAbSgAwIBAgIQOOkVcEN7TNa9E80GoUs4xDANBgkqhkiG9w0BAQsFADAO\n-----END CERTIFICATE-----\n", - "bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4", "links":[ { "href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4", diff --git a/api-ref/source/samples/certificates-ca-sign-req.json b/api-ref/source/samples/certificates-ca-sign-req.json index b2ff96c22c..e97bf7ade4 100644 --- a/api-ref/source/samples/certificates-ca-sign-req.json +++ b/api-ref/source/samples/certificates-ca-sign-req.json @@ -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" -} \ No newline at end of file +} diff --git a/api-ref/source/samples/certificates-ca-sign-resp.json b/api-ref/source/samples/certificates-ca-sign-resp.json index 9858da7e2d..6b71a043b6 100644 --- a/api-ref/source/samples/certificates-ca-sign-resp.json +++ b/api-ref/source/samples/certificates-ca-sign-resp.json @@ -1,6 +1,6 @@ { "pem":"-----BEGIN CERTIFICATE-----\nMIIDxDCCAqygAwIBAgIRALgUbIjdKUy8lqErJmCxVfkwDQYJKoZIhvcNAQELBQAw\n-----END CERTIFICATE-----\n", - "bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4", + "cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4", "links":[ { "href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4", @@ -12,4 +12,4 @@ } ], "csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n" -} \ No newline at end of file +} diff --git a/api-ref/source/samples/cluster-get-all-resp.json b/api-ref/source/samples/cluster-get-all-resp.json index 5c181fa89d..96119a7cd2 100644 --- a/api-ref/source/samples/cluster-get-all-resp.json +++ b/api-ref/source/samples/cluster-get-all-resp.json @@ -6,11 +6,11 @@ "uuid":"731387cf-a92b-4c36-981e-3271d63e5597", "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" }, { - "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" } ], diff --git a/api-ref/source/samples/versions-01-get-resp.json b/api-ref/source/samples/versions-01-get-resp.json index 299d001e85..2018c0989b 100644 --- a/api-ref/source/samples/versions-01-get-resp.json +++ b/api-ref/source/samples/versions-01-get-resp.json @@ -26,16 +26,6 @@ "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":[ { "href":"http://10.164.180.104:9511/v1/clustertemplates/", @@ -66,15 +56,5 @@ "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" -} \ No newline at end of file +} diff --git a/contrib/templates/example/example_template/__init__.py b/contrib/templates/example/example_template/__init__.py index 58a3d5d5d9..4d2032db02 100644 --- a/contrib/templates/example/example_template/__init__.py +++ b/contrib/templates/example/example_template/__init__.py @@ -28,9 +28,9 @@ class ExampleTemplate(template_def.BaseTemplateDefinition): super(ExampleTemplate, self).__init__() self.add_output('server_address', - bay_attr='api_address') + cluster_attr='api_address') self.add_output('node_addresses', - bay_attr='node_addresses') + cluster_attr='node_addresses') def template_path(self): return os.path.join(os.path.dirname(__file__), 'example.yaml') diff --git a/doc/source/admin/troubleshooting-guide.rst b/doc/source/admin/troubleshooting-guide.rst index 7fd482c86a..1d43579501 100644 --- a/doc/source/admin/troubleshooting-guide.rst +++ b/doc/source/admin/troubleshooting-guide.rst @@ -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 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 -the service account created for the cluster (the "trustee"). For details, -please refer to -`Create a trustee user for each bay `_. +the service account created for the cluster (the "trustee"). 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' diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst index 189a0db60e..a893a93fb2 100644 --- a/doc/source/user/index.rst +++ b/doc/source/user/index.rst @@ -481,7 +481,7 @@ the table are linked to more details elsewhere in the user guide. 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 defined in the particular ClusterTemplate as well as a few additional 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 ++++++ -**NOTE** bay- 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:: openstack coe cluster create mycluster \ @@ -2884,14 +2877,11 @@ Supported Events ---------------- The following table displays the corresponding relationship between resource -types and operations. The bay type is deprecated and will be removed in a -future version. Cluster is the new equivalent term. +types and operations. +---------------+----------------------------+-------------------------+ | resource type | supported operations | typeURI | +===============+============================+=========================+ -| bay | create, update, delete | service/magnum/bay | -+---------------+----------------------------+-------------------------+ | cluster | create, update, delete | service/magnum/cluster | +---------------+----------------------------+-------------------------+ diff --git a/magnum/api/controllers/v1/__init__.py b/magnum/api/controllers/v1/__init__.py index 408631d2d0..a8d6941052 100644 --- a/magnum/api/controllers/v1/__init__.py +++ b/magnum/api/controllers/v1/__init__.py @@ -24,8 +24,6 @@ from wsme import types as wtypes from magnum.api.controllers import base as controllers_base 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 cluster from magnum.api.controllers.v1 import cluster_template @@ -76,12 +74,6 @@ class V1(controllers_base.APIBase): links = [link.Link] """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] """Links to the clustertemplates resource""" @@ -119,18 +111,6 @@ class V1(controllers_base.APIBase): bookmark=True, type='text/html')] v1.media_types = [MediaType('application/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', pecan.request.host_url, 'clustertemplates', ''), @@ -189,8 +169,6 @@ class V1(controllers_base.APIBase): class Controller(controllers_base.Controller): """Version 1 API controller root.""" - bays = bay.BaysController() - baymodels = baymodel.BayModelsController() clusters = cluster.ClustersController() clustertemplates = cluster_template.ClusterTemplatesController() quotas = quota.QuotaController() diff --git a/magnum/api/controllers/v1/bay.py b/magnum/api/controllers/v1/bay.py deleted file mode 100755 index 56729cc1e1..0000000000 --- a/magnum/api/controllers/v1/bay.py +++ /dev/null @@ -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 diff --git a/magnum/api/controllers/v1/baymodel.py b/magnum/api/controllers/v1/baymodel.py deleted file mode 100644 index fc11fa16a4..0000000000 --- a/magnum/api/controllers/v1/baymodel.py +++ /dev/null @@ -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() diff --git a/magnum/api/controllers/v1/certificate.py b/magnum/api/controllers/v1/certificate.py index 353827fa77..24f76c5292 100644 --- a/magnum/api/controllers/v1/certificate.py +++ b/magnum/api/controllers/v1/certificate.py @@ -71,10 +71,6 @@ class Certificate(base.APIBase): elif value == 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, _set_cluster_uuid) """The cluster UUID or id""" @@ -102,10 +98,6 @@ class Certificate(base.APIBase): self.fields.append(field) 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): if not self._cluster: self._cluster = api_utils.get_resource('Cluster', @@ -115,7 +107,7 @@ class Certificate(base.APIBase): @staticmethod def _convert_with_links(certificate, url, expand=True): if not expand: - certificate.unset_fields_except(['bay_uuid', 'cluster_uuid', + certificate.unset_fields_except(['cluster_uuid', 'csr', 'pem', 'ca_cert_type']) certificate.links = [link.Link.make_link('self', url, @@ -135,8 +127,7 @@ class Certificate(base.APIBase): @classmethod def sample(cls, expand=True): - sample = cls(bay_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae', - cluster_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae', + sample = cls(cluster_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae', created_at=timeutils.utcnow(), csr='AAA....AAA', ca_cert_type='kubernetes') diff --git a/magnum/api/controllers/versions.py b/magnum/api/controllers/versions.py index 5059d6eb4f..ff60c8a383 100644 --- a/magnum/api/controllers/versions.py +++ b/magnum/api/controllers/versions.py @@ -33,10 +33,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.8 - Add upgrade API * 1.9 - Add nodegroup API * 1.10 - Allow nodegroups with 0 nodes + * 1.11 - Remove bay and baymodel objects """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.10' +CURRENT_MAX_VER = '1.11' class Version(object): diff --git a/magnum/api/rest_api_version_history.rst b/magnum/api/rest_api_version_history.rst index fd4784d821..bcbfa3a8c1 100644 --- a/magnum/api/rest_api_version_history.rst +++ b/magnum/api/rest_api_version_history.rst @@ -104,3 +104,8 @@ user documentation. Allow the cluster to be created with node_count = 0 as well as to update existing nodegroups to have 0 nodes. + +1.11 +--- + + Drop bay and baymodels objects from magnum source code diff --git a/magnum/common/policies/__init__.py b/magnum/common/policies/__init__.py index b0733fa53e..f3a0a7dfcb 100644 --- a/magnum/common/policies/__init__.py +++ b/magnum/common/policies/__init__.py @@ -15,8 +15,6 @@ import itertools 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 cluster from magnum.common.policies import cluster_template @@ -30,8 +28,6 @@ from magnum.common.policies import stats def list_rules(): return itertools.chain( base.list_rules(), - bay.list_rules(), - baymodel.list_rules(), certificate.list_rules(), cluster.list_rules(), cluster_template.list_rules(), diff --git a/magnum/common/policies/bay.py b/magnum/common/policies/bay.py deleted file mode 100644 index 4cba970a53..0000000000 --- a/magnum/common/policies/bay.py +++ /dev/null @@ -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 diff --git a/magnum/common/policies/baymodel.py b/magnum/common/policies/baymodel.py deleted file mode 100644 index a238079a9b..0000000000 --- a/magnum/common/policies/baymodel.py +++ /dev/null @@ -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 diff --git a/magnum/common/policies/certificate.py b/magnum/common/policies/certificate.py index 5e96b64f5b..3d385754e8 100644 --- a/magnum/common/policies/certificate.py +++ b/magnum/common/policies/certificate.py @@ -34,10 +34,10 @@ rules = [ policy.DocumentedRuleDefault( name=CERTIFICATE % 'get', 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=[ { - 'path': '/v1/certificates/{bay_uuid/cluster_uuid}', + 'path': '/v1/certificates/{cluster_uuid}', 'method': 'GET' } ] @@ -45,10 +45,10 @@ rules = [ policy.DocumentedRuleDefault( name=CERTIFICATE % 'rotate_ca', 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=[ { - 'path': '/v1/certificates/{bay_uuid/cluster_uuid}', + 'path': '/v1/certificates/{cluster_uuid}', 'method': 'PATCH' } ] diff --git a/magnum/conf/cluster_heat.py b/magnum/conf/cluster_heat.py index 07eb9e5259..27a09b8868 100644 --- a/magnum/conf/cluster_heat.py +++ b/magnum/conf/cluster_heat.py @@ -25,19 +25,18 @@ cluster_heat_opts = [ 'during cluster creation if timeout is set as the poll ' 'will continue until cluster creation either ends ' 'or times out.'), - deprecated_group='bay_heat'), + ), cfg.IntOpt('wait_interval', default=1, help=('Sleep time interval between two attempts of querying ' 'the Heat stack. This interval is in seconds.'), - deprecated_group='bay_heat'), + ), cfg.IntOpt('create_timeout', default=60, help=('The length of time to let cluster creation continue. ' 'This interval is in minutes. The default is 60 minutes.' ), - deprecated_group='bay_heat', - deprecated_name='bay_create_timeout') + ) ] diff --git a/magnum/conf/cluster_templates.py b/magnum/conf/cluster_templates.py index 4e1dc1fae0..e708151155 100644 --- a/magnum/conf/cluster_templates.py +++ b/magnum/conf/cluster_templates.py @@ -24,12 +24,12 @@ cluster_template_opts = [ "cluster-templates. Use 'all' keyword to allow all " "drivers supported for kubernetes cluster-templates. " "Supported network drivers include flannel."), - deprecated_group='baymodel'), + ), cfg.StrOpt('kubernetes_default_network_driver', default='flannel', help=_("Default network driver for kubernetes " "cluster-templates."), - deprecated_group='baymodel'), + ), cfg.ListOpt('swarm_allowed_network_drivers', default=['all'], help=_("Allowed network drivers for docker swarm " @@ -37,7 +37,7 @@ cluster_template_opts = [ "drivers supported for swarm cluster-templates. " "Supported network drivers include docker and flannel." ), - deprecated_group='baymodel'), + ), cfg.StrOpt('swarm_default_network_driver', default='docker', help=_("Default network driver for docker swarm " diff --git a/magnum/db/sqlalchemy/models.py b/magnum/db/sqlalchemy/models.py index 74bc8ca6cc..8d093d6690 100644 --- a/magnum/db/sqlalchemy/models.py +++ b/magnum/db/sqlalchemy/models.py @@ -107,7 +107,7 @@ class Cluster(Base): __tablename__ = 'cluster' __table_args__ = ( - schema.UniqueConstraint('uuid', name='uniq_bay0uuid'), + schema.UniqueConstraint('uuid'), table_args() ) id = Column(Integer, primary_key=True) @@ -158,7 +158,7 @@ class ClusterTemplate(Base): __tablename__ = 'cluster_template' __table_args__ = ( - schema.UniqueConstraint('uuid', name='uniq_baymodel0uuid'), + schema.UniqueConstraint('uuid'), table_args() ) id = Column(Integer, primary_key=True) diff --git a/magnum/tests/functional/api/v1/clients/bay_client.py b/magnum/tests/functional/api/v1/clients/bay_client.py deleted file mode 100755 index 844ed4a2ba..0000000000 --- a/magnum/tests/functional/api/v1/clients/bay_client.py +++ /dev/null @@ -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 diff --git a/magnum/tests/functional/api/v1/clients/baymodel_client.py b/magnum/tests/functional/api/v1/clients/baymodel_client.py deleted file mode 100644 index 10d6a8b4a4..0000000000 --- a/magnum/tests/functional/api/v1/clients/baymodel_client.py +++ /dev/null @@ -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) diff --git a/magnum/tests/functional/api/v1/models/bay_model.py b/magnum/tests/functional/api/v1/models/bay_model.py deleted file mode 100644 index 4c89acf305..0000000000 --- a/magnum/tests/functional/api/v1/models/bay_model.py +++ /dev/null @@ -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 diff --git a/magnum/tests/functional/api/v1/models/baymodel_model.py b/magnum/tests/functional/api/v1/models/baymodel_model.py deleted file mode 100644 index 606fb04f81..0000000000 --- a/magnum/tests/functional/api/v1/models/baymodel_model.py +++ /dev/null @@ -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 diff --git a/magnum/tests/functional/api/v1/models/baymodelpatch_model.py b/magnum/tests/functional/api/v1/models/baymodelpatch_model.py deleted file mode 100644 index 1623cb366e..0000000000 --- a/magnum/tests/functional/api/v1/models/baymodelpatch_model.py +++ /dev/null @@ -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 diff --git a/magnum/tests/functional/api/v1/models/baypatch_model.py b/magnum/tests/functional/api/v1/models/baypatch_model.py deleted file mode 100644 index 717f65cd79..0000000000 --- a/magnum/tests/functional/api/v1/models/baypatch_model.py +++ /dev/null @@ -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 diff --git a/magnum/tests/functional/common/datagen.py b/magnum/tests/functional/common/datagen.py index 70ad15bc5b..e64144aa93 100644 --- a/magnum/tests/functional/common/datagen.py +++ b/magnum/tests/functional/common/datagen.py @@ -17,10 +17,6 @@ import struct 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 cluster_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)) -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): data = { "cluster_uuid": cluster_uuid, diff --git a/magnum/tests/functional/common/manager.py b/magnum/tests/functional/common/manager.py index 9ecf0460cc..5e7a9f4184 100644 --- a/magnum/tests/functional/common/manager.py +++ b/magnum/tests/functional/common/manager.py @@ -13,8 +13,6 @@ from tempest import clients 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 cluster_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.base_url = self.bypassed_base_url auth = self.auth_provider - if request_type == 'baymodel': - self.client = baymodel_client.BayModelClient(auth) - elif request_type == 'bay': - self.client = bay_client.BayClient(auth) - elif request_type == 'cert': + if request_type == 'cert': self.client = cert_client.CertClient(auth) elif request_type == 'cluster_template': self.client = cluster_template_client.ClusterTemplateClient(auth) diff --git a/magnum/tests/unit/api/controllers/test_root.py b/magnum/tests/unit/api/controllers/test_root.py index e187715016..2430dc29d7 100644 --- a/magnum/tests/unit/api/controllers/test_root.py +++ b/magnum/tests/unit/api/controllers/test_root.py @@ -41,7 +41,7 @@ class TestRootController(api_base.FunctionalTest): [{u'href': u'http://localhost/v1/', u'rel': u'self'}], u'status': u'CURRENT', - u'max_version': u'1.10', + u'max_version': u'1.11', u'min_version': u'1.1'}]} self.v1_expected = { @@ -58,14 +58,6 @@ class TestRootController(api_base.FunctionalTest): u'rel': u'self'}, {u'href': u'http://localhost/stats/', 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'rel': u'self'}, {u'href': u'http://localhost/clusters/', diff --git a/magnum/tests/unit/api/controllers/v1/test_bay.py b/magnum/tests/unit/api/controllers/v1/test_bay.py deleted file mode 100644 index 7657affa63..0000000000 --- a/magnum/tests/unit/api/controllers/v1/test_bay.py +++ /dev/null @@ -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) diff --git a/magnum/tests/unit/api/controllers/v1/test_baymodel.py b/magnum/tests/unit/api/controllers/v1/test_baymodel.py deleted file mode 100644 index 1929dee222..0000000000 --- a/magnum/tests/unit/api/controllers/v1/test_baymodel.py +++ /dev/null @@ -1,1052 +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_utils import timeutils -from oslo_utils import uuidutils -from six.moves.urllib import parse as urlparse -from webtest.app import AppError -from wsme import types as wtypes - -from magnum.api import attr_validator -from magnum.api.controllers.v1 import baymodel as api_baymodel -from magnum.common import exception -from magnum.common import policy as magnum_policy -import magnum.conf -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.objects import utils as obj_utils - -CONF = magnum.conf.CONF - - -class TestBayModelObject(base.TestCase): - - def test_baymodel_init(self): - baymodel_dict = apiutils.baymodel_post_data() - del baymodel_dict['image_id'] - baymodel = api_baymodel.BayModel(**baymodel_dict) - self.assertEqual(wtypes.Unset, baymodel.image_id) - - -class TestListBayModel(api_base.FunctionalTest): - - _baymodel_attrs = ('name', 'apiserver_port', 'network_driver', - 'coe', 'flavor_id', 'fixed_network', - 'dns_nameserver', 'http_proxy', - 'docker_volume_size', 'server_type', - 'cluster_distro', 'external_network_id', - 'image_id', 'registry_enabled', 'no_proxy', - 'keypair_id', 'https_proxy', 'tls_disabled', - 'public', 'labels', 'master_flavor_id', - 'volume_driver', 'insecure_registry') - - def test_empty(self): - response = self.get_json('/baymodels') - self.assertEqual([], response['baymodels']) - - def test_one(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - response = self.get_json('/baymodels') - self.assertEqual(baymodel.uuid, response['baymodels'][0]["uuid"]) - self._verify_attrs(self._baymodel_attrs, - response['baymodels'][0]) - - def test_get_one(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - response = self.get_json('/baymodels/%s' % baymodel['uuid']) - self.assertEqual(baymodel.uuid, response['uuid']) - self._verify_attrs(self._baymodel_attrs, response) - - def test_get_one_by_name(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - response = self.get_json('/baymodels/%s' % baymodel['name']) - self.assertEqual(baymodel.uuid, response['uuid']) - self._verify_attrs(self._baymodel_attrs, response) - - def test_get_one_by_name_not_found(self): - response = self.get_json( - '/baymodels/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_baymodel(self): - obj_utils.create_test_cluster_template( - self.context, name='test_baymodel', - uuid=uuidutils.generate_uuid()) - obj_utils.create_test_cluster_template( - self.context, name='test_baymodel', - uuid=uuidutils.generate_uuid()) - response = self.get_json( - '/baymodels/test_baymodel', - 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): - bm_list = [] - for id_ in range(4): - baymodel = obj_utils.create_test_cluster_template( - self.context, id=id_, - uuid=uuidutils.generate_uuid()) - bm_list.append(baymodel) - - response = self.get_json('/baymodels?limit=3&marker=%s' - % bm_list[2].uuid) - self.assertEqual(1, len(response['baymodels'])) - self.assertEqual(bm_list[-1].uuid, response['baymodels'][0]['uuid']) - - def test_detail(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - response = self.get_json('/baymodels/detail') - self.assertEqual(baymodel.uuid, response['baymodels'][0]["uuid"]) - self._verify_attrs(self._baymodel_attrs, - response['baymodels'][0]) - - def test_detail_with_pagination_marker(self): - bm_list = [] - for id_ in range(4): - baymodel = obj_utils.create_test_cluster_template( - self.context, id=id_, - uuid=uuidutils.generate_uuid()) - bm_list.append(baymodel) - - response = self.get_json('/baymodels/detail?limit=3&marker=%s' - % bm_list[2].uuid) - self.assertEqual(1, len(response['baymodels'])) - self.assertEqual(bm_list[-1].uuid, response['baymodels'][0]['uuid']) - self._verify_attrs(self._baymodel_attrs, - response['baymodels'][0]) - - def test_detail_against_single(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - response = self.get_json('/baymodels/%s/detail' % baymodel['uuid'], - expect_errors=True) - self.assertEqual(404, response.status_int) - - def test_many(self): - bm_list = [] - for id_ in range(5): - baymodel = obj_utils.create_test_cluster_template( - self.context, id=id_, - uuid=uuidutils.generate_uuid()) - bm_list.append(baymodel.uuid) - response = self.get_json('/baymodels') - self.assertEqual(len(bm_list), len(response['baymodels'])) - uuids = [bm['uuid'] for bm in response['baymodels']] - self.assertEqual(sorted(bm_list), sorted(uuids)) - - def test_links(self): - uuid = uuidutils.generate_uuid() - obj_utils.create_test_cluster_template(self.context, id=1, uuid=uuid) - response = self.get_json('/baymodels/%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_template( - self.context, id=id_, uuid=uuidutils.generate_uuid()) - response = self.get_json('/baymodels/?limit=3') - self.assertEqual(3, len(response['baymodels'])) - - next_marker = response['baymodels'][-1]['uuid'] - self.assertIn(next_marker, response['next']) - - def test_collection_links_default_limit(self): - CONF.set_override('max_limit', 3, 'api') - for id_ in range(5): - obj_utils.create_test_cluster_template( - self.context, id=id_, uuid=uuidutils.generate_uuid()) - response = self.get_json('/baymodels') - self.assertEqual(3, len(response['baymodels'])) - - next_marker = response['baymodels'][-1]['uuid'] - self.assertIn(next_marker, response['next']) - - -class TestPatch(api_base.FunctionalTest): - - def setUp(self): - super(TestPatch, self).setUp() - p = mock.patch.object(attr_validator, 'validate_os_resources') - self.mock_valid_os_res = p.start() - self.addCleanup(p.stop) - self.baymodel = obj_utils.create_test_cluster_template( - self.context, - name='bay_model_example_A', - image_id='nerdherd', - apiserver_port=8080, - fixed_network='private', - flavor_id='m1.magnum', - master_flavor_id='m1.magnum', - external_network_id='public', - keypair_id='test', - volume_driver='rexray', - public=False, - docker_volume_size=20, - coe='swarm', - labels={'key1': 'val1', 'key2': 'val2'} - ) - - def test_update_not_found(self): - uuid = uuidutils.generate_uuid() - response = self.patch_json('/baymodels/%s' % uuid, - [{'path': '/name', - 'value': 'bay_model_example_B', - 'op': 'add'}], - expect_errors=True) - self.assertEqual(404, response.status_int) - self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['errors']) - - def test_update_baymodel_with_bay(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - obj_utils.create_test_cluster(self.context, - cluster_template_id=baymodel.uuid) - - response = self.patch_json('/baymodels/%s' % baymodel.uuid, - [{'path': '/network_driver', - 'value': 'flannel', - 'op': 'replace'}], - expect_errors=True) - self.assertEqual(400, response.status_int) - self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['errors']) - self.assertIn(baymodel.uuid, response.json['errors'][0]['detail']) - - def test_update_baymodel_name_with_bay(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - obj_utils.create_test_cluster(self.context, - cluster_template_id=baymodel.uuid) - - response = self.patch_json('/baymodels/%s' % baymodel.uuid, - [{'path': '/name', - 'value': 'bay_model_example_B', - 'op': 'replace'}], - expect_errors=True) - self.assertEqual(200, response.status_int) - - @mock.patch.object(magnum_policy, 'enforce') - def test_update_public_baymodel_success(self, mock_policy): - mock_policy.return_value = True - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/public', 'value': True, - 'op': 'replace'}]) - self.assertEqual('application/json', response.content_type) - self.assertEqual(200, response.status_code) - - response = self.get_json('/baymodels/%s' % self.baymodel.uuid) - self.assertTrue(response['public']) - - @mock.patch.object(magnum_policy, 'enforce') - def test_update_public_baymodel_fail(self, mock_policy): - mock_policy.return_value = False - self.assertRaises(AppError, self.patch_json, - '/baymodels/%s' % self.baymodel.uuid, - [{'path': '/public', 'value': True, - 'op': 'replace'}]) - - @mock.patch.object(magnum_policy, 'enforce') - def test_update_baymodel_with_bay_allow_update(self, mock_policy): - mock_policy.return_value = True - baymodel = obj_utils.create_test_cluster_template(self.context) - obj_utils.create_test_cluster(self.context, - cluster_template_id=baymodel.uuid) - response = self.patch_json('/baymodels/%s' % baymodel.uuid, - [{'path': '/public', - 'value': True, - 'op': 'replace'}], - expect_errors=True) - self.assertEqual(200, response.status_int) - response = self.get_json('/baymodels/%s' % self.baymodel.uuid) - self.assertEqual(response['public'], True) - - def test_update_baymodel_with_bay_not_allow_update(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - obj_utils.create_test_cluster(self.context, - cluster_template_id=baymodel.uuid) - response = self.patch_json('/baymodels/%s' % baymodel.uuid, - [{'path': '/network_driver', - 'value': 'calico', - 'op': 'replace'}], - expect_errors=True) - self.assertEqual(400, response.status_code) - - @mock.patch('oslo_utils.timeutils.utcnow') - def test_replace_singular(self, mock_utcnow): - name = 'bay_model_example_B' - test_time = datetime.datetime(2000, 1, 1, 0, 0) - - mock_utcnow.return_value = test_time - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/name', 'value': name, - 'op': 'replace'}]) - self.assertEqual('application/json', response.content_type) - self.assertEqual(200, response.status_code) - - response = self.get_json('/baymodels/%s' % self.baymodel.uuid) - self.assertEqual(name, response['name']) - 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.baymodel.uuid, response['uuid']) - self.assertEqual(self.baymodel.image_id, response['image_id']) - self.assertEqual(self.baymodel.apiserver_port, - response['apiserver_port']) - self.assertEqual(self.baymodel.fixed_network, - response['fixed_network']) - self.assertEqual(self.baymodel.network_driver, - response['network_driver']) - self.assertEqual(self.baymodel.volume_driver, - response['volume_driver']) - self.assertEqual(self.baymodel.docker_volume_size, - response['docker_volume_size']) - self.assertEqual(self.baymodel.coe, - response['coe']) - self.assertEqual(self.baymodel.http_proxy, - response['http_proxy']) - self.assertEqual(self.baymodel.https_proxy, - response['https_proxy']) - self.assertEqual(self.baymodel.no_proxy, - response['no_proxy']) - self.assertEqual(self.baymodel.labels, - response['labels']) - - def test_replace_baymodel_with_no_exist_flavor_id(self): - self.mock_valid_os_res.side_effect = exception.FlavorNotFound("aaa") - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/flavor_id', 'value': 'aaa', - '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_baymodel_with_no_exist_keypair_id(self): - self.mock_valid_os_res.side_effect = exception.KeyPairNotFound("aaa") - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/keypair_id', 'value': 'aaa', - 'op': 'replace'}], - expect_errors=True) - self.assertEqual('application/json', response.content_type) - self.assertEqual(404, response.status_code) - self.assertTrue(response.json['errors']) - - def test_replace_baymodel_with_no_exist_external_network_id(self): - self.mock_valid_os_res.side_effect = exception.ExternalNetworkNotFound( - "aaa") - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/external_network_id', - 'value': 'aaa', - '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_baymodel_with_no_exist_image_id(self): - self.mock_valid_os_res.side_effect = exception.ImageNotFound("aaa") - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/image_id', 'value': 'aaa', - '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_create_baymodel_with_no_os_distro_image(self): - image_exce = exception.OSDistroFieldNotFound('img') - self.mock_valid_os_res.side_effect = image_exce - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/image_id', 'value': 'img', - '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_remove_singular(self): - response = self.get_json('/baymodels/%s' % self.baymodel.uuid) - self.assertIsNotNone(response['dns_nameserver']) - - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/dns_nameserver', - 'op': 'remove'}]) - self.assertEqual('application/json', response.content_type) - self.assertEqual(200, response.status_code) - - response = self.get_json('/baymodels/%s' % self.baymodel.uuid) - self.assertIsNone(response['dns_nameserver']) - # Assert nothing else was changed - self.assertEqual(self.baymodel.uuid, response['uuid']) - self.assertEqual(self.baymodel.name, response['name']) - self.assertEqual(self.baymodel.apiserver_port, - response['apiserver_port']) - self.assertEqual(self.baymodel.image_id, - response['image_id']) - self.assertEqual(self.baymodel.fixed_network, - response['fixed_network']) - self.assertEqual(self.baymodel.network_driver, - response['network_driver']) - self.assertEqual(self.baymodel.volume_driver, - response['volume_driver']) - self.assertEqual(self.baymodel.docker_volume_size, - response['docker_volume_size']) - self.assertEqual(self.baymodel.coe, response['coe']) - self.assertEqual(self.baymodel.http_proxy, response['http_proxy']) - self.assertEqual(self.baymodel.https_proxy, response['https_proxy']) - self.assertEqual(self.baymodel.no_proxy, response['no_proxy']) - self.assertEqual(self.baymodel.labels, response['labels']) - - def test_remove_non_existent_property_fail(self): - response = self.patch_json('/baymodels/%s' % self.baymodel.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']) - - def test_remove_mandatory_property_fail(self): - mandatory_properties = ('/image_id', '/keypair_id', '/coe', - '/external_network_id', '/server_type', - '/tls_disabled', '/public', - '/registry_enabled', - '/cluster_distro', '/network_driver') - for p in mandatory_properties: - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': p, 'op': 'remove'}], - expect_errors=True) - self.assertEqual('application/json', response.content_type) - self.assertEqual(400, response.status_code) - self.assertTrue(response.json['errors']) - - def test_add_root_non_existent(self): - response = self.patch_json( - '/baymodels/%s' % self.baymodel.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']) - - def test_remove_uuid(self): - response = self.patch_json('/baymodels/%s' % self.baymodel.uuid, - [{'path': '/uuid', 'op': 'remove'}], - expect_errors=True) - self.assertEqual(400, response.status_int) - self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['errors']) - - -class TestPost(api_base.FunctionalTest): - - def setUp(self): - super(TestPost, self).setUp() - p = mock.patch.object(attr_validator, 'validate_os_resources') - self.mock_valid_os_res = p.start() - self.addCleanup(p.stop) - - @mock.patch('magnum.api.attr_validator.validate_image') - @mock.patch('oslo_utils.timeutils.utcnow') - def test_create_baymodel(self, mock_utcnow, - mock_image_data): - bdict = apiutils.baymodel_post_data() - test_time = datetime.datetime(2000, 1, 1, 0, 0) - mock_utcnow.return_value = test_time - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - - response = self.post_json('/baymodels', bdict) - self.assertEqual(201, response.status_int) - # Check location header - self.assertIsNotNone(response.location) - expected_location = '/v1/baymodels/%s' % bdict['uuid'] - self.assertEqual(expected_location, - urlparse.urlparse(response.location).path) - self.assertEqual(bdict['uuid'], 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) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_set_project_id_and_user_id(self, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - self.post_json('/baymodels', bdict) - cc_mock.assert_called_once_with(mock.ANY) - self.assertEqual(self.context.project_id, - cc_mock.call_args[0][0]['project_id']) - self.assertEqual(self.context.user_id, - cc_mock.call_args[0][0]['user_id']) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_doesnt_contain_id(self, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(image_id='my-image') - response = self.post_json('/baymodels', bdict) - self.assertEqual(bdict['image_id'], response.json['image_id']) - 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 _create_baymodel_raises_app_error(self, **kwargs): - # Create mock for db and image data - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock,\ - mock.patch('magnum.api.attr_validator.validate_image')\ - as mock_image_data: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(**kwargs) - self.assertRaises(AppError, self.post_json, '/baymodels', bdict) - self.assertFalse(cc_mock.called) - - def test_create_baymodel_with_invalid_long_string(self): - fields = ["uuid", "name", "image_id", "flavor_id", "master_flavor_id", - "dns_nameserver", "keypair_id", "external_network_id", - "cluster_distro", "fixed_network", "apiserver_port", - "docker_volume_size", "http_proxy", "https_proxy", - "no_proxy", "network_driver", "labels", "volume_driver"] - for field in fields: - self._create_baymodel_raises_app_error(**{field: 'i' * 256}) - - def test_create_baymodel_with_invalid_empty_string(self): - fields = ["uuid", "name", "image_id", "flavor_id", "master_flavor_id", - "dns_nameserver", "keypair_id", "external_network_id", - "cluster_distro", "fixed_network", "apiserver_port", - "docker_volume_size", "labels", "http_proxy", "https_proxy", - "no_proxy", "network_driver", "volume_driver", "coe"] - for field in fields: - self._create_baymodel_raises_app_error(**{field: ''}) - - def test_create_baymodel_with_invalid_coe(self): - self._create_baymodel_raises_app_error(coe='k8s') - self._create_baymodel_raises_app_error(coe='storm') - self._create_baymodel_raises_app_error(coe='meson') - self._create_baymodel_raises_app_error(coe='osomatsu') - - def test_create_baymodel_with_invalid_docker_volume_size(self): - self._create_baymodel_raises_app_error(docker_volume_size=-1) - self._create_baymodel_raises_app_error( - docker_volume_size=1, - docker_storage_driver="devicemapper") - self._create_baymodel_raises_app_error( - docker_volume_size=2, - docker_storage_driver="devicemapper") - self._create_baymodel_raises_app_error(docker_volume_size='notanint') - - def test_create_baymodel_with_invalid_dns_nameserver(self): - self._create_baymodel_raises_app_error(dns_nameserver='1.1.2') - self._create_baymodel_raises_app_error(dns_nameserver='1.1..1') - self._create_baymodel_raises_app_error(dns_nameserver='openstack.org') - - def test_create_baymodel_with_invalid_apiserver_port(self): - self._create_baymodel_raises_app_error(apiserver_port=-12) - self._create_baymodel_raises_app_error(apiserver_port=65536) - self._create_baymodel_raises_app_error(apiserver_port=0) - self._create_baymodel_raises_app_error(apiserver_port=1023) - self._create_baymodel_raises_app_error(apiserver_port='not an int') - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_labels(self, mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(labels={'key1': 'val1', - 'key2': 'val2'}) - response = self.post_json('/baymodels', bdict) - self.assertEqual(bdict['labels'], - response.json['labels']) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_docker_volume_size(self, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(docker_volume_size=99) - response = self.post_json('/baymodels', bdict) - self.assertEqual(bdict['docker_volume_size'], - response.json['docker_volume_size']) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_overlay(self, mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data( - docker_volume_size=1, docker_storage_driver="overlay") - response = self.post_json('/baymodels', bdict) - self.assertEqual(bdict['docker_volume_size'], - response.json['docker_volume_size']) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_generate_uuid(self, - mock_image_data): - # TODO(hongbin): Is this test correct? - pass - - @mock.patch('magnum.api.attr_validator.validate_image') - def _test_create_baymodel_network_driver_attr(self, - baymodel_dict, - baymodel_config_dict, - expect_errors, - mock_image_data): - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - for k, v in baymodel_config_dict.items(): - CONF.set_override(k, v, 'cluster_template') - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - bdict = apiutils.baymodel_post_data(**baymodel_dict) - response = self.post_json('/baymodels', bdict, - expect_errors=expect_errors) - if expect_errors: - self.assertEqual(400, response.status_int) - else: - expected_driver = bdict.get('network_driver') - if not expected_driver: - expected_driver = ( - CONF.cluster_template.swarm_default_network_driver) - self.assertEqual(expected_driver, - response.json['network_driver']) - self.assertEqual(bdict['image_id'], - response.json['image_id']) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - self.assertTrue(uuidutils.is_uuid_like(response.json['uuid'])) - - def test_create_baymodel_with_network_driver(self): - baymodel_dict = {'coe': 'kubernetes', 'network_driver': 'flannel'} - config_dict = {} # Default config - expect_errors_flag = False - self._test_create_baymodel_network_driver_attr(baymodel_dict, - config_dict, - expect_errors_flag) - - def test_create_baymodel_with_no_network_driver(self): - baymodel_dict = {} - config_dict = {} - expect_errors_flag = False - self._test_create_baymodel_network_driver_attr(baymodel_dict, - config_dict, - expect_errors_flag) - - def test_create_baymodel_with_network_driver_non_def_config(self): - baymodel_dict = {'coe': 'kubernetes', 'network_driver': 'flannel'} - config_dict = { - 'kubernetes_allowed_network_drivers': ['flannel', 'foo']} - expect_errors_flag = False - self._test_create_baymodel_network_driver_attr(baymodel_dict, - config_dict, - expect_errors_flag) - - def test_create_baymodel_with_invalid_network_driver(self): - baymodel_dict = {'coe': 'kubernetes', 'network_driver': 'bad_driver'} - config_dict = { - 'kubernetes_allowed_network_drivers': ['flannel', 'good_driver']} - expect_errors_flag = True - self._test_create_baymodel_network_driver_attr(baymodel_dict, - config_dict, - expect_errors_flag) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_volume_driver(self, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(volume_driver='rexray') - response = self.post_json('/baymodels', bdict) - self.assertEqual(bdict['volume_driver'], - response.json['volume_driver']) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_no_volume_driver(self, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict) - self.assertEqual(bdict['volume_driver'], - response.json['volume_driver']) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - - @mock.patch('magnum.api.attr_validator.validate_image') - @mock.patch.object(magnum_policy, 'enforce') - def test_create_baymodel_public_success(self, mock_policy, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_policy.return_value = True - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(public=True) - response = self.post_json('/baymodels', bdict) - self.assertTrue(response.json['public']) - mock_policy.assert_called_with(mock.ANY, "baymodel:publish", - None, do_raise=False) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - self.assertTrue(cc_mock.call_args[0][0]['public']) - - @mock.patch('magnum.api.attr_validator.validate_image') - @mock.patch.object(magnum_policy, 'enforce') - def test_create_baymodel_public_fail(self, mock_policy, - mock_image_data): - with mock.patch.object(self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template): - # make policy enforcement fail - mock_policy.return_value = False - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(public=True) - self.assertRaises(AppError, self.post_json, '/baymodels', bdict) - - @mock.patch('magnum.api.attr_validator.validate_image') - @mock.patch.object(magnum_policy, 'enforce') - def test_create_baymodel_public_not_set(self, mock_policy, - mock_image_data): - with mock.patch.object( - self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template) as cc_mock: - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data(public=False) - response = self.post_json('/baymodels', bdict) - self.assertFalse(response.json['public']) - # policy enforcement is called only once for enforce_wsgi - self.assertEqual(1, mock_policy.call_count) - cc_mock.assert_called_once_with(mock.ANY) - self.assertNotIn('id', cc_mock.call_args[0][0]) - self.assertFalse(cc_mock.call_args[0][0]['public']) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_no_os_distro_image(self, - mock_image_data): - mock_image_data.side_effect = exception.OSDistroFieldNotFound('img') - bdict = apiutils.baymodel_post_data() - del bdict['uuid'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(400, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_os_distro_image(self, - mock_image_data): - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - del bdict['uuid'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(201, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_image_name(self, - mock_image_data): - mock_image = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - mock_image_data.return_value = mock_image - bdict = apiutils.baymodel_post_data() - del bdict['uuid'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(201, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_no_exist_image_name(self, - mock_image_data): - mock_image_data.side_effect = exception.ResourceNotFound('test-img') - bdict = apiutils.baymodel_post_data() - del bdict['uuid'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(404, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_multi_image_name(self, - mock_image_data): - mock_image_data.side_effect = exception.Conflict('Multiple images') - bdict = apiutils.baymodel_post_data() - del bdict['uuid'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(409, response.status_int) - - def test_create_baymodel_without_image_id(self): - bdict = apiutils.baymodel_post_data() - del bdict['image_id'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(400, response.status_int) - - def test_create_baymodel_without_keypair_id(self): - bdict = apiutils.baymodel_post_data() - del bdict['keypair_id'] - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(400, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_dns(self, - mock_image_data): - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict) - self.assertEqual(201, response.status_int) - self.assertEqual(bdict['dns_nameserver'], - response.json['dns_nameserver']) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_no_exist_keypair(self, - mock_image_data): - self.mock_valid_os_res.side_effect = exception.KeyPairNotFound("Test") - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(404, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_flavor(self, - mock_image_data): - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict) - self.assertEqual(201, response.status_int) - self.assertEqual(bdict['flavor_id'], - response.json['flavor_id']) - self.assertEqual(bdict['master_flavor_id'], - response.json['master_flavor_id']) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_no_exist_flavor(self, - mock_image_data): - self.mock_valid_os_res.side_effect = exception.FlavorNotFound("flavor") - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(400, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_external_network(self, - mock_image_data): - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict) - self.assertEqual(201, response.status_int) - self.assertEqual(bdict['external_network_id'], - response.json['external_network_id']) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_with_no_exist_external_network(self, - mock_image_data): - self.mock_valid_os_res.side_effect = exception.ExternalNetworkNotFound( - "test") - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - response = self.post_json('/baymodels', bdict, expect_errors=True) - self.assertEqual(400, response.status_int) - - @mock.patch('magnum.api.attr_validator.validate_image') - def test_create_baymodel_without_name(self, mock_image_data): - with mock.patch.object(self.dbapi, 'create_cluster_template', - wraps=self.dbapi.create_cluster_template): - mock_image_data.return_value = {'name': 'mock_name', - 'os_distro': 'fedora-atomic'} - bdict = apiutils.baymodel_post_data() - bdict.pop('name') - resp = self.post_json('/baymodels', bdict) - self.assertEqual(201, resp.status_int) - self.assertIsNotNone(resp.json['name']) - - -class TestDelete(api_base.FunctionalTest): - - def test_delete_baymodel(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - self.delete('/baymodels/%s' % baymodel.uuid) - response = self.get_json('/baymodels/%s' % baymodel.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_baymodel_with_bay(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - obj_utils.create_test_cluster(self.context, - cluster_template_id=baymodel.uuid) - response = self.delete('/baymodels/%s' % baymodel.uuid, - expect_errors=True) - self.assertEqual(400, response.status_int) - self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['errors']) - self.assertIn(baymodel.uuid, response.json['errors'][0]['detail']) - - def test_delete_baymodel_not_found(self): - uuid = uuidutils.generate_uuid() - response = self.delete('/baymodels/%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_baymodel_with_name(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - response = self.delete('/baymodels/%s' % baymodel['name'], - expect_errors=True) - self.assertEqual(204, response.status_int) - - def test_delete_baymodel_with_name_not_found(self): - response = self.delete('/baymodels/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_multiple_baymodel_by_name(self): - obj_utils.create_test_cluster_template( - self.context, name='test_baymodel', uuid=uuidutils.generate_uuid()) - obj_utils.create_test_cluster_template( - self.context, name='test_baymodel', uuid=uuidutils.generate_uuid()) - response = self.delete('/baymodels/test_baymodel', expect_errors=True) - self.assertEqual(409, response.status_int) - self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['errors']) - - -class TestBayModelPolicyEnforcement(api_base.FunctionalTest): - - 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( - "baymodel:get_all", self.get_json, '/baymodels', - expect_errors=True) - - def test_policy_disallow_get_one(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - self._common_policy_check( - "baymodel:get", self.get_json, - '/baymodels/%s' % baymodel.uuid, - expect_errors=True) - - def test_policy_disallow_detail(self): - self._common_policy_check( - "baymodel:detail", self.get_json, - '/baymodels/%s/detail' % uuidutils.generate_uuid(), - expect_errors=True) - - def test_policy_disallow_update(self): - baymodel = obj_utils.create_test_cluster_template( - self.context, - name='example_A', - uuid=uuidutils.generate_uuid()) - self._common_policy_check( - "baymodel:update", self.patch_json, - '/baymodels/%s' % baymodel.name, - [{'path': '/name', 'value': "new_name", 'op': 'replace'}], - expect_errors=True) - - def test_policy_disallow_create(self): - bdict = apiutils.baymodel_post_data(name='bay_model_example_A') - self._common_policy_check( - "baymodel:create", self.post_json, '/baymodels', bdict, - expect_errors=True) - - def test_policy_disallow_delete(self): - baymodel = obj_utils.create_test_cluster_template(self.context) - self._common_policy_check( - "baymodel:delete", self.delete, - '/baymodels/%s' % baymodel.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): - baymodel = obj_utils.create_test_cluster_template(self.context, - user_id='another') - self._owner_check("baymodel:get", self.get_json, - '/baymodels/%s' % baymodel.uuid, expect_errors=True) - - def test_policy_only_owner_update(self): - baymodel = obj_utils.create_test_cluster_template(self.context, - user_id='another') - self._owner_check( - "baymodel:update", self.patch_json, - '/baymodels/%s' % baymodel.uuid, - [{'path': '/name', 'value': "new_name", 'op': 'replace'}], - expect_errors=True) - - def test_policy_only_owner_delete(self): - baymodel = obj_utils.create_test_cluster_template(self.context, - user_id='another') - self._owner_check( - "baymodel:delete", self.delete, '/baymodels/%s' % baymodel.uuid, - expect_errors=True) diff --git a/magnum/tests/unit/api/controllers/v1/test_certificate.py b/magnum/tests/unit/api/controllers/v1/test_certificate.py index 02fcfb40a2..77f27d4e79 100644 --- a/magnum/tests/unit/api/controllers/v1/test_certificate.py +++ b/magnum/tests/unit/api/controllers/v1/test_certificate.py @@ -62,8 +62,6 @@ class TestGetCaCertificate(api_base.FunctionalTest): headers=HEADERS) 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['pem'], response['pem']) @@ -77,8 +75,6 @@ class TestGetCaCertificate(api_base.FunctionalTest): headers=HEADERS) 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['pem'], response['pem']) @@ -149,23 +145,6 @@ class TestPost(api_base.FunctionalTest): self.assertEqual(201, response.status_int) self.assertEqual(new_cert['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']) def test_create_cert_by_cluster_name(self, ): diff --git a/magnum/tests/unit/api/controllers/v1/test_cluster.py b/magnum/tests/unit/api/controllers/v1/test_cluster.py index 016f8cc173..3cf0767f10 100755 --- a/magnum/tests/unit/api/controllers/v1/test_cluster.py +++ b/magnum/tests/unit/api/controllers/v1/test_cluster.py @@ -49,7 +49,7 @@ class TestClusterObject(base.TestCase): cluster.cluster_template_id = wtypes.Unset 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 = api_cluster.Cluster(**cluster_dict) self.assertEqual(15, cluster.create_timeout) diff --git a/magnum/tests/unit/api/utils.py b/magnum/tests/unit/api/utils.py index 73cfdcb589..b9b3fc85e0 100644 --- a/magnum/tests/unit/api/utils.py +++ b/magnum/tests/unit/api/utils.py @@ -16,8 +16,6 @@ import datetime 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_template as cluster_tmp_ctrl 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} -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): cluster_template = utils.get_test_cluster_template(**kw) internal = cluster_tmp_ctrl.ClusterTemplatePatchType.internal_attrs() 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): kw.update({'for_api_use': True}) cluster = utils.get_test_cluster(**kw)