Răsfoiți Sursa

Updated public IP creation mechanisms for NICs.

Nashwan Azhari 9 ani în urmă
părinte
comite
9e9b427855

+ 80 - 52
coriolis/providers/azure/__init__.py

@@ -51,7 +51,12 @@ OPTIONS = [
                default="coriolis",
                help="Name of the azure storage container which"
                     "will support the migration."),
-    ]
+    cfg.StrOpt("default_migration_hostname",
+               default="migratedinst",
+               help="Default hostname to be used in case the hostname of the "
+                    "instance being migrated does not conform with Azure's "
+                    "standards (no special characters of maximum length 15).")
+]
 
 CONF = cfg.CONF
 CONF.register_opts(OPTIONS, "azure_migration_provider")
@@ -60,7 +65,7 @@ LOG = logging.getLogger(__name__)
 
 MIGRATION_RESGROUP_NAME_FORMAT = "coriolis-migration-%s"
 MIGRATION_WORKER_NAME_FORMAT = "coriolis-worker-%s"
-MIGRATION_WORKER_DISK_FORMAT = "%s.vhd"
+AZURE_DISK_NAME_FORMAT = "%s.vhd"
 MIGRATION_WORKER_NIC_NAME_FORMAT = "coriolis-worker-nic-%s"
 MIGRATION_WORKER_PIP_NAME_FORMAT = "coriolis-worker-pip-%s"
 MIGRATION_NETWORK_NAME_FORMAT = "coriolis-migrnet-%s"
