目前主要还是基于H版的OpenStack,因此Tempest也是H版,对比了下最新社区Tempest,已经有很多实现的改进,连最基础的create_server的方法也优化了,特别是传入的networks,还会先通过方法tenant_network来获取,而目前使用的H版的Tempest默认依旧无网卡创建,除了重写方法外,如果不做任何修改,很多用例都没办法直接通过,加上在用的OpenStack也做了相当多定制化的开发,很多地方已经面目全非,都需要完善
首先看一个testcase
lihui@MacBook ~/work/cloud/openstack/tempest-ci/tempest/api/compute/admin master ●✚ nosetests -sv test_fixed_ips.py ERROR ====================================================================== ERROR: test suite for <class 'tempest.api.compute.admin.test_fixed_ips.fixedipstestjson'=""> ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/local/lib/python2.7/site-packages/nose-1.3.7-py2.7.egg/nose/suite.py", line 209, in run self.setUp() File "/usr/local/lib/python2.7/site-packages/nose-1.3.7-py2.7.egg/nose/suite.py", line 292, in setUp self.setupContext(ancestor) File "/usr/local/lib/python2.7/site-packages/nose-1.3.7-py2.7.egg/nose/suite.py", line 315, in setupContext try_run(context, names) File "/usr/local/lib/python2.7/site-packages/nose-1.3.7-py2.7.egg/nose/util.py", line 471, in try_run return func() File "/Users/lihui/work/cloud/openstack/tempest-ci/tempest/api/compute/admin/test_fixed_ips.py", line 29, in setUpClass raise cls.skipException(msg) SkipTest: FixedIPsTestJson skipped as neutron is available ---------------------------------------------------------------------- Ran 0 tests in 0.011s
SetUp的时候直接就出错了,报错的地方在
class FixedIPsTestJson(base.BaseV2ComputeAdminTest): _interface = 'json' @classmethod def setUpClass(cls): if cls.config.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg)
也就是令人费解的if cls.config.service_available.neutron:返回的为真,也就是配置文件里配置neutron可用,查看配置文件
[service_available] # Whether or not cinder is expected to be available cinder = True # Whether or not neutron is expected to be available neutron = True # Whether or not glance is expected to be available glance = True # Whether or not swift is expected to be available swift = False # Whether or not nova is expected to be available nova = True # Whether or not Heat is expected to be available heat = False # Whether or not Ceilometer is expected to be available ceilometer = False
的确neutron配置的是可用,一脸懵逼,neutron使能和测试fixed ip有什么关系呢,直接看整个python文件
from tempest.api.compute import base from tempest.test import attr class FixedIPsTestJson(base.BaseV2ComputeAdminTest): _interface = 'json' @classmethod def setUpClass(cls): if cls.config.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg) super(FixedIPsTestJson, cls).setUpClass() cls.client = cls.os_adm.fixed_ips_client resp, server = cls.create_test_server(wait_until='ACTIVE') resp, server = cls.servers_client.get_server(server['id']) for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break @attr(type='gate') def test_list_fixed_ip_details(self): resp, fixed_ip = self.client.get_fixed_ip_details(self.ip) self.assertEqual(fixed_ip['address'], self.ip) @attr(type='gate') def test_set_reserve(self): body = {"reserve": "None"} resp, body = self.client.reserve_fixed_ip(self.ip, body) self.assertEqual(resp.status, 202) @attr(type='gate') def test_set_unreserve(self): body = {"unreserve": "None"} resp, body = self.client.reserve_fixed_ip(self.ip, body) self.assertEqual(resp.status, 202)
除了setUp的时候,创建了一个VM,然后获取IP,其它三个testcase全部都是将这个IP传入,来验证接口正确性,看到这里,又看到了上次被坑的不带网络创建虚拟机的方法create_test_server,再看看下面三个test case,分别调用的方法基本也是计算服务
class FixedIPsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FixedIPsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_fixed_ip_details(self, fixed_ip): url = "os-fixed-ips/%s" % (fixed_ip) resp, body = self.get(url) body = json.loads(body) return resp, body['fixed_ip'] def reserve_fixed_ip(self, ip, body): """This reserves and unreserves fixed ips.""" url = "os-fixed-ips/%s/action" % (ip) resp, body = self.post(url, json.dumps(body), self.headers) return resp, body
上次HTTP RESTful那篇有写,URL前面的是根据__init__里的self.service来定,而这里是从self.config.compute服务配置传过来的,也就是nova服务,和neutron也没半毛钱关系,完全不清楚这个test case要disable掉neutron作用是什么,有可能在后面哪里会用到,具体以后再看
这里眼下问题是解决case block,但是要认清这里不仅仅是neutron配置一个问题,还有个创建VM后返回fix ip,而本身create_test_server是不带网络创建的,特别由于我不太清楚tempest其它地方会不会需要不带网络,因为毕竟它这样设计,因此也不好直接将这个方法强行添加一个net-id来创建
但是最新社区这个方法是这么写的
@classmethod def create_test_server(cls, validatable=False, volume_backed=False, **kwargs): """Wrapper utility that returns a test server. This wrapper utility calls the common create test server and returns a test server. The purpose of this wrapper is to minimize the impact on the code of the tests already using this function. :param validatable: Whether the server will be pingable or sshable. :param volume_backed: Whether the instance is volume backed or not. """ if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name(cls.__name__ + "-server") tenant_network = cls.get_tenant_network() body, servers = compute.create_test_server( cls.os, validatable, validation_resources=cls.validation_resources, tenant_network=tenant_network, volume_backed=volume_backed, **kwargs) cls.servers.extend(servers) return body
可以看到这里传入了一个tenant_network,是通过cls.get_tenant_network()获取,猜测应该会根据具体租户网络情况,做测试的总喜欢穷追问底,继续看
@classmethod def get_tenant_network(cls, credentials_type='primary'): """Get the network to be used in testing :param credentials_type: The type of credentials for which to get the tenant network :return: network dict including 'id' and 'name' """ # Get a manager for the given credentials_type, but at least # always fall back on getting the manager for primary credentials if isinstance(credentials_type, six.string_types): manager = cls.get_client_manager(credential_type=credentials_type) elif isinstance(credentials_type, list): manager = cls.get_client_manager(roles=credentials_type[1:]) else: manager = cls.get_client_manager() # Make sure cred_provider exists and get a network client networks_client = manager.compute_networks_client cred_provider = cls._get_credentials_provider() # In case of nova network, isolated tenants are not able to list the # network configured in fixed_network_name, even if they can use it # for their servers, so using an admin network client to validate # the network name if (not CONF.service_available.neutron and credentials.is_admin_available( identity_version=cls.get_identity_version())): admin_creds = cred_provider.get_admin_creds() admin_manager = clients.Manager(admin_creds.credentials) networks_client = admin_manager.compute_networks_client return fixed_network.get_tenant_network( cred_provider, networks_client, CONF.compute.fixed_network_name)
看到这里,半脸懵逼,但是中间的注释深深地吸引了我,我已经不关心这个方法的实现了,而是nova network一下子让我有了无心插柳的感觉,我目前所有case全部都是compute服务接口,根本就没有neutron接口,那么上面service_available.neutron设置为disabled的原因,大概就是想走nova-network这个模式,而猜测如果不disabled的话,可能先走的neutron,而无法验证nova这个compute服务正确性,先确认一下
lihui@MacBook ~ nova help | grep network add-fixed-ip Add new IP address on a network to server. interface-attach Attach a network interface to a server. interface-detach Detach a network interface from a server. network-associate-host Associate host with network. network-associate-project Associate project with network. network-create Create a network. network-disassociate network. network-list Print a list of available networks. network-show Show details about the given network. reset-network Reset network of a server. Add a network interface to a baremetal node. List network interfaces associated with a baremetal Remove a network interface from a baremetal node. net Show a network net-create Create a network net-delete Delete a network net-list List networks lihui@MacBook ~ lihui@MacBook ~ nova net-list +--------------------------------------+------------------------------------------+------+ | ID | Label | CIDR | +--------------------------------------+------------------------------------------+------+ | 1e0801ae-7ee4-4fdb-bea6-80c14d2ea202 | public_admin_1 | - | | 3c645e48-43b9-46ac-bd24-3c841e09c31d | public_admin | - | | ea69e216-ba00-47a7-8cb9-0d9c02c0e34d | private_66daf0ccaf3a41d383c24f4080c41f71 | - | +--------------------------------------+------------------------------------------+------+ lihui@MacBook ~ lihui@MacBook ~ neutron net-list +--------------------------------------+------------------------------------------+-------------------------------------------------------+ | id | name | subnets | +--------------------------------------+------------------------------------------+-------------------------------------------------------+ | 1e0801ae-7ee4-4fdb-bea6-80c14d2ea202 | public_admin_1 | 59f2086d-2394-46b2-a1ed-1f8282f6d3e9 60.191.82.0/24 | | 3c645e48-43b9-46ac-bd24-3c841e09c31d | public_admin | f92163ef-86e1-40ac-a999-4bb27084a815 115.236.127.0/24 | | ea69e216-ba00-47a7-8cb9-0d9c02c0e34d | private_66daf0ccaf3a41d383c24f4080c41f71 | 816f12d2-be98-4569-8ddd-b680cb1f6e50 10.180.80.0/23 | +--------------------------------------+------------------------------------------+-------------------------------------------------------+
果然,nova-network和neutron就接口而言,是完全一致的,最早的OpenStack是全部集成在nova服务当中,包括网络,neutron也是后来才分开的,如此一来就说得通了
可是,这还是无济于事,因为目前H版Tempest创建VM这个方法network就是为空,为了不破坏当前的结构,我这里加一个条件fix这个case
先捋一下整体思路,现在的问题是create_server里,创建的VM不带网络,但是实现里有这么一段
for option in ['personality', 'adminPass', 'key_name', #'security_groups', 'networks', 'user_data', 'availability_zone', 'accessIPv4', 'accessIPv6', 'min_count', 'max_count', ('metadata', 'meta'), ('OS-DCF:diskConfig', 'disk_config'), 'return_reservation_id']: if isinstance(option, tuple): post_param = option[0] key = option[1] else: post_param = option key = option value = kwargs.get(key) if value is not None: post_body[post_param] = value
看到option是可以遍历到networks的,只不过我们传入的networks的value为空,导致不会存入post_body里,因此我们的目标就是将network uuid穿进去就行了,也就是传到kwargs里,回头看下调用的地方
@classmethod def create_test_server(cls, **kwargs): """Wrapper utility that returns a test server.""" name = data_utils.rand_name(cls.__name__ + "-instance") if 'name' in kwargs: name = kwargs.pop('name') flavor = kwargs.get('flavor', cls.flavor_ref) image_id = kwargs.get('image_id', cls.image_ref) resp, body = cls.servers_client.create_server( name, image_id, flavor, **kwargs)
kwargs没做删减,只有pop,因此,继续看create_test_server调用的地方,也就是最开始
resp, server = cls.create_test_server(wait_until='ACTIVE')
这就太简单了,直接再添加一个networks=xxxx的参数进去,就行了,而这个参数值最好是写在配置文件当中,也就是tempest.conf里,由于这个版本还没有tenant_network方法,目前就直接配置进去,这样,整体思路就清晰了
tempest.conf => private_network_id => kwargs => create_test_server => kwargs => create_server
首先将neutron disabled
[service_available] # Whether or not cinder is expected to be available cinder = True # Whether or not neutron is expected to be available neutron = False # Whether or not glance is expected to be available glance = True # Whether or not swift is expected to be available swift = False # Whether or not nova is expected to be available nova = True # Whether or not Heat is expected to be available
然后配置private_network_id,注意要写在[network]里
# Id of the private network that provides external connectivity. private_network_id = 64916627-9d23-4b37-adcf-8cc0f407260b
此时并没有完,还没发读取,具体涉及到oslo_config的实现,下次有空研究下,刚刚就少修改了下面这处地方,在NetworkGroup里面
cfg.StrOpt('private_network_id', default="", help="Id of the private network that provides external " "connectivity"),
base.py里添加成员变量
class BaseComputeTest(tempest.test.BaseTestCase): """Base test case class for all Compute API tests.""" force_tenant_isolation = False @classmethod def setUpClass(cls): super(BaseComputeTest, cls).setUpClass() if not cls.config.service_available.nova: skip_msg = ("%s skipped as nova is not available" % cls.__name__) raise cls.skipException(skip_msg) os = cls.get_client_manager() cls.os = os cls.build_interval = cls.config.compute.build_interval cls.build_timeout = cls.config.compute.build_timeout cls.ssh_user = cls.config.compute.ssh_user cls.image_ssh_user = cls.config.compute.image_ssh_user cls.image_ssh_password = cls.config.compute.image_ssh_password cls.image_alt_ssh_user = cls.config.compute.image_alt_ssh_user cls.image_alt_ssh_password = cls.config.compute.image_alt_ssh_password cls.image_ref = cls.config.compute.image_ref cls.image_ref_alt = cls.config.compute.image_ref_alt cls.flavor_ref = cls.config.compute.flavor_ref cls.private_network_id = cls.config.network.private_network_id cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt cls.servers = [] cls.images = [] cls.multi_user = cls.get_multi_user()
做了这么多工作,只是为了让上面这个类派生出来的类能够直接获取成员变量
最后在我们的test case里创建VM的时候,添加一个network id参数
class FixedIPsTestJson(base.BaseV2ComputeAdminTest): _interface = 'json' @classmethod def setUpClass(cls): if cls.config.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg) super(FixedIPsTestJson, cls).setUpClass() cls.client = cls.os_adm.fixed_ips_client resp, server = cls.create_test_server(networks=[{'uuid': cls.private_network_id}], wait_until='ACTIVE') resp, server = cls.servers_client.get_server(server['id']) for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break
通过这几步,就成功将network id传进去了,而且没有修改已有的创建VM的方法
执行以下test case,成功带网络创建,但是接口还是失败,返回的是404
nosetests -sv test_fixed_ips.py:FixedIPsTestJson.test_list_fixed_ip_details tempest.api.compute.admin.test_fixed_ips.FixedIPsTestJson.test_list_fixed_ip_details ... FAIL ====================================================================== FAIL: tempest.api.compute.admin.test_fixed_ips.FixedIPsTestJson.test_list_fixed_ip_details ----------------------------------------------------------------------
TRACE日志找不到
Traceback (most recent call last): File "/Users/lihui/work/cloud/openstack/tempest-ci/tempest/api/compute/admin/test_fixed_ips.py", line 45, in test_list_fixed_ip_details resp, fixed_ip = self.client.get_fixed_ip_details(self.ip) File "/Users/lihui/work/cloud/openstack/tempest-ci/tempest/services/compute/json/fixed_ips_client.py", line 32, in get_fixed_ip_details resp, body = self.get(url) File "/Users/lihui/work/cloud/openstack/tempest-ci/tempest/common/rest_client.py", line 320, in get return self.request('GET', url, headers) File "/Users/lihui/work/cloud/openstack/tempest-ci/tempest/common/rest_client.py", line 451, in request resp, resp_body) File "/Users/lihui/work/cloud/openstack/tempest-ci/tempest/common/rest_client.py", line 496, in _error_checker raise exceptions.NotFound(resp_body) tempest.exceptions.NotFound: Object not found Details: {"itemNotFound": {"message": "Fixed ip not found for address 10.180.80.24.", "code": 404}}
八成是这个接口已经不适用了,这里先到此为止,这个测试场景修改了一下打通了,以备后面其它用例也需要这种用法