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

Key/Value backend #865

Closed
wants to merge 81 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
7035abe
Move statefile into state.file module
moretea Mar 13, 2017
6da031f
Introduce different state state schemes.
moretea Mar 13, 2017
8269650
Rename statefile->state in deployment.py
moretea Mar 13, 2017
c73773f
Move file based deployment lock to file state implementation.
moretea Mar 13, 2017
8a4e34a
Fix indenting
moretea Mar 13, 2017
bb056ea
Bite the bullet; remove _db references from business objects + rename…
moretea Mar 13, 2017
6cc171a
move get_resources_for
moretea Mar 13, 2017
813a231
move set_deployment_attrs
moretea Mar 13, 2017
7b58934
move del_deployment_attr
moretea Mar 13, 2017
7ed3fd3
move get_deployment_attr
moretea Mar 13, 2017
0db686d
move create_resource
moretea Mar 13, 2017
97e0714
Move most logic in export() to get_all_deployment_attrs in statefile.
moretea Mar 13, 2017
584d658
Make import use an atomic transaction thingy.
moretea Mar 13, 2017
97bda1a
move clone_deployment
moretea Mar 13, 2017
77e9b93
move part of delete_resource
moretea Mar 13, 2017
4d33f93
missed a few _'s
moretea Mar 13, 2017
5649e39
move part of delete to _delete_deployment
moretea Mar 13, 2017
4075901
use state atomic
moretea Mar 13, 2017
5f39278
Move part of logic of rename to _rename_resource
moretea Mar 13, 2017
6605128
oh yeah, I did rename _db to __db!
moretea Mar 13, 2017
5881c06
Use atomic instead of _db
moretea Mar 13, 2017
de9d700
Use atomic instead of _db
moretea Mar 13, 2017
ff23c1c
move set_resource_attrs
moretea Mar 13, 2017
eb2a24a
move del_resource_attr
moretea Mar 13, 2017
47b3a43
move get_resource_attr
moretea Mar 13, 2017
6835823
move part of export to get_all_resource_attrs
moretea Mar 13, 2017
3739051
Use atomic instead of _db
moretea Mar 13, 2017
66dee1d
oh yeah, I did rename _db to __db!
moretea Mar 13, 2017
6254ccb
simple typo's
moretea Mar 13, 2017
2d1a822
Need to pass in object; too much coupling to refactor right now
moretea Mar 13, 2017
20c5e89
missed assigning to local dict
moretea Mar 13, 2017
6c37bbd
typo
moretea Mar 13, 2017
4baebf9
missing subclass function. Just copied for now
moretea Mar 13, 2017
44652be
typo
moretea Mar 13, 2017
e238874
locally reachable already
moretea Mar 13, 2017
56c39b7
missed the self parameter
moretea Mar 13, 2017
83b5ca1
use _state.atomic instead of _db
moretea Mar 14, 2017
abf3d6e
schema: file -> sqlite3
moretea Mar 14, 2017
0d999cf
Fix _rename_resource, needs resource_id
moretea Mar 14, 2017
a08de62
WIP: initial json backend.
moretea Mar 14, 2017
6baf57f
Include the deployment_uuid in the API for modifying resource attributes
moretea Mar 14, 2017
2dfa148
Small bug fixes
moretea Mar 14, 2017
6733fa7
Utilize deployment_uid
moretea Mar 14, 2017
3827e69
Also recognize json's true as a valid bool value
moretea Mar 14, 2017
056ab60
Undo split between state.atomic and state.db
moretea Mar 29, 2017
38aa181
Typo
moretea Mar 29, 2017
4aaf084
Rename file -> sqlite3_file in tests
moretea Mar 29, 2017
6950f37
Rename file -> sqlite3_file in scripts/nixops
moretea Mar 29, 2017
6576e16
Rename file -> sqlite3_file in scripts/nixops
moretea Mar 29, 2017
9d7cf8c
Merge branch 'master' into kv-state
rbvermaa Jul 24, 2017
7c34e60
Merge branch 'kv-state' of https://github.com/moretea/nixops into mor…
mogorman Feb 4, 2018
ef2f480
Merge branch 'moretea-kv-state' into kv-state
mogorman Feb 4, 2018
4ff5a24
fixed probable typo from fork
mogorman Feb 4, 2018
f7ef59f
change all instances to use state wrapper
mogorman Feb 5, 2018
47684d5
integrate new state wrapper with one from fork
mogorman Feb 5, 2018
b0f18b5
rewrite this to use the wrapper
mogorman Feb 7, 2018
b3690b0
initial commit of moving over to sqlalchemy for all sql endpoints
mogorman Feb 7, 2018
0b89b58
typo letter
mogorman Feb 7, 2018
1ab3156
working switcher code. now rewriting sql code to use sqlalchemy
mogorman Feb 7, 2018
98290e6
fixed sqlalchemy sqlite support by using internal implementation. now…
mogorman Feb 7, 2018
3863edc
working sqlalchemy
mogorman Feb 7, 2018
c11756c
removed prints
mogorman Feb 7, 2018
b42814f
beginning of porting to mysql as well. will need specific sql for cre…
mogorman Feb 7, 2018
19d3178
working mysql support
mogorman Feb 7, 2018
5d200ee
possible row exception
mogorman Feb 7, 2018
6e71adc
better locking and some temporary debugging
mogorman Feb 10, 2018
ceaf062
fix the past
mogorman Feb 12, 2018
6f4f855
fixed unit tests
mogorman Feb 12, 2018
28ac4b2
move function into the core state file
mogorman Feb 13, 2018
759b96b
this simplifies to kv / sqlite for now so that we can move forward on
mogorman Feb 14, 2018
cc70b2a
testing this theory
mogorman Feb 14, 2018
4585685
move class around
mogorman Feb 14, 2018
07278c8
test
mogorman Feb 14, 2018
76ffca7
remove debugging
mogorman Feb 14, 2018
6cf8c02
adding unit test and testing it
mogorman Feb 14, 2018
f656d58
new unit tests
mogorman Feb 14, 2018
83c4af9
remove debugging
mogorman Feb 14, 2018
4cea4d2
fix variable name issue and move subclasses to common spot among
mogorman Feb 19, 2018
f477f2a
work on unit tests
mogorman Feb 19, 2018
36f2479
make json match the behavior of sqlite
mogorman Feb 19, 2018
0a836b6
misnamed variable
mogorman Feb 19, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions nixops/backends/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def __init__(self, depl, name, id):

