Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial openstack support #113

Merged
merged 15 commits into from
Mar 1, 2021
Merged

Initial openstack support #113

merged 15 commits into from
Mar 1, 2021

Conversation

TheRealFalcon
Copy link
Member

@TheRealFalcon TheRealFalcon commented Jan 30, 2021

At this point I think the basic functionality is usable and reviewable.

Everything in the base_api.py test works, so the basics all work. I intentionally didn't add an example since all clouds should share the same base API right now. We should really only need one example unless we want to showcase specialized use cases of a specific cloud.

@TheRealFalcon
Copy link
Member Author

I recently noticed that on the CLI, I don't need to provide the network information like I do through the API. I'm curious how the CLI knows which network to create the instance on and if we could use that same knowledge to acquire the network to use when using the API.

@TheRealFalcon
Copy link
Member Author

I'm also going on the assumption that we want a floating IP associated with instances that we launch. Obviously not every openstack deployment in the wild will require that, but I don't think that's something we need to worry about yet.

@TheRealFalcon TheRealFalcon removed the wip label Feb 8, 2021
@TheRealFalcon TheRealFalcon changed the title WIP: Initial openstack support Initial openstack support Feb 8, 2021
Copy link
Contributor

@OddBloke OddBloke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this work, James! By and large, this looks good. I have some thoughts inline.

docs/clouds/openstack.md Outdated Show resolved Hide resolved
instance = self.conn.compute.create_server(
name=self.tag,
image_id=image_id,
flavor_id=self.conn.compute.find_flavor(instance_type).id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flavors are not necessarily the same across OpenStack deployments: will this produce a meaningful error message if used against a cloud without "m1.small" defined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't realize that about flavors. I'll update it accordingly.

Comment on lines 33 to 65
self.floating_ip = self.conn.create_floating_ip(
wait=True,
)
tries = 30
while tries:
try:
self.conn.compute.add_floating_ip_to_server(
self.server,
self.floating_ip.floating_ip_address
)
break
except BadRequestException as e:
if 'Instance network is not ready yet' in str(e):
tries -= 1
time.sleep(1)
continue
raise e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will assign a new floating IP address to existing instances which are found via (e.g.) get_instance, which I don't think is what we want to do.

(I guess this should move up into launch?)

pycloudlib/openstack/instance.py Outdated Show resolved Hide resolved
Comment on lines 79 to 80
self.conn.compute.delete_server(self.server.id)
self.conn.delete_floating_ip(self.floating_ip.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we ensure we delete the floating IP even if the server delete fails?

Comment on lines 109 to 134
except ConflictException as e:
if 'while it is in vm_state active' in str(e):
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An explanatory comment here would be good!

@@ -27,6 +27,7 @@ def read_readme():
"azure-mgmt-compute >= 13.0.0, < 17",
"azure-cli-core >= 2.9.1",
"knack >= 0.7.1",
"python-openstackclient >= 5.2.1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version declares a minimum version of Python 3.6. We might be OK, because (last I saw) OpenStack manages its Python dependencies globally, so that could be an artifically high lower bound in this specific case.

@mock.patch('pycloudlib.key.KeyPair.public_key_content',
new_callable=mock.PropertyMock,
return_value='pretend public key')
@mock.patch('openstack.connect', spec=Connection)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this spec is doing what you want here:

In [1]: import unittest.mock as mock
In [3]:  from openstack.connection import Connection
In [4]: p = mock.patch("openstack.connect", spec=Connection)
In [5]: m = p.start()
In [6]: m()  # this is what `openstack.connect()` will return
Out[6]: <MagicMock name='connect()' id='140181990886512'>
In [7]: m  # and I think you actually want it to return this
Out[7]: <openstack.connection.Connection at 0x7f7ea9c62fa0>

class TestOpenstackKeypair:
"""Tests covering _get_openstack_keypair."""

def test_keypair_doesnt_exist(self, m_openstack, m_keypair):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_keypair_doesnt_exist(self, m_openstack, m_keypair):
def test_keypair_doesnt_exist(self, m_openstack, _m_keypair):

cloud._get_openstack_keypair()
assert 1 == cloud.conn.create_keypair.call_count

def test_keypairs_match(self, m_openstack, m_keypair):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_keypair threw me off here: I assumed that would be a mock of pycloudlib.key.KeyPair (making this test very confusing :p). Perhaps m_public_key_content? Still a little confusing, given it's a property mock (whereas you'd never call public_key_content of course).

@TheRealFalcon
Copy link
Member Author

Addressed the code comments. Still need to address the test comments. @OddBloke , I addressed the floating IP issue a little differently than we initially discussed, so you might want to take a look.

@TheRealFalcon
Copy link
Member Author

Updated the test contents, but now I have new floating ip tests to write 😉

@TheRealFalcon
Copy link
Member Author

Added new tests. Should be ready for re-review.

Copy link
Contributor

@OddBloke OddBloke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Floating IP stuff LGTM, I've left a few inline comments, all of which are nits. I'm going to run some tests locally before approving but, assuming that's all good, this will be good to land.

self.delete_floating_ip = False
self.floating_ip = self._get_existing_floating_ip()
if self.floating_ip is None:
self._create_and_attach_floating_id()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit, but self.floating_ip = self._create_and_attach_floating_id() feels a little easier to follow; it would give _get_... and _create_... a (more) similar API and means that self.floating_ip is only set in one place.

Comment on lines 33 to 36
NETWORK_IPS = [
{'floating_ip_address': '10.0.0.4'},
{'floating_ip_address': '10.0.0.3'},
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another nit, but if the code under test incorrectly did the equivalent of chain(*[d.values() for d in NETWORK_IPS]), we wouldn't catch that with this input.

def test_existing_floating_ip(self, m_create):
"""Test that if a server has an existing floating IP, we use it."""
m_connection = mock.Mock()
m_server = m_connection.compute.get_server.return_value = mock.Mock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit; I don't think we need this final assignment:

Suggested change
m_server = m_connection.compute.get_server.return_value = mock.Mock()
m_server = m_connection.compute.get_server.return_value
In [6]: from unittest import mock                                                                                                                                                                                                                                                         

In [7]: m_connection = mock.Mock()                                                                                                                                                                                                                                                        

In [8]: m_server = m_connection.compute.get_server.return_value                                                                                                                                                                                                                           

In [9]: m_server                                                                                                                                                                                                                                                                          
Out[9]: <Mock name='mock.compute.get_server()' id='140123963984144'>

In [10]: m_server = m_connection.compute.get_server.return_value = mock.Mock()                                                                                                                                                                                                            

In [11]: m_server                                                                                                                                                                                                                                                                         
Out[11]: <Mock name='mock.compute.get_server()' id='140123963995232'>

Copy link
Contributor

@OddBloke OddBloke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants