Add admin password support for Azure driver

Under Azure, an admin password is required in order to launch a
VM from a Windows image.  Add support for that.

Also, shorten the node name to less than 15 characters in order
to accomodate Windows restrictions.

Change-Id: I899f3e02046ffdb5f9fd19fe90c4bc9afdb01a7c
This commit is contained in:
James E. Blair 2021-11-15 15:50:27 -08:00
parent c6723ea88f
commit 44f3d63973
7 changed files with 147 additions and 6 deletions

View File

@ -248,6 +248,24 @@ section of the configuration.
The username that should be used when connecting to the node.
.. attr:: password
:type: str
If booting a Windows image, an administrative password is
required. Either supply it here, or set
:attr:`providers.[azure].cloud-images.generate-password`.
Nodepool does not provide the password to requesting clients;
to be used it must be provided in some other manner.
.. attr:: generate-password
:type: bool
If booting a Windows image, an administrative password is
required. If the password is not actually used (e.g., the
image has key-based authentication enabled), a random
password can be provided by enabling this option. The
password is not stored anywhere and is not retrievable.
.. attr:: key
:type: str

View File

@ -12,10 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import math
import logging
import json
import logging
import math
import os
import random
import string
import cachetools.func
@ -41,6 +43,18 @@ def quota_info_from_sku(sku):
instances=1)
def generate_password():
while True:
chars = random.choices(string.ascii_lowercase +
string.ascii_uppercase +
string.digits,
k=64)
if ((set(string.ascii_lowercase) & set(chars)) and
(set(string.ascii_uppercase) & set(chars)) and
(set(string.digits) & set(chars))):
return(''.join(chars))
class AzureInstance(statemachine.Instance):
def __init__(self, vm, nic=None, public_ipv4=None,
public_ipv6=None, sku=None):
@ -646,7 +660,7 @@ class AzureAdapter(statemachine.Adapter):
else:
image_reference = {'id': label.cloud_image.image_id}
os_profile = {'computerName': hostname}
if image.username and image.key:
if image.key:
linux_config = {
'ssh': {
'publicKeys': [{
@ -657,8 +671,13 @@ class AzureAdapter(statemachine.Adapter):
},
"disablePasswordAuthentication": True,
}
os_profile['adminUsername'] = image.username
os_profile['linuxConfiguration'] = linux_config
if image.username:
os_profile['adminUsername'] = image.username
if image.password:
os_profile['adminPassword'] = image.password
elif image.generate_password:
os_profile['adminPassword'] = generate_password()
if label.custom_data:
os_profile['customData'] = label.custom_data

View File

@ -32,6 +32,8 @@ class AzureProviderCloudImage(ConfigValue):
}
self.name = image['name']
self.username = image['username']
self.password = image.get('password')
self.generate_password = image.get('generate-password', False)
# TODO(corvus): remove zuul_public_key
self.key = image.get('key', zuul_public_key)
self.image_reference = image.get('image-reference')
@ -60,6 +62,8 @@ class AzureProviderCloudImage(ConfigValue):
return v.All({
v.Required('name'): str,
v.Required('username'): str,
'password': str,
'generate-password': bool,
# TODO(corvus): make required when zuul_public_key removed
'key': str,
v.Exclusive('image-reference', 'spec'): azure_image_reference,
@ -89,6 +93,8 @@ class AzureProviderDiskImage(ConfigValue):
self.python_path = image.get('python-path')
self.shell_type = image.get('shell-type')
self.username = image.get('username')
self.password = image.get('password')
self.generate_password = image.get('generate-password', False)
self.key = image.get('key')
self.connection_type = image.get('connection-type', 'ssh')
self.connection_port = image.get(

View File

@ -122,7 +122,8 @@ class StateMachineNodeLauncher(stats.StatsReporter):
self.node.connection_type = image.connection_type
self.zk.storeNode(self.node)
hostname = 'nodepool-' + self.node.id
# Windows computer names can be no more than 15 chars long.
hostname = 'np' + self.node.id
retries = self.manager.provider.launch_retries
metadata = {'nodepool_node_id': self.node.id,
'nodepool_pool_name': self.handler.pool.name,

View File

@ -15,6 +15,10 @@ zookeeper-tls:
labels:
- name: bionic
min-ready: 0
- name: windows-password
min-ready: 0
- name: windows-generate
min-ready: 0
providers:
- name: azure
@ -34,6 +38,22 @@ providers:
publisher: Canonical
version: latest
offer: UbuntuServer
- name: windows-password
image-reference:
sku: 2022-datacenter-azure-edition
publisher: MicrosoftWindowsServer
version: latest
offer: WindowsServer
username: foobar
password: reallybadpassword123
- name: windows-generate
image-reference:
sku: 2022-datacenter-azure-edition
publisher: MicrosoftWindowsServer
version: latest
offer: WindowsServer
username: foobar
generate-password: True
pools:
- name: main
max-servers: 10
@ -51,3 +71,11 @@ providers:
systemPurpose: CI
user-data: "This is the user data"
custom-data: "This is the custom data"
- name: windows-password
cloud-image: windows-password
hardware-profile:
vm-size: Standard_B1ls
- name: windows-generate
cloud-image: windows-generate
hardware-profile:
vm-size: Standard_B1ls

View File

@ -139,3 +139,67 @@ class TestDriverAzure(tests.DBTestCase):
"/subscriptions/c35cf7df-ed75-4c85-be00-535409a85120"
"/resourceGroups/nodepool/providers/Microsoft.Compute"
"/images/test-image-1234")
def test_azure_windows_image_password(self):
configfile = self.setup_config(
'azure.yaml',
auth_path=self.fake_azure.auth_file.name)
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
req = zk.NodeRequest()
req.state = zk.REQUESTED
req.node_types.append('windows-password')
self.zk.storeNodeRequest(req)
req = self.waitForNodeRequest(req)
self.assertEqual(req.state, zk.FULFILLED)
self.assertNotEqual(req.nodes, [])
node = self.zk.getNode(req.nodes[0])
self.assertEqual(node.allocated_to, req.id)
self.assertEqual(node.state, zk.READY)
self.assertIsNotNone(node.launcher)
self.assertEqual(node.connection_type, 'ssh')
self.assertEqual(node.attributes,
{'key1': 'value1', 'key2': 'value2'})
self.assertEqual(node.host_keys, ['ssh-rsa FAKEKEY'])
self.assertEqual(
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['properties']['osProfile']['adminUsername'],
'foobar')
self.assertEqual(
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['properties']['osProfile']['adminPassword'],
'reallybadpassword123')
def test_azure_windows_image_generate(self):
configfile = self.setup_config(
'azure.yaml',
auth_path=self.fake_azure.auth_file.name)
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
req = zk.NodeRequest()
req.state = zk.REQUESTED
req.node_types.append('windows-generate')
self.zk.storeNodeRequest(req)
req = self.waitForNodeRequest(req)
self.assertEqual(req.state, zk.FULFILLED)
self.assertNotEqual(req.nodes, [])
node = self.zk.getNode(req.nodes[0])
self.assertEqual(node.allocated_to, req.id)
self.assertEqual(node.state, zk.READY)
self.assertIsNotNone(node.launcher)
self.assertEqual(node.connection_type, 'ssh')
self.assertEqual(node.attributes,
{'key1': 'value1', 'key2': 'value2'})
self.assertEqual(node.host_keys, ['ssh-rsa FAKEKEY'])
self.assertEqual(
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['properties']['osProfile']['adminUsername'],
'foobar')
self.assertEqual(
len(self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['properties']['osProfile']['adminPassword']),
64)

View File

@ -0,0 +1,5 @@
---
features:
- |
The Azure driver now supports setting an admin password, which is
required in order to launch Windows images on Azure.