def _reset_state(self):
"""Discard all state pertaining to an instance."""
with self.depl._db:
with self.depl._state.db:
self.state = MachineState.MISSING
self.associate_public_ip_address = None
self.use_private_ip_address = None
Expand Down Expand Up @@ -344,7 +344,7 @@ def _instance_ip_ready(ins):

self.log_end("{0} / {1}".format(instance.ip_address, instance.private_ip_address))

with self.depl._db:
with self.depl._state.db:
self.private_ipv4 = instance.private_ip_address
self.public_ipv4 = instance.ip_address
self.public_dns_name = instance.public_dns_name
Expand Down Expand Up @@ -588,7 +588,7 @@ def _assign_elastic_ip(self, elastic_ipv4, check):

nixops.known_hosts.update(self.public_ipv4, elastic_ipv4, self.public_host_key)

with self.depl._db:
with self.depl._state.db:
self.elastic_ipv4 = elastic_ipv4
self.public_ipv4 = elastic_ipv4
self.ssh_pinged = False
Expand All @@ -601,7 +601,7 @@ def _assign_elastic_ip(self, elastic_ipv4, check):
else:
self.log("address ‘{0}’ was not associated with instance ‘{1}’".format(self.elastic_ipv4, self.vm_id))

with self.depl._db:
with self.depl._state.db:
self.elastic_ipv4 = None
self.public_ipv4 = None
self.ssh_pinged = False
Expand Down Expand Up @@ -665,7 +665,7 @@ def create_instance(self, defn, zone, devmap, user_data, ebs_optimized):
lambda: self._conn.request_spot_instances(price=defn.spot_instance_price/100.0, **common_args)
)[0]

with self.depl._db:
with self.depl._state.db:
self.spot_instance_price = defn.spot_instance_price
self.spot_instance_request_id = request.id

Expand Down Expand Up @@ -696,7 +696,7 @@ def create_instance(self, defn, zone, devmap, user_data, ebs_optimized):
# the instance ID, we'll get the same instance ID on the
# next run.
if not self.client_token:
with self.depl._db:
with self.depl._state.db:
self.client_token = nixops.util.generate_random_string(length=48) # = 64 ASCII chars
self.state = self.STARTING

Expand Down Expand Up @@ -899,7 +899,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
# Generate a public/private host key.
if not self.public_host_key:
(private, public) = nixops.util.create_key_pair(type=defn.host_key_type())
with self.depl._db:
with self.depl._state.db:
self.public_host_key = public
self.private_host_key = private

Expand All @@ -909,7 +909,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):

instance = self.create_instance(defn, zone, devmap, user_data, ebs_optimized)

with self.depl._db:
with self.depl._state.db:
self.vm_id = instance.id
self.ami = defn.ami
self.instance_type = defn.instance_type
Expand Down Expand Up @@ -994,7 +994,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
elastic_ipv4 = res.public_ipv4
self._assign_elastic_ip(elastic_ipv4, check)