@@ -407,9 +412,7 @@ class ImportProvider(BaseImportProvider):
 
         password = azutils.get_random_password()
         os_profile = compute.models.OSProfile(
-            # TODO: generate these more sanely within constraints:
-            # [no special chars + len tops 15]
-            computer_name="corindows",
+            computer_name=azutils.normalize_hostname(worker_name),
             admin_username=WORKER_USERNAME,
             admin_password=password,
             windows_configuration=compute.models.WindowsConfiguration(
@@ -629,7 +632,7 @@ class ImportProvider(BaseImportProvider):
 
         # create the worker:
         worker_img = WORKER_IMAGES_MAP[export_info["os_type"]]
-        worker_disk_name = MIGRATION_WORKER_DISK_FORMAT % worker_name
+        worker_disk_name = AZURE_DISK_NAME_FORMAT % worker_name
         computec = self._get_compute_client(connection_info)
         worker_osprofile = self._get_worker_osprofile(
             export_info["os_type"], location, worker_name)
@@ -702,13 +705,19 @@ class ImportProvider(BaseImportProvider):
             pkey=worker_osprofile.token
         )
 
-    def _get_migration_os_profile(self, os_type):
+    def _get_migration_os_profile(self, os_type, instance_name):
+        computer_name = instance_name
+        if os_type == constants.OS_TYPE_WINDOWS:
+            computer_name = azutils.normalize_hostname(instance_name)
+
         profile = compute.models.OSProfile(
-            # TODO:
+            computer_name=computer_name,
+            # NOTE: here we are forced to provide a username and password to
+            # the API. These values will be ignored by the Azure agent as
+            # provisioning is turned off in the agent's configuration in
+            # the osmorphing stage.
             admin_username="coriolis",
-            admin_password="Passw0rd",
-            # TODO: ensure name appropriate for Windows hosts too:
-            computer_name="migratedinst"
+            admin_password=azutils.get_random_password()
         )
 
         if os_type == constants.OS_TYPE_WINDOWS:
@@ -753,7 +762,7 @@ class ImportProvider(BaseImportProvider):
                 self._event_manager.progress_update(
                     "Processing instance disk number %d." % (lun + 1))
 
-                disk_name = MIGRATION_WORKER_DISK_FORMAT % (
+                disk_name = AZURE_DISK_NAME_FORMAT % (
                     instance_name + "_" + str(lun))
                 try:
                     datadisk = self._migrate_disk(
@@ -807,21 +816,15 @@ class ImportProvider(BaseImportProvider):
             self._event_manager.progress_update(
                 "Setting up instance '%s's NICs." % instance_name)
 
-            # TOREINST:
-            ip_name = "%s-pip" % instance_name
-            pub_ip = self._create_public_ip(
-                connection_info,
-                target_environment["resource_group"],
-                ip_name, location
-            )
-
             nics = []
             for i, nic in enumerate(export_info["devices"].get("nics", [])):
-                nic_name = nic.get("name", i)
+                nic_name = nic.get("name", "%s-NIC-%d" % (instance_name, i + 1))
                 net_name = nic.get("network_name", None)
                 subnet_name = nic.get("subnet_name", None)
+                add_public_ip = False
+
                 if not net_name:
-                    net = target_environment.get("network", {})
+                    net = target_environment.get("destination_network", {})
                     net_name = net.get("name", None)
 
                     if not net or not net_name:
@@ -837,10 +840,15 @@ class ImportProvider(BaseImportProvider):
                     LOG.info("'network_name' not provided for NIC '%s'. "
                              "Attempting to attach to migration network '%s'.",
                              nic_name, net_name)
-                    net_name = target_environment["network"]["name"]
-                    subnet_name = target_environment["network"]["subnet"]
+
+                    net_name = target_environment["destination_network"]["name"]
+                    subnet_name = target_environment[
+                        "destination_network"]["subnet"]
+                    add_public_ip = target_environment[
+                        "destination_network"]["is_public_network"]
+
                 else:
-                    network_map = target_environment.get("network_map", {})
+                    network_map = target_environment.get("network_map", [])
                     if not network_map:
                         raise azexceptions.FatalAzureOperationException(
                             code=-1,
@@ -851,8 +859,11 @@ class ImportProvider(BaseImportProvider):
                                 )
                         )
 
-                    net_name = network_map.get(net_name, None)
-                    if not net_name:
+                    net = None
+                    for netm in network_map:
+                        if netm["source_network"] == net_name:
+                            net = netm
+                    if not net:
                         raise azexceptions.FatalAzureOperationException(
                             code=-1,
                             msg="Cannot find suitable network name "
@@ -862,47 +873,58 @@ class ImportProvider(BaseImportProvider):
                                 )
                         )
 
-                    subnet_name = "default"
+                    net_name = net["destination_network"]
+                    subnet_name = net["destination_subnet"]
+                    add_public_ip = net["is_public_network"]
                     LOG.info(
                         "Subnet for nic '%s' within '%s' defaulting to "
                         "'default'.", nic_name, net_name)
 
-                nic_name = "%s-NIC-%d" % (instance_name, i)
+                nic_name = azutils.normalize_resource_name(nic_name)
+
+                nic_ip_config = network.models.NetworkInterfaceIPConfiguration(
+                    name=nic_name + "-ipconf",
+                    subnet=self._get_subnet(
+                        connection_info,
+                        target_environment["resource_group"],
+                        net_name,
+                        subnet_name
+                    ),
+                    private_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
+                )
+
+                if add_public_ip:
+                    ip_name = "%s-pip" % nic_name
+                    pub_ip = self._create_public_ip(
+                        connection_info,
+                        target_environment["resource_group"],
+                        ip_name, location
+                    )
+
+                    nic_ip_config.public_ip_address = pub_ip
+
                 nics.append(self._create_nic(
                     connection_info,
                     target_environment["resource_group"],
                     nic_name,
                     location,
-                    ip_configs=[
-                        network.models.NetworkInterfaceIPConfiguration(
-                            name=nic_name + "-ipconf",
-                            subnet=self._get_subnet(
-                                connection_info,
-                                target_environment["resource_group"],
-                                net_name,
-                                subnet_name
-                            ),
-                            private_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
-                            # TOREINST:
-                            public_ip_address=pub_ip,
-                        )
-                    ],
+                    ip_configs=[nic_ip_config],
                 ))
 
             # create the VM:
             self._event_manager.progress_update(
                 "Starting instance '%s' on Azure." % instance_name)
 
-            disk_name = MIGRATION_WORKER_DISK_FORMAT % (
+            disk_name = AZURE_DISK_NAME_FORMAT % (
                 "".join(instance_name) + "_os_disk")
             vm_profile = self._get_migration_os_profile(
-                export_info["os_type"])
+                export_info["os_type"], instance_name)
 
             vmclient = self._get_compute_client(
                 connection_info).virtual_machines
             awaited(vmclient.create_or_update)(
                 target_environment["resource_group"],
-                instance_name,
+                azutils.normalize_resource_name(instance_name),
                 compute.models.VirtualMachine(
                     location=location,
                     os_profile=vm_profile,
@@ -921,6 +943,8 @@ class ImportProvider(BaseImportProvider):
                             os_type=AZURE_OSTYPES_MAP[export_info["os_type"]],
                             caching=compute.models.CachingTypes.none,
                             create_option=compute.models.DiskCreateOptionTypes.from_image,
+                            # NOTE: we are guaranteed that the first disk is
+                            # the one with the OS inside it:
                             image=worker_info.datadisks[0].vhd,
                             vhd=compute.models.VirtualHardDisk(
                                 BLOB_PATH_FORMAT % (
@@ -930,7 +954,9 @@ class ImportProvider(BaseImportProvider):
                                 )
                             ),
                         ),
-                        # NOTE: slicing is bad.
+                        # NOTE: we are guaranteed that the first disk in the
+                        # export_info is the one with the OS inside of it,
+                        # thus the rest are the datadisks:
                         data_disks=worker_info.datadisks[1:],
                     )
                 ),
