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
Added directory_usage to utils #629
Conversation
This parameter allows to modify login state machine behavior in case of a hung state.
This utility is similar to "show system directory-usage" Junos command (or UNIX "du" command). It allows to see how much space is taken by a particular directory on disk. Unit test added as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pklimai Thanks for submitting this pull request. I think this method is a useful addition. Please consider my comments and then we will looking at merging this feature.
# directory_usage - filesystem directory usage | ||
# ------------------------------------------------------------------------- | ||
|
||
def directory_usage(self, path="."): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest adding a depth=0
argument to the method.
""" | ||
Returns the directory usage, similar to the unix "du" command. | ||
|
||
:returns: approximate directory usage, including subdirectories, in bytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than returning a single integer, I suggest returning a dictionary similar to the return value of storage_usage()
. The first level key would be directory name. The second level keys could be blocks
, bytes
, and size
(where size
would be the 'human-readable' value with units).
Returning this type of data structure allows the support of the depth > 0
situation and allows returning the values in multiple formats so the user can easily access their preferred format without having to perform conversion.
|
||
:returns: approximate directory usage, including subdirectories, in bytes | ||
""" | ||
rsp = self._dev.rpc.get_directory_usage_information(path=path, depth="0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming depth
is now an argument method per comment on line 271, change depth="0"
to depth=str(depth)
.
|
||
used_space = used_space.strip() | ||
|
||
multiplier = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calculate the bytes
value using the number of blocks (the used-blocks
attribute of the <used-space>
element. Then it's simply: bytes = blocks * BLOCK_SIZE
. You can define BLOCK_SIZE = 512
at the beginning of the method.
""" | ||
Returns the directory usage, similar to the unix "du" command. | ||
|
||
:returns: approximate directory usage, including subdirectories, in bytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you implement my comments, then "approximate" can be quantified as 512-byte precision.
|
||
:returns: approximate directory usage, including subdirectories, in bytes | ||
""" | ||
rsp = self._dev.rpc.get_directory_usage_information(path=path, depth="0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a possibility this line will raise any of the exceptions raised when invoking an RPC. That's fine, but we might want to document it.
used_space = rsp.findtext("directory/used-space") | ||
|
||
if used_space is None: | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure returning None
makes sense for a missing <used-space>
element. Especially if you implement my recommendation on changing the return value to a dict
. Instead, I suggest raising a RpcError
exception.
Hi Stacy, Thanks for your excellent ideas, I will try to implement it. The only thing I'm not sure is why you want to make "depth" the method parameter. I just want directory_usage method to return usage for directory provided in the "path". So for example with depth=0 the return XML can be
while the same for depth=1
so there is a lot more information here, but all that is not needed - we still only need "directory/used-space" which is 2.1M anyway. So calling with other depths just consumes extra resources. Please tell me what you think.
|
I understand your use case for the directory method, but I can also envision other use cases which can be handled by adding a Here's a quick example with Sometimes I want the space consumed by all the home directories on the system:
Other times I'd like a single call to tell me the space consumed by each user's home directory:
Yes, you could invoke |
Thanks Stacy, I think you are right and it is better to have a more general method. Just pushed the changes, please review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the requested changes. Just a few more suggested changes after reviewing the most current diff.
'G': 1024**3, | ||
'T': 1024**4, | ||
} | ||
for directory in dirs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to for directory in rsp.findall('//directory'):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stacywsmith findall('//directory') fails with error,
File "/usr/lib/python3/dist-packages/lxml/_elementpath.py", line 246, in _build_path_iterator
raise SyntaxError("cannot use absolute path on element")
SyntaxError: cannot use absolute path on element
that's why I was using xpath() initially. However findall('.//directory') works, so I will use it.
|
||
used_space = used_space.strip() | ||
dirs = rsp.xpath("//directory") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete this line. Use findall()
on line 290 instead.
|
||
used_space = rsp.findtext("directory/used-space") | ||
rsp = self._dev.rpc.get_directory_usage_information(path=path, depth=str(depth)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add normalize=True
argument. So, line becomes:
rsp = self._dev.rpc.get_directory_usage_information(path=path, depth=str(depth), normalize=True)
This avoids having to call strip()
on lines 291-293.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stacywsmith using normalize=True was my initial intention, but then test test_directory_usage() fails with reason that is completely unclear to me:
Error
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/home/peter/PycharmProjects/py-junos-eznc/tests/unit/utils/test_fs.py", line 272, in test_directory_usage
self.assertEqual(self.fs.directory_usage(path="/var/tmp"),
File "/home/peter/PycharmProjects/py-junos-eznc/lib/jnpr/junos/utils/fs.py", line 281, in directory_usage
rsp = self._dev.rpc.get_directory_usage_information(path=path, depth=str(depth), normalize=True)
File "/home/peter/PycharmProjects/py-junos-eznc/lib/jnpr/junos/rpcmeta.py", line 156, in _exec_rpc
return self._junos.execute(rpc, **dec_args)
File "/usr/local/lib/python3.5/dist-packages/mock/mock.py", line 1062, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "/usr/local/lib/python3.5/dist-packages/mock/mock.py", line 1128, in _mock_call
ret_val = effect(*args, **kwargs)
File "/home/peter/PycharmProjects/py-junos-eznc/tests/unit/utils/test_fs.py", line 338, in _mock_manager
device_params = kwargs['device_params']
KeyError: 'device_params'
I see other utility methods use strip() rather extensively - I guess this is probably due to the same reason.
return None | ||
if rsp.findtext("directory/used-space") is None: | ||
# Likely, no such directory | ||
raise RpcError(rsp=rsp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry. I know I suggested this change, but now I'm rethinking it.
This would only raise an RpcError
if there was no <directory>
element in the response that contained a <used-space>
element. If there were multiple <directory>
elements in the response, but only some of them were missing the <used-space>
element, it wouldn't address that situation.
Now that you've modified to return a dictionary, we probably need to handle the exceptional conditions of missing elements within the loop over each <directory>
element. I suggest removing lines 283-285.
'T': 1024**4, | ||
} | ||
for directory in dirs: | ||
dir_name = directory.findtext("directory-name").strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove .strip()
from the end. If findtext()
doesn't find a matching element, it returns None
. Calling strip()
on a None
value would cause an exception to be raised. We can avoid this situation by implementing the comment on line 281.
'T': 1024**4, | ||
} | ||
for directory in dirs: | ||
dir_name = directory.findtext("directory-name").strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to check that the <directory-name>
element was actually found. Otherwise, we will end up trying to add a key with a value of None
to the dict. I suggest adding the following after line 291:
if dir_name is None:
raise RpcError(rsp=rsp)
"size": dir_size, | ||
"blocks": dir_blocks, | ||
"bytes": dir_blocks * BLOCK_SIZE, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to account for the possibility that the <used-space>
element (or used-blocks
attribute) doesn't exist. I suggest replacing lines 292-298 with:
used_space = directory.find('used-space')
if used_space is not None:
dir_size = used_space.text()
dir_blocks = used_space.get('used-blocks')
if dir_blocks is not None:
dir_blocks = int(dir_blocks)
dir_bytes = dir_blocks * BLOCK_SIZE
result[dir_name] = {
"size": dir_size,
"blocks": dir_blocks,
"bytes": dir_bytes,
}
1 similar comment
Pushed latest version, please review. As I said before I wasn't able to use "normalize" option (possibly a separate issue?). Instead using text.strip(), hopefully this is rather safe. Also note that in this version for non-existing "path" we just return {}, is it what we want? |
@pklimai Can you please fine tune your tests as its failing for Python 3.x |
@vnitinv That was some problem with the test system - test_sw_install_kwargs_force_host was failing, that I didn't touch. Now all tests passed. |
Thanks @pklimai. |
In PyEZ filesystem utils we were missing a method to see the size of the particular directory (together with all subdirectories). I added directory_usage util to do that.
Unit test for this util added as well.