with self.depl._db:
with self.depl._state.db:
self.use_private_ip_address = defn.use_private_ip_address
self.associate_public_ip_address = defn.associate_public_ip_address

Expand Down
4 changes: 2 additions & 2 deletions nixops/backends/hetzner.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
self.log_start("creating an exclusive robot admin sub-account "
"for ‘{0}’... ".format(self.name))
server = self._get_server_from_main_robot(self.main_ipv4, defn)
with self.depl._db:
with self.depl._state.db:
(self.robot_admin_user,
self.robot_admin_pass) = server.admin.create()
self.log_end("done. ({0})".format(self.robot_admin_user))
Expand All @@ -609,7 +609,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
)
if robot_user != self.robot_admin_user or \
robot_pass != self.robot_admin_pass:
with self.depl._db:
with self.depl._state.db:
(self.robot_admin_user,
self.robot_admin_pass) = (robot_user, robot_pass)

Expand Down
5 changes: 3 additions & 2 deletions nixops/backends/virtualbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def _update_ip(self):
capture_stdout=True).rstrip()
if res[0:7] != "Value: ": return
new_address = res[7:]

nixops.known_hosts.update(self.private_ipv4, new_address, self.public_host_key)
self.private_ipv4 = new_address

Expand Down Expand Up @@ -194,7 +195,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
# Generate a public/private host key.
if not self.public_host_key:
(private, public) = nixops.util.create_key_pair()
with self.depl._db:
with self.depl._state.db:
self.public_host_key = public
self.private_host_key = private

Expand All @@ -203,7 +204,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):

# Backwards compatibility.
if self.disk:
with self.depl._db:
with self.depl._state.db:
self._update_disk("disk1", {"created": True, "path": self.disk,
"attached": self.disk_attached,
"port": 0})
Expand Down
121 changes: 24 additions & 97 deletions nixops/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import errno
from collections import defaultdict
from xml.etree import ElementTree
import nixops.statefile
import nixops.backends
import nixops.logger
import nixops.parallel
Expand All @@ -22,7 +21,6 @@
import getpass
import traceback
import glob
import fcntl
import itertools
import platform
from nixops.util import ansi_success
Expand Down Expand Up @@ -56,9 +54,8 @@ class Deployment(object):
# internal variable to mark if network attribute of network has been evaluated (separately)
network_attr_eval = False

def __init__(self, statefile, uuid, log_file=sys.stderr):
self._statefile = statefile
self._db = statefile._db
def __init__(self, state, uuid, log_file=sys.stderr):
self._state = state
self.uuid = uuid

self._last_log_prefix = None
Expand All @@ -78,15 +75,8 @@ def __init__(self, statefile, uuid, log_file=sys.stderr):
if not os.path.exists(self.expr_path):
self.expr_path = os.path.dirname(__file__) + "/../nix"

self.resources = {}
with self._db:
c = self._db.cursor()
c.execute("select id, name, type from Resources where deployment = ?", (self.uuid,))
for (id, name, type) in c.fetchall():
r = _create_state(self, type, name, id)
self.resources[name] = r
self.resources = self._state.get_resources_for(self)
self.logger.update_log_prefixes()

self.definitions = None


Expand Down Expand Up @@ -126,65 +116,41 @@ def get_machine(self, name):
raise Exception("resource ‘{0}’ is not a machine".format(name))
return res


def _set_attrs(self, attrs):
"""Update deployment attributes in the state file."""
with self._db:
c = self._db.cursor()
for n, v in attrs.iteritems():
if v == None:
c.execute("delete from DeploymentAttrs where deployment = ? and name = ?", (self.uuid, n))
else:
c.execute("insert or replace into DeploymentAttrs(deployment, name, value) values (?, ?, ?)",
(self.uuid, n, v))
"""Update deployment attributes in the state."""
self._state.set_deployment_attrs(self.uuid, attrs)


def _set_attr(self, name, value):
"""Update one deployment attribute in the state file."""
"""Update one deployment attribute in the state."""
self._set_attrs({name: value})


def _del_attr(self, name):
"""Delete a deployment attribute from the state file."""
with self._db:
self._db.execute("delete from DeploymentAttrs where deployment = ? and name = ?", (self.uuid, name))
"""Delete a deployment attribute from the state."""
self._state.del_deployment_attr(self.uuid, name)


#TODO(moretea): The default param does not appear to be used at all?
# Removed it when moving the body to nixops/state/file.py.
def _get_attr(self, name, default=nixops.util.undefined):
"""Get a deployment attribute from the state file."""
with self._db:
c = self._db.cursor()
c.execute("select value from DeploymentAttrs where deployment = ? and name = ?", (self.uuid, name))
row = c.fetchone()
if row != None: return row[0]
return nixops.util.undefined