@@ -970,17 +996,19 @@ class ImportProvider(BaseImportProvider):
             "Cleaning up after '%s' and its associated disks." % worker_name)
 
         # first; ensure the worker is properly gone:
-        computec = self._get_compute_client(connection_info).virtual_machines
-        try:
+        @utils.ignore_exceptions
+        def cleanupw():
+            computec = self._get_compute_client(
+                connection_info).virtual_machines
             worker_vm = computec.get(migration_resgroup, worker_name)
             if worker_vm:
                 awaited(computec.delete)(migration_resgroup, worker_name)
-        except:
-            pass
+        cleanupw()
 
+        # delete worker disks:
         blobd = self._get_page_blob_client(target_environment)
         cont_name = CONF.azure_migration_provider.migr_container_name
-        blob_name = MIGRATION_WORKER_DISK_FORMAT % worker_name
+        blob_name = AZURE_DISK_NAME_FORMAT % worker_name
 
         if blobd.exists(cont_name, blob_name):
             blobd.delete_blob(cont_name, blob_name)

+ 34 - 4
coriolis/providers/azure/schemas/target_environment_schema.json

@@ -33,9 +33,32 @@
       ]
     },
     "network_map": {
-      "type": "object"
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "source_network": {
+            "type": "string"
+          },
+          "destination_network": {
+            "type": "string"
+          },
+          "destination_subnet_name": {
+            "type": "string"
+          },
+          "is_public_network": {
+            "type": "boolean"
+          }
+        },
+        "required": [
+          "source_network",
+          "destination_network",
+          "destination_subnet",
+          "is_public_network"
+        ]
+      }
     },
-    "network": {
+    "destination_network": {
       "type": "object",
       "properties": {
         "name": {
@@ -43,11 +66,15 @@
         },
         "subnet": {
           "type": "string"
+        },
+        "is_public_network": {
+          "type": "boolean"
         }
       },
       "required": [
         "name",
-        "subnet"
+        "subnet",
+        "is_public_network"
       ]
     }
   },
@@ -56,7 +83,10 @@
       "required": ["secret_ref"]
     },
     {
-      "required": ["size", "location", "resource_group", "storage"]
+      "required": ["size", "location", "resource_group", "storage", "destination_network"]
+    },
+    {
+      "required": ["size", "location", "resource_group", "storage", "network_map"]
     }
   ]
 }

+ 40 - 0
coriolis/providers/azure/utils.py

@@ -10,6 +10,13 @@ import random
 import string
 import uuid
 
+from oslo_config import cfg
+from oslo_log import log as logging
+
+
+CONF = cfg.CONF
+LOG = logging.getLogger(__name__)
+
 
 def get_random_password():
     """ Returns a random password compatible with the minimal requirements of
@@ -30,6 +37,39 @@ def get_unique_id():
     return "".join([x for x in str(uuid.uuid4()) if x.isalnum()])
 
 
+def normalize_resource_name(name):
+    """ Normalizes a resource name.
+
+    Constraints are:
+        - alphanumeric characters or '.', '-', '_'
+        - maximum length is 80
+    """
+    return "".join([x for x in name if x.isalnum() or x in ['.', '-', '_']])
+
+
+def normalize_hostname(hostname):
+    """ Normalizes the provided hostname for Azure.
+
+    Constraints are:
+        - no special characters
+        - maximum length of 15
+    """
+    new = "".join([x for x in hostname if x.isalnum])
+    if not new:
+        new = CONF.azure_migration_provider.default_migration_hostname
+        LOG.info("Cannot normalize instance hostname '%s' for Azure. "
+                 "Defaulting to using '%s'.", hostname, new)
+
+    if len(new) > 15:
+        new = new[:15]
+
+    if new != hostname:
+        LOG.info("Normalized instance hostname '%s' to '%s' "
+                 "for booting instance on Azure.", new, hostname)
+
+    return new
+
+
 def normalize_location(location):
     """ Normalizes a location for azure. """
     return "".join([x.lower() for x in location if x.isalnum()])