diff --git a/lib/cmdlib.py b/lib/cmdlib.py index b9c8014fb933eb86ca229545984dcd086e45af19..8d2721c452f4b6cb1188c410cc1df1bf59f51a40 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -2837,7 +2837,7 @@ class LUCreateInstance(LogicalUnit): HTYPE = constants.HTYPE_INSTANCE _OP_REQP = ["instance_name", "mem_size", "disk_size", "pnode", "disk_template", "swap_size", "mode", "start", "vcpus", - "wait_for_sync", "ip_check"] + "wait_for_sync", "ip_check", "mac"] def BuildHooksEnv(self): """Build hooks env. @@ -3013,6 +3013,12 @@ class LUCreateInstance(LogicalUnit): raise errors.OpPrereqError("IP %s of instance %s already in use" % (hostname1.ip, instance_name)) + # MAC address verification + if self.op.mac != "auto": + if not utils.IsValidMac(self.op.mac.lower()): + raise errors.OpPrereqError("invalid MAC address specified: %s" % + self.op.mac) + # bridge verification bridge = getattr(self.op, "bridge", None) if bridge is None: @@ -3037,7 +3043,12 @@ class LUCreateInstance(LogicalUnit): instance = self.op.instance_name pnode_name = self.pnode.name - nic = objects.NIC(bridge=self.op.bridge, mac=self.cfg.GenerateMAC()) + if self.op.mac == "auto": + mac_address=self.cfg.GenerateMAC() + else: + mac_address=self.op.mac + + nic = objects.NIC(bridge=self.op.bridge, mac=mac_address) if self.inst_ip is not None: nic.ip = self.inst_ip @@ -4073,8 +4084,9 @@ class LUSetInstanceParms(LogicalUnit): self.mem = getattr(self.op, "mem", None) self.vcpus = getattr(self.op, "vcpus", None) self.ip = getattr(self.op, "ip", None) + self.mac = getattr(self.op, "mac", None) self.bridge = getattr(self.op, "bridge", None) - if [self.mem, self.vcpus, self.ip, self.bridge].count(None) == 4: + if [self.mem, self.vcpus, self.ip, self.bridge, self.mac].count(None) == 5: raise errors.OpPrereqError("No changes submitted") if self.mem is not None: try: @@ -4096,6 +4108,12 @@ class LUSetInstanceParms(LogicalUnit): else: self.do_ip = False self.do_bridge = (self.bridge is not None) + if self.mac is not None: + if self.cfg.IsMacInUse(self.mac): + raise errors.OpPrereqError('MAC address %s already in use in cluster' % + self.mac) + if not utils.IsValidMac(self.mac): + raise errors.OpPrereqError('Invalid MAC address %s' % self.mac) instance = self.cfg.GetInstanceInfo( self.cfg.ExpandInstanceName(self.op.instance_name)) @@ -4125,6 +4143,9 @@ class LUSetInstanceParms(LogicalUnit): if self.bridge: instance.nics[0].bridge = self.bridge result.append(("bridge", self.bridge)) + if self.mac: + instance.nics[0].mac = self.mac + result.append(("mac", self.mac)) self.cfg.AddInstance(instance) diff --git a/lib/config.py b/lib/config.py index fcf8b9fa06cc2a8d8b162dd2a7cf78d96f6402ea..d8c2605f77ac580b1ddd87cf0e93e87f1e49f84a 100644 --- a/lib/config.py +++ b/lib/config.py @@ -96,6 +96,18 @@ class ConfigWriter: raise errors.ConfigurationError("Can't generate unique MAC") return mac + def IsMacInUse(self, mac): + """Predicate: check if the specified MAC is in use in the Ganeti cluster. + + This only checks instances managed by this cluster, it does not + check for potential collisions elsewhere. + + """ + self._OpenConfig() + self._ReleaseLock() + all_macs = self._AllMACs() + return mac in all_macs + def _ComputeAllLVs(self): """Compute the list of all LVs. diff --git a/lib/opcodes.py b/lib/opcodes.py index 3b248fc3069ad012c6a2c07bc0f8704ce0152bf0..8cbca532da6d8305072729112106737b4be2c3f3 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -160,7 +160,7 @@ class OpCreateInstance(OpCode): __slots__ = ["instance_name", "mem_size", "disk_size", "os_type", "pnode", "disk_template", "snode", "swap_size", "mode", "vcpus", "ip", "bridge", "src_node", "src_path", "start", - "wait_for_sync", "ip_check"] + "wait_for_sync", "ip_check", "mac"] class OpReinstallInstance(OpCode): @@ -257,7 +257,7 @@ class OpQueryInstanceData(OpCode): class OpSetInstanceParms(OpCode): """Change the parameters of an instance.""" OP_ID = "OP_INSTANCE_SET_PARMS" - __slots__ = ["instance_name", "mem", "vcpus", "ip", "bridge"] + __slots__ = ["instance_name", "mem", "vcpus", "ip", "bridge", "mac"] # OS opcodes diff --git a/lib/utils.py b/lib/utils.py index d5fe36ea97d4dc1b04c66b41d1e6a1997f578c92..f854f5aaa9031f92cc807cd86bcad85bc9c5147f 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1055,3 +1055,13 @@ def UniqueSequence(seq): """ seen = set() return [i for i in seq if i not in seen and not seen.add(i)] + + +def IsValidMac(mac): + """Predicate to check if a MAC address is valid. + + Checks wether the supplied MAC address is formally correct, only + accepts colon separated format. + """ + mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$") + return mac_check.match(mac) is not None diff --git a/scripts/gnt-instance b/scripts/gnt-instance index fd6f48d6a4af229ad4e8c22796c2a574f3ce123b..07285f09ced0c38b62eb352a55e3ebcf5b44fe88 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -226,7 +226,7 @@ def AddInstance(opts, args): snode=snode, vcpus=opts.vcpus, ip=opts.ip, bridge=opts.bridge, start=opts.start, ip_check=opts.ip_check, - wait_for_sync=opts.wait_for_sync) + wait_for_sync=opts.wait_for_sync, mac=opts.mac) SubmitOpCode(op) return 0 @@ -624,15 +624,16 @@ def SetInstanceParms(opts, args): Opts used: memory - the new memory size vcpus - the new number of cpus + mac - the new MAC address of the instance """ - if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge: + if not (opts.mem or opts.vcpus or opts.ip or opts.bridge or opts.mac): logger.ToStdout("Please give at least one of the parameters.") return 1 op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem, vcpus=opts.vcpus, ip=opts.ip, - bridge=opts.bridge) + bridge=opts.bridge, mac=opts.mac) result = SubmitOpCode(op) if result: @@ -701,6 +702,9 @@ add_opts = [ make_option("-i", "--ip", dest="ip", help="IP address ('none' [default], 'auto', or specify address)", default='none', type="string", metavar="<ADDRESS>"), + make_option("--mac", dest="mac", + help="MAC address ('auto' [default], or specify address)", + default='auto', type="string", metavar="<MACADDRESS>"), make_option("--no-wait-for-sync", dest="wait_for_sync", default=True, action="store_false", help="Don't wait for sync (DANGEROUS!)"), make_option("-b", "--bridge", dest="bridge", @@ -808,6 +812,9 @@ commands = { make_option("-b", "--bridge", dest="bridge", help="Bridge to connect this instance to", default=None, type="string", metavar="<bridge>"), + make_option("--mac", dest="mac", + help="MAC address", default=None, + type="string", metavar="<MACADDRESS>"), ], "<instance>", "Alters the parameters of an instance"), 'shutdown': (ShutdownInstance, ARGS_ANY,