From efd6f58a50f671d32c923ddffe8ce6740d8463f6 Mon Sep 17 00:00:00 2001 From: "oizaki.atsushi" Date: Mon, 8 Nov 2021 10:49:04 +0900 Subject: [PATCH 1/3] :sparkles: IF-5903 add bto baremetals api --- ecl/baremetal/v2/_proxy.py | 65 ++++++++++-- ecl/baremetal/v2/chassis.py | 65 ++++++++++++ ecl/baremetal/v2/server.py | 13 +++ .../functional/baremetal/test_chassis.py | 83 ++++++++++++++++ ecl/tests/functional/baremetal/test_server.py | 9 ++ ecl/tests/unit/baremetal/v2/test_charssis.py | 99 +++++++++++++++++++ 6 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 ecl/baremetal/v2/chassis.py create mode 100644 ecl/tests/functional/baremetal/test_chassis.py create mode 100644 ecl/tests/unit/baremetal/v2/test_charssis.py diff --git a/ecl/baremetal/v2/_proxy.py b/ecl/baremetal/v2/_proxy.py index 89b1d87..dfba9d1 100755 --- a/ecl/baremetal/v2/_proxy.py +++ b/ecl/baremetal/v2/_proxy.py @@ -21,6 +21,7 @@ from ecl.baremetal.v2 import stock as _stock from ecl.baremetal.v2 import nic_physical_port as _port from ecl.baremetal.v2 import server as _server +from ecl.baremetal.v2 import chassis as _chassis from ecl.baremetal import version as _version from ecl import proxy2 from ecl import session @@ -286,9 +287,9 @@ def find_server(self, name_or_id, ignore_missing=False): """ return self._find(_server.Server, name_or_id, ignore_missing=ignore_missing) - def create_server(self, name, networks, flavor, admin_pass=None, - image=None, key_name=None, availability_zone=None, - user_data=None, raid_arrays=None, + def create_server(self, name, networks, admin_pass=None, image=None, + flavor=None, chassis_id=None, key_name=None, + availability_zone=None, user_data=None, raid_arrays=None, lvm_volume_groups=None, filesystems=None, metadata=None, personality=None): """This API create additional Baremetal server. @@ -297,11 +298,13 @@ def create_server(self, name, networks, flavor, admin_pass=None, :param array networks: Network Array. If it is specified greater than two, default gateway is first network. - :param string flavor: The flavor reference for the desired flavor for your - Baremetal server. :param string admin_pass: Password for the administrator. :param string image: The image reference for the desired image for your Baremetal server. + :param string flavor: The flavor reference for the desired flavor for your + Baremetal server. You can specify either flavor or chassis_id. + :param string chassis_id: The ID of chassis which you use to deploy + Baremetal server. You can specify either flavor or chassis_id. :param string key_name: SSH Keypair name you created on KeyPairs API. :param string availability_zone: The availability zone name in which to launch the server. @@ -318,11 +321,15 @@ def create_server(self, name, networks, flavor, admin_pass=None, esxi. :return: :class:`~ecl.baremetal.v2.server.Server` """ - attrs = {"name": name, "networks": networks, "flavorRef": flavor} + attrs = {"name": name, "networks": networks} if admin_pass: attrs["adminPass"] = admin_pass if image: attrs["imageRef"] = image + if flavor: + attrs["flavorRef"] = flavor + if chassis_id: + attrs["chassis_id"] = chassis_id if key_name: attrs["key_name"] = key_name if availability_zone: @@ -350,6 +357,17 @@ def delete_server(self, server_id): """ return self._delete(_server.Server, server_id) + def update_server(self, server_id, name): + """ This API updates the editable attributes of the specified baremetal server. + + :param string server_id: ID for the specified server. + :param string name: Name of your baremetal server as a string. + :return: :class:`~ecl.baremetal.v2.server.Server` + """ + attrs = {"name": name} + server = _server.Server() + return server.update(self.session, server_id, **attrs) + def start_server(self, server_id): """Power on the Baremetal Server associated with server_id. This request will be accepted only when the task_state is None. @@ -509,3 +527,38 @@ def update_metadata(self, server_id, key, **attr): """ metadata = _metadata.Metadata() return metadata.update(self.session, server_id, key, **attr) + + def chassis(self, details=True): + """Lists all Chassis or ChassisDetail. + + A chassis represents base object of baremetal server. + Each chassis is assigned an unique id and has dedicated disk spaces, + memory capacities and cpu resources. You can create baremetal server + upon this object. + + :param bool details: When set to ``False`` + :class:`~ecl.baremetal.v2.chassis.Chassis` instance + will be returned. The default, ``True``, will cause + :class:`~ecl.baremetal.v2.chassis.ChassisDetail` + instances to be returned. + + :return: A List of :class:`~ecl.baremetal.v2.chassis.Chassis` or + :class:`~ecl.baremetal.v2.chassis.ChassisDetail` + """ + chassis = _chassis.ChassisDetail if details else _chassis.Chassis + return list(self._list(chassis)) + + def get_chassis(self, chassis_id): + """Gets details for a ChassisDetail associated with chassis_id. + + A chassis represents base object of baremetal server. + Each chassis is assigned an unique id and has dedicated disk spaces, + memory capacities and cpu resources. You can create baremetal server + upon this object. + + :param string chassis_id: ID for the chassis. + :return: :class:`~ecl.baremetal.v2.chassis.Chassis` + """ + # Use "Chassis" instead of "ChassisDetail". + # Because "detail" is not included to the request path. + return self._get(_chassis.Chassis, chassis_id) diff --git a/ecl/baremetal/v2/chassis.py b/ecl/baremetal/v2/chassis.py new file mode 100644 index 0000000..becd782 --- /dev/null +++ b/ecl/baremetal/v2/chassis.py @@ -0,0 +1,65 @@ +# 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 ecl.baremetal import baremetal_service +from ecl import resource2 + + +class Chassis(resource2.Resource): + """Chassis Resource""" + + resource_key = 'chassis' + resources_key = 'chassis' + base_path = '/chassis' + service = baremetal_service.BaremetalService() + + # Capabilities + allow_get = True + allow_list = True + + # Properties + #: The ID for the chassis, which is a unique integer value. + id = resource2.Body('id') + #: The name of availability zone where chassis exists. + availability_zone = resource2.Body('availability_zone') + #: The name of flavor of chassis. + flavor_name = resource2.Body('flavor_name') + #: The object of summarized hardware spec. That has cpus, disks and rams. + hardware_summary = resource2.Body('hardware_summary', type=dict) + #: The status of chassis. + status = resource2.Body('status') + #: The ID of server attached to chassis. If no server is attached to chassis, the vlaue is null. + server_id = resource2.Body('server_id') + #: The name of server attached to chassis. If no server is attached to chassis, the value is null. + server_name = resource2.Body('server_name') + #: The minimum contract period of your chassis. + contract_year = resource2.Body('contract_year', type=int) + #: The date that you start to use the chassis. + start_time = resource2.Body('start_time') + + # Properties (for Detail) + #: The spec of all cpus installed in chassis. + cpus = resource2.Body('cpus', type=list) + #: The spec of all disks installed in chassis. + disks = resource2.Body('disks', type=list) + #: The spec of all rams installed in chassis. + rams = resource2.Body('rams', type=list) + + +class ChassisDetail(Chassis): + """ChassisDetail Resource""" + + base_path = '/chassis/detail' + + # Capabilities + allow_get = False + diff --git a/ecl/baremetal/v2/server.py b/ecl/baremetal/v2/server.py index 56b2316..412152a 100755 --- a/ecl/baremetal/v2/server.py +++ b/ecl/baremetal/v2/server.py @@ -26,6 +26,7 @@ class Server(resource2.Resource): allow_list = True allow_create = True allow_delete = True + allow_update = True # Properties #: UUID of the Baremetal server. @@ -107,6 +108,8 @@ class Server(resource2.Resource): def create(self, session, **attrs): body = {"server": attrs} + + print(body) resp = session.post( self.base_path, endpoint_filter=self.service, json=body, @@ -115,6 +118,16 @@ def create(self, session, **attrs): self._translate_response(resp, has_body=True) return self + def update(self, session, server_id, **attrs): + url = "%s/%s" % (self.base_path, server_id) + body = {"server": attrs} + resp = session.put(url, + endpoint_filter=self.service, + json=body, + headers={"Accept": "application/json"}) + self._translate_response(resp, has_body=True) + return self + class ServerDetail(Server): base_path = '/servers/detail' diff --git a/ecl/tests/functional/baremetal/test_chassis.py b/ecl/tests/functional/baremetal/test_chassis.py new file mode 100644 index 0000000..69d8472 --- /dev/null +++ b/ecl/tests/functional/baremetal/test_chassis.py @@ -0,0 +1,83 @@ +# 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 six + +from ecl.tests.functional import base + + +class TestChassis(base.BaseFunctionalTest): + + def test_chassis(self): + chassis_list = list(self.conn.baremetal.chassis(details=False)) + self.assertGreater(len(chassis_list), 0) + + for chassis in chassis_list: + print(chassis) + self.assertIsInstance(chassis.id, six.string_types) + self.assertIsInstance(chassis.availability_zone, six.string_types) + self.assertIsInstance(chassis.flavor_name, six.string_types) + self.assertIsInstance(chassis.hardware_summary, dict) + self.assertIsInstance(chassis.status, six.string_types) + self.assertIsInstance(chassis.server_id, + (six.string_types, type(None))) + self.assertIsInstance(chassis.server_name, + (six.string_types, type(None))) + self.assertIsInstance(chassis.contract_year, int) + self.assertIsInstance(chassis.start_time, six.string_types) + + def test_chassis_detail(self): + chassis_list = list(self.conn.baremetal.chassis(details=True)) + self.assertGreater(len(chassis_list), 0) + + for chassis in chassis_list: + print(chassis) + self.assertIsInstance(chassis.id, six.string_types) + self.assertIsInstance(chassis.availability_zone, six.string_types) + self.assertIsInstance(chassis.flavor_name, six.string_types) + self.assertIsInstance(chassis.hardware_summary, dict) + self.assertIsInstance(chassis.status, six.string_types) + self.assertIsInstance(chassis.server_id, + (six.string_types, type(None))) + self.assertIsInstance(chassis.server_name, + (six.string_types, type(None))) + self.assertIsInstance(chassis.contract_year, int) + self.assertIsInstance(chassis.start_time, six.string_types) + + self.assertIsInstance(chassis.cpus, list) + self.assertIsInstance(chassis.disks, list) + self.assertIsInstance(chassis.rams, list) + + def test_get_chassis(self): + # ChassisIDはMock Serverに合わせて変更する + chassis_id = '31e7a0c4-f49e-19e6-2192-c9f9cc6f5a74' + + chassis = self.conn.baremetal.get_chassis(chassis_id) + print(chassis) + # Mock Serverを使用するとランダムなUUIDが返却されるのでパスパラメータを一致しない + # self.assertEqual(chassis.id, chassis_id) + + self.assertIsInstance(chassis.availability_zone, six.string_types) + self.assertIsInstance(chassis.flavor_name, six.string_types) + self.assertIsInstance(chassis.hardware_summary, dict) + self.assertIsInstance(chassis.status, six.string_types) + self.assertIsInstance(chassis.server_id, + (six.string_types, type(None))) + self.assertIsInstance(chassis.server_name, + (six.string_types, type(None))) + self.assertIsInstance(chassis.contract_year, int) + self.assertIsInstance(chassis.start_time, six.string_types) + + self.assertIsInstance(chassis.cpus, list) + self.assertIsInstance(chassis.disks, list) + self.assertIsInstance(chassis.rams, list) + \ No newline at end of file diff --git a/ecl/tests/functional/baremetal/test_server.py b/ecl/tests/functional/baremetal/test_server.py index d8a7271..e2e011f 100755 --- a/ecl/tests/functional/baremetal/test_server.py +++ b/ecl/tests/functional/baremetal/test_server.py @@ -72,3 +72,12 @@ def test_04_show_server(self): def test_05_delete_server(self): server = self.conn.baremetal.delete_server("752aac2e-4b82-4d47-a7c7-xx") assert False + + def test_06_update_server(self): + # ServerIDはMock Serverに合わせて変更する + server_id = "3072a550-2ff4-a9d6-438b-0ce8b650eaa5" + name = "hoge" + + server = self.conn.baremetal.update_server(server_id, name) + print(server) + self.assertIsInstance(server.id, six.string_types) diff --git a/ecl/tests/unit/baremetal/v2/test_charssis.py b/ecl/tests/unit/baremetal/v2/test_charssis.py new file mode 100644 index 0000000..95e123d --- /dev/null +++ b/ecl/tests/unit/baremetal/v2/test_charssis.py @@ -0,0 +1,99 @@ +# 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 testtools + +from ecl.baremetal.v2 import chassis + +IDENTIFIER = 'IDENTIFIER' +CHASSIS_EXAMPLE = { + 'id': IDENTIFIER, + 'availability_zone': '2', + 'flavor_name': '3', + 'hardware_summary': {'4': 4}, + 'status': '5', + 'server_id': '6', + 'server_name': '7', + 'contract_year': 8, + 'start_time': '9', +} + +CHASSIS_DETAIL_EXAMPLE = { + 'id': IDENTIFIER, + 'availability_zone': '2', + 'flavor_name': '3', + 'hardware_summary': {'4': 4}, + 'status': '5', + 'server_id': '6', + 'server_name': '7', + 'contract_year': 8, + 'start_time': '9', + 'cpus': [{'10': 10},], + 'disks': [{'11': 11},], + 'rams': [{'12': 12},], +} +from unittest.mock import patch + +class TestChassis(testtools.TestCase): + + def test_basic(self): + sot = chassis.Chassis() + self.assertEqual('chassis', sot.resource_key) + self.assertEqual('chassis', sot.resources_key) + self.assertEqual('/chassis', sot.base_path) + self.assertEqual('baremetal-server', sot.service.service_type) + self.assertTrue(sot.allow_get) + self.assertTrue(sot.allow_list) + self.assertFalse(sot.allow_create) + self.assertFalse(sot.allow_delete) + self.assertFalse(sot.allow_update) + self.assertFalse(sot.allow_head) + + def test_make_basic(self): + sot = chassis.Chassis(**CHASSIS_EXAMPLE) + self.assertEqual(CHASSIS_EXAMPLE['id'], sot.id) + self.assertEqual(CHASSIS_EXAMPLE['availability_zone'], sot.availability_zone) + self.assertEqual(CHASSIS_EXAMPLE['flavor_name'], sot.flavor_name) + self.assertEqual(CHASSIS_EXAMPLE['hardware_summary'], sot.hardware_summary) + self.assertEqual(CHASSIS_EXAMPLE['status'], sot.status) + self.assertEqual(CHASSIS_EXAMPLE['server_id'], sot.server_id) + self.assertEqual(CHASSIS_EXAMPLE['server_name'], sot.server_name) + self.assertEqual(CHASSIS_EXAMPLE['contract_year'], sot.contract_year) + self.assertEqual(CHASSIS_EXAMPLE['start_time'], sot.start_time) + + def test_detail(self): + sot = chassis.ChassisDetail() + self.assertEqual('chassis', sot.resource_key) + self.assertEqual('chassis', sot.resources_key) + self.assertEqual('/chassis/detail', sot.base_path) + self.assertEqual('baremetal-server', sot.service.service_type) + self.assertFalse(sot.allow_get) + self.assertTrue(sot.allow_list) + self.assertFalse(sot.allow_create) + self.assertFalse(sot.allow_delete) + self.assertFalse(sot.allow_update) + self.assertFalse(sot.allow_head) + + def test_make_detail(self): + sot = chassis.ChassisDetail(**CHASSIS_DETAIL_EXAMPLE) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['id'], sot.id) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['availability_zone'], sot.availability_zone) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['flavor_name'], sot.flavor_name) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['hardware_summary'], sot.hardware_summary) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['status'], sot.status) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['server_id'], sot.server_id) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['server_name'], sot.server_name) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['contract_year'], sot.contract_year) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['start_time'], sot.start_time) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['cpus'], sot.cpus) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['disks'], sot.disks) + self.assertEqual(CHASSIS_DETAIL_EXAMPLE['rams'], sot.rams) From 6657446f4433972cfd150f070096ed62e856f993 Mon Sep 17 00:00:00 2001 From: "oizaki.atsushi" Date: Mon, 8 Nov 2021 17:25:28 +0900 Subject: [PATCH 2/3] :sparkles: IF-5903 add bto baremetals api --- ecl/baremetal/v2/chassis.py | 1 - ecl/baremetal/v2/server.py | 1 - 2 files changed, 2 deletions(-) diff --git a/ecl/baremetal/v2/chassis.py b/ecl/baremetal/v2/chassis.py index becd782..bf4e8fb 100644 --- a/ecl/baremetal/v2/chassis.py +++ b/ecl/baremetal/v2/chassis.py @@ -62,4 +62,3 @@ class ChassisDetail(Chassis): # Capabilities allow_get = False - diff --git a/ecl/baremetal/v2/server.py b/ecl/baremetal/v2/server.py index 412152a..80ce490 100755 --- a/ecl/baremetal/v2/server.py +++ b/ecl/baremetal/v2/server.py @@ -109,7 +109,6 @@ class Server(resource2.Resource): def create(self, session, **attrs): body = {"server": attrs} - print(body) resp = session.post( self.base_path, endpoint_filter=self.service, json=body, From dec0eeed6d5375d761ac3ed239d34f4a9b1031bf Mon Sep 17 00:00:00 2001 From: "oizaki.atsushi" Date: Tue, 9 Nov 2021 08:39:28 +0900 Subject: [PATCH 3/3] :sparkles: IF-5903 add bto baremetals api --- ecl/baremetal/v2/chassis.py | 2 +- ecl/baremetal/v2/server.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ecl/baremetal/v2/chassis.py b/ecl/baremetal/v2/chassis.py index bf4e8fb..713c61d 100644 --- a/ecl/baremetal/v2/chassis.py +++ b/ecl/baremetal/v2/chassis.py @@ -37,7 +37,7 @@ class Chassis(resource2.Resource): hardware_summary = resource2.Body('hardware_summary', type=dict) #: The status of chassis. status = resource2.Body('status') - #: The ID of server attached to chassis. If no server is attached to chassis, the vlaue is null. + #: The ID of server attached to chassis. If no server is attached to chassis, the value is null. server_id = resource2.Body('server_id') #: The name of server attached to chassis. If no server is attached to chassis, the value is null. server_name = resource2.Body('server_name') diff --git a/ecl/baremetal/v2/server.py b/ecl/baremetal/v2/server.py index 80ce490..77381de 100755 --- a/ecl/baremetal/v2/server.py +++ b/ecl/baremetal/v2/server.py @@ -108,7 +108,6 @@ class Server(resource2.Resource): def create(self, session, **attrs): body = {"server": attrs} - resp = session.post( self.base_path, endpoint_filter=self.service, json=body,