"""Get a deployment attribute from the state."""
return self._state.get_deployment_attr(self.uuid, name)

def _create_resource(self, name, type):
c = self._db.cursor()
c.execute("select 1 from Resources where deployment = ? and name = ?", (self.uuid, name))
if len(c.fetchall()) != 0:
raise Exception("resource already exists in database!")
c.execute("insert into Resources(deployment, name, type) values (?, ?, ?)",
(self.uuid, name, type))
id = c.lastrowid
r = _create_state(self, type, name, id)
r = self._state.create_resource(self, name, type)
self.resources[name] = r
return r


def export(self):
with self._db:
c = self._db.cursor()
c.execute("select name, value from DeploymentAttrs where deployment = ?", (self.uuid,))
rows = c.fetchall()
res = {row[0]: row[1] for row in rows}
res['resources'] = {r.name: r.export() for r in self.resources.itervalues()}
return res
res = self._state.get_all_deployment_attrs(self.uuid)
res['resources'] = {r.name: r.export() for r in self.resources.itervalues()}
return res


def import_(self, attrs):
with self._db:
with self._state.db:
for k, v in attrs.iteritems():
if k == 'resources': continue
self._set_attr(k, v)
Expand All @@ -195,49 +161,21 @@ def import_(self, attrs):


def clone(self):
with self._db:
new = self._statefile.create_deployment()
self._db.execute("insert into DeploymentAttrs (deployment, name, value) " +
"select ?, name, value from DeploymentAttrs where deployment = ?",
(new.uuid, self.uuid))
new.configs_path = None
return new
return self._state.clone_deployment(self.uuid)


def _get_deployment_lock(self):
if self._lock_file_path is None:
lock_dir = os.environ.get("HOME", "") + "/.nixops/locks"
if not os.path.exists(lock_dir): os.makedirs(lock_dir, 0700)
self._lock_file_path = lock_dir + "/" + self.uuid
class DeploymentLock(object):
def __init__(self, depl):
self._lock_file_path = depl._lock_file_path
self._logger = depl.logger
self._lock_file = None
def __enter__(self):
self._lock_file = open(self._lock_file_path, "w")
fcntl.fcntl(self._lock_file, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
try:
fcntl.flock(self._lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
self._logger.log(
"waiting for exclusive deployment lock..."
)
fcntl.flock(self._lock_file, fcntl.LOCK_EX)
def __exit__(self, exception_type, exception_value, exception_traceback):
self._lock_file.close()
return DeploymentLock(self)
return self._state.get_deployment_lock(self)


def delete_resource(self, m):
del self.resources[m.name]
with self._db:
self._db.execute("delete from Resources where deployment = ? and id = ?", (self.uuid, m.id))
self._state.delete_resource(self.uuid, m.id)


def delete(self, force=False):
"""Delete this deployment from the state file."""
with self._db:
with self._state.db:
if not force and len(self.resources) > 0:
raise Exception("cannot delete this deployment because it still has resources")

Expand All @@ -248,7 +186,7 @@ def delete(self, force=False):
if os.path.islink(p): os.remove(p)

# Delete the deployment from the database.
self._db.execute("delete from Deployments where uuid = ?", (self.uuid,))
self._state._delete_deployment(self.uuid)


def _nix_path_flags(self):
Expand Down Expand Up @@ -856,7 +794,7 @@ def evaluate_active(self, include=[], exclude=[], kill_obsolete=False):
self.evaluate()

# Create state objects for all defined resources.
with self._db:
with self._state.db:
for m in self.definitions.itervalues():
if m.name not in self.resources:
self._create_resource(m.name, m.get_type())
Expand Down Expand Up @@ -1197,9 +1135,7 @@ def rename(self, name, new_name):

m = self.resources.pop(name)
self.resources[new_name] = m

with self._db:
self._db.execute("update Resources set name = ? where deployment = ? and id = ?", (new_name, self.uuid, m.id))
self._state._rename_resource(self.uuid, m.id, new_name)


def send_keys(self, include=[], exclude=[]):
Expand Down Expand Up @@ -1244,15 +1180,6 @@ def _create_definition(xml, config, type_name):

raise nixops.deployment.UnknownBackend("unknown resource type ‘{0}’".format(type_name))

def _create_state(depl, type, name, id):
"""Create a resource state object of the desired type."""

for cls in _subclasses(nixops.resources.ResourceState):
if type == cls.get_type():
return cls(depl, name, id)

raise nixops.deployment.UnknownBackend("unknown resource type ‘{0}’".format(type))


# Automatically load all resource types.
def _load_modules_from(dir):
Expand Down
Loading